mocking axios object in test file - jestjs

Runing simple test, in console it shows this error,
Why it says its not a function whereas obviously it is,
content referenced here
how to fix it?
import axios, { AxiosResponse } from 'axios'
export type Todo = {
userId: number
id: number
title: string
completed: boolean
}
const mockedAxios = axios as jest.Mocked<typeof axios>
describe('getTodos()', () => {
test('should return todo list', async () => {
//Our desired output
const todos: Todo[] = [
{
userId: 1,
id: 1,
title: 'todo-test-1',
completed: false,
}
]
const mockedResponse: AxiosResponse = {
data: todos,
status: 200,
statusText: 'OK',
headers: {},
config: {},
}
mockedAxios.get.mockResolvedValueOnce(mockedResponse)
expect(axios.get).not.toHaveBeenCalled()
expect(axios.get).toHaveBeenCalled()
expect(data).toEqual(todos)
})
})
package.json file content:
"scripts": {
...
"test": "react-app-rewired test"
},

Related

NodeJS: Creating a tree from a string for http routing

I am trying to create a tree like radix tree for http routing
My code:
interface Node {
nodes?: Record<string, Node>
methods?: Record<string, () => unknown>
}
class Tree {
root: Node
constructor() {
this.root = {}
}
insert(path: string, method: string, handler: () => unknown): void {
let node = this.root
path.split('/').forEach((path: string): void => {
path = path === '' ? '/' : path
node.nodes = node.nodes ?? {}
const child = node.nodes[path] ?? {}
node.nodes[path] = child
node = child
})
node.methods = node.methods ?? {}
if (!node.methods[method]) {
node.methods[method] = handler
}
}
}
const tree = new Tree()
tree.insert('/users', 'GET', (): string => {
return 'users get handler'
})
tree.insert('/posts', 'GET', (): string => {
return 'posts get handler'
})
tree.insert('/profile', 'GET', (): string => {
return 'profile get handler'
})
tree.insert('/profile/posts', 'GET', (): string => {
return 'profile posts get handler'
})
tree.insert('/profile/friends', 'GET', (): string => {
return 'profile friends get handler'
})
console.log(
JSON.stringify(tree),
)
Expected result:
{
root: {
nodes: {
"/": {
nodes: {
users: {
methods: {
GET: handler,
},
},
posts: {
methods: {
GET: handler,
},
},
profile: {
methods: {
GET: handler,
},
nodes: {
posts: {
methods: {
GET: handler,
},
},
friends: {
methods: {
GET: handler,
},
},
},
},
},
},
},
},
}
I would like to replace the root variable with nodes in the Tree class:
class Tree {
nodes: Record<string, Node>
}
To get the desired result:
{
nodes: {
"/": {
nodes: {
users: {
methods: {
GET: handler,
},
},
posts: {
methods: {
GET: handler,
},
},
profile: {
methods: {
GET: handler,
},
nodes: {
posts: {
methods: {
GET: handler,
},
},
friends: {
methods: {
GET: handler,
},
},
},
},
},
},
},
}
I tried several times to implement what I wanted, but in the end I got confused with my own code...
PS: Thank you all in advance for your answers :)
To achieve that your class works with a nodes member instead of root, you only need to change the first statement in your insert method.
Replace:
let node = this.root
with:
let node = { nodes: this.nodes };

Recaptcha v3 Invalid Input Response

I'm having a problem when trying to apply recaptcha into my web app.
Basically, it's returning the error message: "invalid-input-response"
What could I be doing wrong?
Stack:
#nuxtjs/recaptcha 1.1.1
express-recaptcha 5.1.0
nuxt 2.15.8
node 16.15.9
Here is my configuration on front, I don't have certain about the recaptcha mode, if I should use base or enterprise.
nuxt.config.js
modules: [
'#nuxtjs/recaptcha',
],
recaptcha: {
hideBadge: true,
mode: 'base',
siteKey: 'MY_SITE_KEY',
version: 3,
size: 'normal',
},
On index I don't know if has any problem here
Based on docs, it's only do that
On my action I'm sending the token alongside my data on that format
{
token: 'asdadsadadasdas',
review: {...}
}
index.vue
async mounted() {
try {
await this.$recaptcha.init()
} catch (e) {
console.log(e);
}
},
methods: {
...mapActions({
getProduct: "getProduct",
postReview: "postReview",
}),
async submit() {
const postReviewToken = await this.$recaptcha.execute('postReview')
try {
await this.postReview({
token: postReviewToken,
productId: this.$route.params.productId,
review: {
title: this.review.title,
content: this.review.content,
},
});
this.getProduct({ productId: this.$route.params.productId });
this.review = {
title: "",
content: "",
};
} catch (error) {}
},
},
Node
And on my api I just added the middleware as docs require
import { RecaptchaV3 } from 'express-recaptcha/dist'
const recaptcha = new RecaptchaV3(
'MY_SITE_KEY',
'MY_SECRET_KEY'
)
routes.post('/product/:id/review', recaptcha.middleware.verify, async (request: Request, response: Response) => {
if (request.recaptcha?.error) {
return response.status(400).json({ message: request.recaptcha.error })
}
const review = await prisma.review.create({
data: {
productId: request.params.id,
title: request.body.review.title,
content: request.body.review.content,
},
select: {
id: true,
title: true,
content: true,
}
})
response.json({review});
});

How I can solve $near query in mongoddb adbpter query building issue?

I'm trying to create a nearby searching platform with keystone.js
But the nearby queries not working with the keystone js.
How I can run mongoose $nearSphere query or aggregation queries in the keystone?
Hereby added my best try and errors.
Index.js
require('dotenv').config({
path: './.env'
});
const {
Keystone
} = require('#keystonejs/keystone');
const {
GraphQLApp
} = require('#keystonejs/app-graphql');
const {
AdminUIApp
} = require('#keystonejs/app-admin-ui');
const {
PasswordAuthStrategy
} = require('#keystonejs/auth-password');
const {
MongooseAdapter
} = require('#keystonejs/adapter-mongoose');
const initialiseData = require('./initial-data');
const {
Post,
User,
Category,
SubCategory,
Comment,
ForgottenPasswordToken,
} = require('./schema');
const PROJECT_NAME = 'rentospot';
const keystone = new Keystone({
appVersion: {
version: '1.0.0',
addVersionToHttpHeaders: false,
access: false,
},
cookie: {
secure: process.env.NODE_ENV === 'production', // Default to true in production
maxAge: 1000 * 60 * 60 * 24 * 30, // 30 days
sameSite: false,
},
cookieSecret: process.env.COOKIE_SECRET,
adapter: new MongooseAdapter({
mongoUri: 'mongodb+srv://*******:******.mongodb.net/my-app-key-stone'
}),
onConnect: process.env.CREATE_TABLES !== 'true' && initialiseData,
});
const postList = keystone.createList('Post', Post);
keystone.createList('Category', Category);
keystone.createList('SubCategory', SubCategory);
keystone.createList('Comment', Comment);
keystone.createList('User', User);
keystone.createList('ForgottenPasswordToken', ForgottenPasswordToken);
const authStrategy = keystone.createAuthStrategy({
type: PasswordAuthStrategy,
list: 'User',
});
const extendSchema = {
queries: [{
schema: 'nearBy: [Post]',
resolver: async(_) => {
try {
const {
adapter
} = postList;
const result = await adapter.find({
// "feature": true,
"venue.location": {
"$nearSphere": {
"$geometry": {
"type": "Point",
"coordinates": [12, 75],
},
"$minDistance": 0,
"$maxDistance": 500,
},
}
});
console.log(result)
return result;
} catch (e) {
console.log("errrrrrrrrrrrrrror: ", e)
return null
}
},
}, ]
};
keystone.extendGraphQLSchema(extendSchema)
module.exports = {
keystone,
apps: [new GraphQLApp(),
new AdminUIApp({
name: PROJECT_NAME,
enableDefaultRoute: true,
authStrategy
})
],
};
Schema.js
const fetch = require('node-fetch');
const { v4: uuid } = require('uuid');
const { sendEmail } = require('./emails');
const {
File,
Text,
Slug,
Relationship,
Select,
Password,
Checkbox,
CalendarDay,
DateTime,
Integer
} = require('#keystonejs/fields');
const { S3Adapter } = require('#keystonejs/file-adapters');
const { AuthedRelationship } = require('#keystonejs/fields-authed-relationship');
const { formatISO } = require('date-fns');
const { Wysiwyg } = require('#keystonejs/fields-wysiwyg-tinymce');
const LocationGoogle = require('./LocationGoogle');
const fileAdapter = new S3Adapter({
bucket: process.env.BUCKET_NAME,
folder: process.env.S3_PATH,
publicUrl: ({ filename }) =>
// `https://${process.env.CF_DISTRIBUTION_ID}.cloudfront.net/${process.env.S3_PATH}/${filename}`,
`https://${process.env.CF_DISTRIBUTION_ID}/${process.env.S3_PATH}/${filename}`,
s3Options: {
// Optional paramaters to be supplied directly to AWS.S3 constructor
apiVersion: '2006-03-01',
accessKeyId: process.env.API_KEY_ID,
secretAccessKey: process.env.SECRET_ACCESS_KEY,
region: process.env.REGION
},
uploadParams: ({ id }) => ({
Metadata: {
keystone_id: `${id}`,
},
}),
});
// Access control functions
const userIsAdmin = ({ authentication: { item: user } }) => Boolean(user && user.isAdmin);
const userOwnsItem = ({ authentication: { item: user } }) => {
if (!user) {
return false;
}
return { id: user.id };
};
const userIsCurrentAuth = ({ authentication: { item: user } }) => Boolean(user); // item will be undefined for anonymous user
const userIsAdminOrOwner = auth => {
const isAdmin = access.userIsAdmin(auth);
const isOwner = access.userOwnsItem(auth);
return isAdmin ? isAdmin : isOwner;
};
const access = { userIsAdmin, userIsCurrentAuth, userOwnsItem, userIsAdminOrOwner };
// Read: public / Write: admin
const DEFAULT_LIST_ACCESS = {
create: access.userIsCurrentAuth,
read: true,
update: access.userIsAdminOrOwner,
delete: access.userIsAdmin,
};
exports.User = {
access: {
update: access.userIsCurrentAuth,
delete: access.userIsAdmin,
},
fields: {
name: { type: Text },
dob: {
type: CalendarDay,
format: 'do MMMM yyyy',
dateFrom: '1901-01-01',
dateTo: formatISO(new Date(), { representation: 'date' }),
},
phone: {
type: Text,
isUnique: true,
access: { read: access.userIsCurrentAuth },
// hooks: {
// validateInput:
// }
},
email: { type: Text, isUnique: true, access: { read: access.userIsCurrentAuth } },
password: { type: Password, isRequired: true },
isAdmin: { type: Checkbox, access: { update: access.userIsAdmin } },
twitterHandle: { type: Text },
image: { type: File, adapter: fileAdapter },
},
hooks: {
afterChange: async ({ updatedItem, existingItem }) => {
if (existingItem && updatedItem.password !== existingItem.password) {
const url = process.env.SERVER_URL || 'http://localhost:3000';
const props = {
recipientEmail: updatedItem.email,
signinUrl: `${url}/signin`,
};
const options = {
subject: 'Your password has been updated',
to: updatedItem,
from: process.env.MAILGUN_FROM,
domain: process.env.MAILGUN_DOMAIN,
apiKey: process.env.MAILGUN_API_KEY,
};
await sendEmail('password-updated.jsx', props, options);
}
},
},
};
// exports.Organiser = {
// access: DEFAULT_LIST_ACCESS,
// fields: {
// user: { type: Relationship, ref: 'User' },
// order: { type: Integer },
// role: { type: Text },
// },
// };
// TODO: We can't access the existing item at the list update level yet,
// read access needs to check if event is "active" or if the user is admin
// read: ({ existingItem, authentication }) => access.userIsAdmin({ authentication }) || !!(existingItem && existingItem.status === 'active'),
exports.Post = {
access: DEFAULT_LIST_ACCESS,
fields: {
title: { type: Text },
slug: { type: Slug, from: 'title' },
author: {
type: AuthedRelationship,
ref: 'User',
isRequired: true,
access: {
read: userIsCurrentAuth,
update: userIsAdmin,
},
},
status: {
type: Select,
defaultValue: 'published',
options: [
{ label: 'Draft', value: 'draft' },
{ label: 'Published', value: 'published' },
],
},
feature: { type: Checkbox, access: { update: access.userIsAdmin } },
assured: { type: Checkbox, access: { update: access.userIsAdmin } },
posted: { type: DateTime, format: 'dd/MM/yyyy' },
categories: {
type: Relationship,
ref: 'Category',
many: false,
},
subCategories: {
type: Relationship,
ref: 'SubCategory',
many: true,
},
description: { type: Wysiwyg },
venue: {
type: LocationGoogle,
googleMapsKey: process.env.GOOGLE_MAPS_KEY,
// hooks: {
// resolveInput: async ({ originalInput }) => {
// try {
// const placeId = originalInput.venue;
// if (typeof placeId === 'undefined') {
// // Nothing was passed in, so we can bail early.
// return undefined;
// }
// const r = await fetch(
// `https://maps.googleapis.com/maps/api/geocode/json?place_id=${placeId}&key=${process.env.GOOGLE_MAPS_KEY}`
// )
// const response = await r.json()
// if (response.results && response.results[0]) {
// const { place_id, formatted_address } = response.results[0];
// const { lat, lng } = response.results[0].geometry.location;
// return {
// googlePlaceID: place_id,
// formattedAddress: formatted_address,
// lat,
// lng,
// loc: {
// type: "Point",
// coordinates: [lng, lat]
// }
// };
// }
// return null;
// }
// catch (e) {
// console.log("error:", e)
// }
// },
// validateInput: async (x) => {
// console.log("---------------------------------item 2-------------------------------------- : ",x);
// },
// beforeChange: async (x) => {
// console.log("========", x)
// },
// afterChange: async (x) => {
// console.log("++++", x)
// }
// },
},
locationAddress: { type: Text },
locationDescription: { type: Text },
createdby: {
type: AuthedRelationship,
ref: 'User',
isRequired: true,
access: {
read: userIsCurrentAuth,
update: userIsAdmin,
},
},
image: {
type: File,
adapter: fileAdapter,
hooks: {
beforeChange: async ({ existingItem }) => {
if (existingItem && existingItem.image) {
await fileAdapter.delete(existingItem.image);
}
},
},
},
},
hooks: {
afterDelete: ({ existingItem }) => {
if (existingItem.image) {
fileAdapter.delete(existingItem.image);
}
},
},
adminConfig: {
defaultPageSize: 20,
defaultColumns: 'title, status',
defaultSort: 'title',
},
labelResolver: item => item.title,
};
exports.Category = {
// access: userIsAdmin,
fields: {
name: { type: Text },
slug: { type: Slug, from: 'name' },
image: {
type: File,
adapter: fileAdapter,
hooks: {
beforeChange: async ({ existingItem }) => {
if (existingItem && existingItem.image) {
await fileAdapter.delete(existingItem.image);
}
},
},
},
},
};
exports.SubCategory = {
// access: userIsAdmin,
fields: {
name: { type: Text },
slug: { type: Slug, from: 'name' },
categories: {
type: Relationship,
ref: 'Category',
many: false,
},
image: {
type: File,
adapter: fileAdapter,
hooks: {
beforeChange: async ({ existingItem }) => {
if (existingItem && existingItem.image) {
await fileAdapter.delete(existingItem.image);
}
},
},
},
},
};
exports.Comment = {
access: {
create: userIsCurrentAuth,
update: userIsAdminOrOwner,
},
fields: {
body: { type: Text, isMultiline: true },
originalPost: {
type: Relationship,
ref: 'Post',
},
author: {
type: AuthedRelationship,
ref: 'User',
isRequired: true,
access: {
create: userIsAdmin,
update: userIsAdmin,
},
},
posted: { type: CalendarDay },
},
labelResolver: item => item.body,
};
// exports.Talk = {
// access: DEFAULT_LIST_ACCESS,
// fields: {
// name: { type: Text },
// event: { type: Relationship, ref: 'Event.talks' },
// speakers: { type: Relationship, ref: 'User.talks', many: true },
// isLightningTalk: { type: Checkbox },
// description: { type: Wysiwyg },
// },
// };
// exports.Rsvp = {
// access: {
// create: true,
// read: true,
// update: ({ authentication: { item } }) => {
// if (!item) {
// return false;
// }
// return { user: { id: item.id } };
// },
// delete: access.userIsAdmin,
// },
// fields: {
// event: { type: Relationship, ref: 'Event' },
// user: { type: Relationship, ref: 'User' },
// status: { type: Select, options: 'yes, no' },
// },
// hooks: {
// validateInput: async ({ context, resolvedData, existingItem }) => {
// const { status } = resolvedData;
// const { event: eventId } = existingItem ? existingItem : resolvedData;
// if (status === 'no') {
// return;
// }
// const { data } = await context.executeGraphQL({
// query: `query {
// event: Event(where: { id: "${eventId}" }) {
// id
// startTime
// maxRsvps
// isRsvpAvailable
// }
// allRsvps(where: { event: { id: "${eventId}" }}) {
// id
// }
// }`,
// });
// const { event, allRsvps } = data;
// if (
// !event ||
// !event.isRsvpAvailable ||
// !event.startTime ||
// new Date() > new Date(event.startTime) ||
// allRsvps.length >= event.maxRsvps
// ) {
// throw 'Error rsvping to event';
// }
// },
// },
// };
// exports.Sponsor = {
// access: DEFAULT_LIST_ACCESS,
// fields: {
// name: { type: Text },
// website: { type: Text },
// logo: { type: CloudinaryImage, adapter: cloudinaryAdapter },
// },
// };
exports.ForgottenPasswordToken = {
access: {
create: true,
read: true,
update: access.userIsAdmin,
delete: access.userIsAdmin,
},
fields: {
user: {
type: Relationship,
ref: 'User',
access: {
read: access.userIsAdmin,
},
},
token: {
type: Text,
isRequired: true,
isUnique: true,
access: {
read: access.userIsAdmin,
},
},
requestedAt: { type: DateTime, isRequired: true },
accessedAt: { type: DateTime },
expiresAt: { type: DateTime, isRequired: true },
},
hooks: {
afterChange: async ({ context, updatedItem, existingItem }) => {
if (existingItem) return null;
const now = new Date().toISOString();
const { errors, data } = await context.executeGraphQL({
context: context.createContext({ skipAccessControl: true }),
query: `
query GetUserAndToken($user: ID!, $now: DateTime!) {
User( where: { id: $user }) {
id
email
}
allForgottenPasswordTokens( where: { user: { id: $user }, expiresAt_gte: $now }) {
token
expiresAt
}
}
`,
variables: { user: updatedItem.user.toString(), now },
});
if (errors) {
console.error(errors, `Unable to construct password updated email.`);
return;
}
const { allForgottenPasswordTokens, User } = data;
const forgotPasswordKey = allForgottenPasswordTokens[0].token;
const url = process.env.SERVER_URL || 'http://localhost:3000';
const props = {
forgotPasswordUrl: `${url}/change-password?key=${forgotPasswordKey}`,
recipientEmail: User.email,
};
const options = {
subject: 'Request for password reset',
to: User.email,
from: process.env.MAILGUN_FROM,
domain: process.env.MAILGUN_DOMAIN,
apiKey: process.env.MAILGUN_API_KEY,
};
await sendEmail('forgot-password.jsx', props, options);
},
},
};
exports.customSchema = {
mutations: [
{
schema: 'startPasswordRecovery(email: String!): ForgottenPasswordToken',
resolver: async (obj, { email }, context) => {
const token = uuid();
const tokenExpiration =
parseInt(process.env.RESET_PASSWORD_TOKEN_EXPIRY) || 1000 * 60 * 60 * 24;
const now = Date.now();
const requestedAt = new Date(now).toISOString();
const expiresAt = new Date(now + tokenExpiration).toISOString();
const { errors: userErrors, data: userData } = await context.executeGraphQL({
context: context.createContext({ skipAccessControl: true }),
query: `
query findUserByEmail($email: String!) {
allUsers(where: { email: $email }) {
id
email
}
}
`,
variables: { email: email },
});
if (userErrors || !userData.allUsers || !userData.allUsers.length) {
console.error(
userErrors,
`Unable to find user when trying to create forgotten password token.`
);
return;
}
const userId = userData.allUsers[0].id;
const result = {
userId,
token,
requestedAt,
expiresAt,
};
const { errors } = await context.executeGraphQL({
context: context.createContext({ skipAccessControl: true }),
query: `
mutation createForgottenPasswordToken(
$userId: ID!,
$token: String,
$requestedAt: DateTime,
$expiresAt: DateTime,
) {
createForgottenPasswordToken(data: {
user: { connect: { id: $userId }},
token: $token,
requestedAt: $requestedAt,
expiresAt: $expiresAt,
}) {
id
token
user {
id
}
requestedAt
expiresAt
}
}
`,
variables: result,
});
if (errors) {
console.error(errors, `Unable to create forgotten password token.`);
return;
}
return true;
},
},
{
schema: 'changePasswordWithToken(token: String!, password: String!): User',
resolver: async (obj, { token, password }, context) => {
const now = Date.now();
const { errors, data } = await context.executeGraphQL({
context: context.createContext({ skipAccessControl: true }),
query: `
query findUserFromToken($token: String!, $now: DateTime!) {
passwordTokens: allForgottenPasswordTokens(where: { token: $token, expiresAt_gte: $now }) {
id
token
user {
id
}
}
}`,
variables: { token, now },
});
if (errors || !data.passwordTokens || !data.passwordTokens.length) {
console.error(errors, `Unable to find token`);
throw errors.message;
}
const user = data.passwordTokens[0].user.id;
const tokenId = data.passwordTokens[0].id;
const { errors: passwordError } = await context.executeGraphQL({
context: context.createContext({ skipAccessControl: true }),
query: `mutation UpdateUserPassword($user: ID!, $password: String!) {
updateUser(id: $user, data: { password: $password }) {
id
}
}`,
variables: { user, password },
});
if (passwordError) {
console.error(passwordError, `Unable to change password`);
throw passwordError.message;
}
await context.executeGraphQL({
context: context.createContext({ skipAccessControl: true }),
query: `mutation DeletePasswordToken($tokenId: ID!) {
deleteForgottenPasswordToken(id: $tokenId) {
id
}
}
`,
variables: { tokenId },
});
return true;
},
},
],
};
LocationGoogle is a directory of the files exactly #keystonejs/fields-location-google and small changes in its Implementation.js
Implementation.js
const { Implementation } = require('#keystonejs/fields');
const { MongooseFieldAdapter } = require('#keystonejs/adapter-mongoose');
const fetch = require('node-fetch');
// Disabling the getter of mongoose >= 5.1.0
// https://github.com/Automattic/mongoose/blob/master/migrating_to_5.md#checking-if-a-path-is-populated
class LocationGoogleImplementation extends Implementation {
constructor(_, { googleMapsKey }) {
super(...arguments);
this.graphQLOutputType = 'LocationGoogle';
if (!googleMapsKey) {
throw new Error(
'You must provide a `googleMapsKey` to LocationGoogle Field. To generate a Google Maps API please visit: https://developers.google.com/maps/documentation/javascript/get-api-key'
);
}
this._googleMapsKey = googleMapsKey;
}
get _supportsUnique() {
return false;
}
extendAdminMeta(meta) {
return {
...meta,
googleMapsKey: this._googleMapsKey,
};
}
gqlOutputFields() {
return [`${this.path}: ${this.graphQLOutputType}`];
}
gqlQueryInputFields() {
return [...this.equalityInputFields('String'), ...this.inInputFields('String')];
}
getGqlAuxTypes() {
return [
`
type Location{
coordinates: [Float]
},
type ${this.graphQLOutputType} {
location: Location
googlePlaceID: String
formattedAddress: String
lat: Float
lng: Float
}
`,
];
}
// Called on `User.avatar` for example
gqlOutputFieldResolvers() {
return {
[this.path]: item => {
const itemValues = item[this.path];
if (!itemValues) {
return null;
}
return itemValues;
},
};
}
async resolveInput({ resolvedData }) {
const placeId = resolvedData[this.path];
// NOTE: The following two conditions could easily be combined into a
// single `if (!inputId) return inputId`, but that would lose the nuance of
// returning `undefined` vs `null`.
// Premature Optimisers; be ware!
if (typeof placeId === 'undefined') {
// Nothing was passed in, so we can bail early.
return undefined;
}
if (placeId === null) {
// `null` was specifically set, and we should set the field value to null
// To do that we... return `null`
return null;
}
const response = await fetch(
`https://maps.googleapis.com/maps/api/geocode/json?place_id=${placeId}&key=${this._googleMapsKey}`
).then(r => r.json());
if (response.results && response.results[0]) {
const { place_id, formatted_address } = response.results[0];
const { lat, lng } = response.results[0].geometry.location;
return {
location: {
type: "Point",
coordinates: [lng, lat]
},
googlePlaceID: place_id,
formattedAddress: formatted_address,
lat: lat,
lng: lng,
};
}
return null;
}
gqlUpdateInputFields() {
return [`${this.path}: String`];
}
gqlCreateInputFields() {
return [`${this.path}: String`];
}
getBackingTypes() {
const type = `null | {
location: {
type: "Point",
coordinates: [lng, lat]
};
googlePlaceID: string;
formattedAddress: string;
lat: number;
lng: number;
}
`;
return { [this.path]: { optional: true, type } };
}
}
const CommonLocationInterface = superclass =>
class extends superclass {
getQueryConditions(dbPath) {
return {
...this.equalityConditions(dbPath),
...this.inConditions(dbPath),
};
}
};
class MongoLocationGoogleInterface extends CommonLocationInterface(MongooseFieldAdapter) {
addToMongooseSchema(schema) {
const schemaOptions = {
type: {
location: {
type: { type: String, default: "Point" },
coordinates: [Number]
},
googlePlaceID: String,
formattedAddress: String,
lat: Number,
lng: Number,
},
};
schema.add({
[this.path]: this.mergeSchemaOptions(schemaOptions, this.config)
});
}
}
module.exports = {
LocationGoogleImplementation,
MongoLocationGoogleInterface
}
package.json
{
"name": "#keystonejs/example-projects-blank",
"description": "A blank KeystoneJS starter project.",
"private": true,
"version": "5.0.15",
"author": "The KeystoneJS Development Team",
"repository": "https://github.com/keystonejs/keystone/tree/master/packages/create-keystone-app/example-projects/blank",
"homepage": "https://github.com/keystonejs/keystone",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"scripts": {
"dev": "cross-env NODE_ENV=development ENABLE_DEV_FEATURES=false DISABLE_LOGGING=false nodemon --exec keystone dev --harmony",
"build": "cross-env NODE_ENV=production ENABLE_DEV_FEATURES=false keystone build",
"start": "cross-env NODE_ENV=production keystone start",
"create-tables": "cross-env keystone create-tables"
},
"dependencies": {
"#arch-ui/layout": "^0.2.14",
"#arch-ui/typography": "^0.0.18",
"#keystonejs/adapter-mongoose": "^10.1.2",
"#keystonejs/app-admin-ui": "^7.3.13",
"#keystonejs/app-graphql": "^6.2.1",
"#keystonejs/auth-password": "^6.0.0",
"#keystonejs/email": "^5.2.0",
"#keystonejs/fields-authed-relationship": "^1.0.16",
"#keystonejs/fields-location-google": "^3.2.1",
"#keystonejs/fields-wysiwyg-tinymce": "^5.3.15",
"#keystonejs/file-adapters": "^7.0.8",
"#keystonejs/keystone": "^18.1.0",
"cross-env": "^7.0.3",
"date-fns": "^2.16.1",
"dotenv": "^8.2.0",
"google-maps-react": "^2.0.6",
"node-fetch": "^2.6.1",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"uuid": "^8.3.2"
},
"devDependencies": {
"nodemon": "^2.0.7"
}
}
The Error screenshot attached bellow

Dynamic testing with Jest, useRouteMatch, react-route-dom

I have a Switch component that renders Routes based on a switch statement that takes useRouteMatch().path as the argument and returns a string.
In my unit test I want to dynamically mock this but it seems I can only set one jest.mock per file.
SectionComponent.tsx:
import React from 'react'
import { useRouteMatch } from 'react-router-dom'
import * as strings from '../../../strings'
import Header from '../../shared/header/Header'
const SectionComponent: React.FC = ({ children }) => {
const { path } = useRouteMatch()
const sectionTitle = () => {
switch (path) {
case '/header':
return strings.demo.header.title
case '/navbars':
return strings.demo.navbar.title
case '/button':
// ...etc
default:
return ''
}
}
return (
<div>
<Header
title={sectionTitle()}
titleColor={strings.theme.orange}
titleSize="S"
bgColor={strings.theme.white}
size="S"
/>
{children}
</div>
)
}
export default SectionComponent
SectionComponent.spec.tsx:
import React from 'react'
import { render } from '#testing-library/react'
import SectionComponent from './SectionComponent'
import * as strings from '../../../strings'
// list I want to dynamically generate useRouteMatch().path and expected result from
const options = [
{ name: 'header', to: '/header' },
{ name: 'navbars', to: '/navbars' },
{ name: 'button', to: '/button' },
{ name: 'typography', to: '/typography' },
{ name: 'form', to: '/form' },
{ name: 'navs', to: '/navs' },
{ name: 'indicators', to: '/indicators' },
{ name: 'list-group', to: '/list-group' },
{ name: 'cards', to: '/cards' },
]
// Can only do this once per file
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'), // use actual for all non-hook parts
useRouteMatch: () => ({ path: '/header' }), // return value can't access outside variables eg option.to ('/header')
}))
describe('SectionComponent', () => {
// ideally want to map thorugh options and test here:
it('should render with each Title', () => {
const { getByText } = render(<SectionComponent />)
expect(getByText(strings.demo.header.title)).toBeInTheDocument()
})
})
Does anyone have any suggestions? I'm happy to provide more info if necessary
Figured a workaround using jest.fn().mockReturnValueOnce() multiple times
import React from 'react'
import { render } from '#testing-library/react'
import SectionComponent from './SectionComponent'
const options = [
{ name: 'Headers' },
{ name: 'Navbars' },
{ name: 'Button' },
{ name: 'Typography' },
{ name: 'Forms' },
{ name: 'Navs' },
{ name: 'Indicators' },
{ name: 'List Group' },
{ name: 'Cards' },
]
jest.mock('react-router', () => ({
useRouteMatch: jest
.fn()
.mockReturnValueOnce({ path: '/header' })
.mockReturnValueOnce({ path: '/navbars' })
.mockReturnValueOnce({ path: '/button' })
.mockReturnValueOnce({ path: '/typography' })
.mockReturnValueOnce({ path: '/form' })
.mockReturnValueOnce({ path: '/navs' })
.mockReturnValueOnce({ path: '/indicators' })
.mockReturnValueOnce({ path: '/list-group' })
.mockReturnValueOnce({ path: '/cards' }),
}))
options.forEach(option => {
describe('SectionComponent', () => {
it('should render with each Title', () => {
const { getByText } = render(<SectionComponent />)
expect(getByText(option.name)).toBeInTheDocument()
})
})
})
Maybe not the most efficient but it works

Must provide source or customer stripe live mode

this is my first time using stripe and I am getting an error Must provide source or customer. once I went live. In the test mode I used "tok_mastercard" as my source but clearly when I went live it isn't valid. What am I missing here please help.
This is my POST request in the backend
stripe.charges
.create({
amount: req.renter.rent * 100,
currency: "usd",
source: req.body.token,
application_fee_amount: req.renter.rent * 0.05 * 100,
transfer_data: {
//the destination needs to not be hard coded it needs to
//come from what property the renter is in
destination: req.renter.stripeConnectId,
// destination: "acct_1GOCMqDfw1BzXvj0",
},
})
.then((charge) => {
console.log(req.renter);
res.send(charge);
})
.catch((err) => {
console.log(err);
});
});
this is my two functions in the frontend using react-native and node
handleCardPayPress = async () => {
try {
this.setState({ loading: true, token: null });
const token = await stripe.paymentRequestWithCardForm({
// Only iOS support this options
smsAutofillDisabled: true,
requiredBillingAddressFields: "full",
prefilledInformation: {
billingAddress: {
name: "",
line1: "",
line2: "",
city: "",
state: "",
country: "US",
postalCode: "",
email: "",
},
},
});
this.setState({ loading: false, token });
} catch (error) {
this.setState({ loading: false });
}
};
makePayment = async () => {
try {
//Mate the payment
const response = await unitApi.post("/payments", {
data: {
amount: this.state.renter.rent,
currency: "usd",
token: this.state.token,
},
});
Alert.alert("Success!", `Confirmation ID: ${response.data.id}`, [
{ text: "Done" },
]);
console.log(response.data);
// navigate("Home");
} catch (err) {
//failiur and showing the error message
console.log(err);
Alert.alert("Failed!", "Card declined.", [{ text: "Declined" }]);
}
};
Odds are pretty good that this.state.token doesn't contain what you think it does in the unitApi.post() function call; I'd recommend logging that and seeing if that helps, and also logging req.body.token server-side.

Resources