How can I test google.maps.Geocoder? - javascript

Hi! I'm attempting to write a "simple" test to check for a react class component state change. Specifically, I'm testing if the lat(latitude) and lng(longitude) states change if Google successfully geocodes some string(address) that I send it. Here is an example of what I want to test (i.e. the lat and lng states being set to results[0].geometry.location.lat()):
getLatLong = (address) => {
const that = this;
var geo = new google.maps.Geocoder;
geo.geocode({'address':address},(results, status) => {
if (status == google.maps.GeocoderStatus.OK) {
that.setState(
{
center: {
lat: results[0].geometry.location.lat(),
lng: results[0].geometry.location.lng(),
},
});
}
});
}
In my jest suite, I'm having issues writing my test and spying/mocking out the google.maps.Geocoder because the google library is never imported. It is attached via a script, like so:
<script async defer
src=<%="https://maps.googleapis.com/maps/api/js?key=#{Rails.application.config.google_api_key}&callback=initMap"%>>
</script>
Just so its confirmed, the code works as intended after manual testing, but I receive errors inside my testing suite when attempting to spy like so:
let geocoderSpy = jest.spyOn(google.maps, 'Geocoder');
I receive an error like this:
● EventMap › getLatLong works as intended by getting coordinates of valid locations › calls the geocoder function
ReferenceError: google is not defined
34 | let geocoder;
35 | beforeEach(() => {
> 36 | let geocoderSpy = jest.spyOn(google.maps, 'Geocoder');
| ^
37 | geocoder = jest.createSpy('Geocoder', ['geocode']);
38 | geocoderSpy.and.returnValue(geocoder);
39 | });
at Object.google (app/javascript/__tests__/EventPage/EventMap.test.jsx:36:42)
So then, I followed up this problem and found this stackoverflow post and this answer that claimed to solve it by adding something like this to jest in the package.json file...
"globals": { "google": { } }
This also failed to solve my problem. I think the solution is to somehow find out how to import google, but not quite sure how to do that.... If anyone can help me out that would be greatly appreciated. I would love to learn how to test something like this. In advance, many thanks. Austin

It seems that you are not the only one having suffered through the terrors of testing Google maps with Jest. I found a lot of projects and conversations around. But they all seem to recommend mocking the Google maps library, by doing something similar to this :
const setupGoogleMock = () => {
/*** Mock Google Maps JavaScript API ***/
const google = {
maps: {
places: {
AutocompleteService: () => {},
PlacesServiceStatus: {
INVALID_REQUEST: 'INVALID_REQUEST',
NOT_FOUND: 'NOT_FOUND',
OK: 'OK',
OVER_QUERY_LIMIT: 'OVER_QUERY_LIMIT',
REQUEST_DENIED: 'REQUEST_DENIED',
UNKNOWN_ERROR: 'UNKNOWN_ERROR',
ZERO_RESULTS: 'ZERO_RESULTS',
},
},
Geocoder: () => {},
GeocoderStatus: {
ERROR: 'ERROR',
INVALID_REQUEST: 'INVALID_REQUEST',
OK: 'OK',
OVER_QUERY_LIMIT: 'OVER_QUERY_LIMIT',
REQUEST_DENIED: 'REQUEST_DENIED',
UNKNOWN_ERROR: 'UNKNOWN_ERROR',
ZERO_RESULTS: 'ZERO_RESULTS',
},
},
};
global.window.google = google;
};
// in test file.
beforeAll(() => {
setupGoogleMock();
});
(source : https://github.com/hibiken/react-places-autocomplete/issues/189#issuecomment-377770674)
There is even an npm package for it! https://github.com/hupe1980/jest-google-maps-mock
Not sure if it will solve your problem, though, because this will only allow you to check if you have correctly called the right method from the API, not to check if you get the correct response from the API.
However, I don't know if it is advisable to actually do real API calls when testing such a functionality.

Inspired by the answer from Mathieu. I had to make a little change to adapt this solution for my project (Nuxt.js/Vue.JS).
I've replaced the Geocoder function with a mock of the Geocoder class.
const setupGoogleMock = () => {
/** * Mock Google Maps JavaScript API ***/
const google = {
maps: {
places: {
AutocompleteService: () => {},
PlacesServiceStatus: {
INVALID_REQUEST: 'INVALID_REQUEST',
NOT_FOUND: 'NOT_FOUND',
OK: 'OK',
OVER_QUERY_LIMIT: 'OVER_QUERY_LIMIT',
REQUEST_DENIED: 'REQUEST_DENIED',
UNKNOWN_ERROR: 'UNKNOWN_ERROR',
ZERO_RESULTS: 'ZERO_RESULTS',
},
},
Geocoder: class {
public async geocode() {
return Promise.resolve()
}
},
GeocoderStatus: {
ERROR: 'ERROR',
INVALID_REQUEST: 'INVALID_REQUEST',
OK: 'OK',
OVER_QUERY_LIMIT: 'OVER_QUERY_LIMIT',
REQUEST_DENIED: 'REQUEST_DENIED',
UNKNOWN_ERROR: 'UNKNOWN_ERROR',
ZERO_RESULTS: 'ZERO_RESULTS',
},
},
}
// #ts-ignore
global.window.google = google
}
setupGoogleMock()

Related

Posting result data to a website (like API or a telegram bot) after a test on Cypress

I've finished writing my first Cypress test. Everything is good except I'm struggling to post the result data to a website. Because I want to send the result data and also if any errors occurs the result screenshot to our coworker telegram group.
For the last two days I've tried everything and couldn't find any solution.
I've tried those in my test script (cypress/integration/test.js);
Cypress.on('test:after:run', (test, runnable) => {
console.log('test,runnable', test, runnable)
const details = {
projectKey: Cypress.env('zephyr-project-key'),
testName: test.invocationDetails.relativeFile,
status: test.status,
error: runnable.err.message,
retries: runnable.retries.length,
duration: test.wallClockDuration,
startTime: test.wallClockStartedAt
}
cy.request('POST', 'http://mywebsite.com/notify.php', { body: details })
fetch('http://mywebsite.com/notify.php')
})
Also this didn't work (cypress/plugins/index.js);
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
on('after:run', (results) => {
if (results) {
// results will be undefined in interactive mode
console.log(results.totalPassed, 'out of', results.totalTests, 'passed')
fetch('http://mywebsite.com/notify.php');
}
})
}
Edit: This is day 3 and I still couldn't solve this. What I've seen from Cypress help page is that cy.task() calls do not fire in 'test:after:run' event block;
https://github.com/cypress-io/cypress/issues/4823
I've seen some telegram groups who can do what I'm trying to do. All I need is to be able to get the results and post it to my website.
The third parameter to cy.request() is body, you don't have to wrap it.
Cypress.on('test:after:run', (test, runnable) => {
const details = {
projectKey: Cypress.env('zephyr-project-key'),
testName: test.invocationDetails.relativeFile,
status: test.status,
error: runnable.err?.message, // need err?.message if there is no error
retries: runnable.retries.length,
duration: test.wallClockDuration,
startTime: test.wallClockStartedAt
}
cy.request('POST', 'http://mywebsite.com/notify.php', details) // don't wrap details
.then(res => expect(res.status).to.eq(201)) // confirm result
})

Can you track background geolocation with React Native?

Problem
I'd like to be able to track a users location even when the app is no longer in the foreground (e.g. The user has switch to another app or switched to the home screen and locked their phone).
The use case would be a user tracking a run. They could open the app and press 'start' at the beginning of their run, then switch or minimise the app (press the home button) and lock the screen. At the end of the run they could bring the app into the foreground and press 'stop' and the app would tell them distance travelled on the run.
Question
Is tracking background geolocation possible on both iOS and Android using pure react native?
The react native docs on geolocation (https://facebook.github.io/react-native/docs/geolocation) are not very clear or detailed. The documented linked above eludes to background geolocation on iOS (without being fully clear) but does not mention Android.
Would it be best that I use Expo?
UPDATE 2019 EXPO 33.0.0:
Expo first deprecated it for their SDK 32.0.0 to meet app store guidelines but then reopened it in SDK 33.0.0.
Since, they have made it super easy to be able to implement background location. Use this code snippet that I used to make background geolocation work.
import React from 'react';
import { Text, TouchableOpacity } from 'react-native';
import * as TaskManager from 'expo-task-manager';
import * as Location from 'expo-location';
const LOCATION_TASK_NAME = 'background-location-task';
export default class Component extends React.Component {
onPress = async () => {
await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, {
accuracy: Location.Accuracy.Balanced,
timeInterval: 5000,
});
};
render() {
return (
<TouchableOpacity onPress={this.onPress} style={{marginTop: 100}}>
<Text>Enable background location</Text>
</TouchableOpacity>
);
}
}
TaskManager.defineTask(LOCATION_TASK_NAME, ({ data, error }) => {
if (error) {
alert(error)
// Error occurred - check `error.message` for more details.
return;
}
if (data) {
const { locations } = data;
alert(JSON.stringify(locations); //will show you the location object
//lat is locations[0].coords.latitude & long is locations[0].coords.longitude
// do something with the locations captured in the background, possibly post to your server with axios or fetch API
}
});
The code works like a charm. One thing to note is that you cannot use geolocation in the Expo App. However, you can use it in your standalone build. Consequently, if you want to use background geolocation you have to use this code and then do expo build:ios and upload to the appstore in order to be able to get a users background location.
Additionally, note that you must include
"UIBackgroundModes":[
"location",
"fetch"
]
In the info.plist section of your app.json file.
The Expo Team release a new feature in SDK 32 that allow you tracking in background the location.
https://expo.canny.io/feature-requests/p/background-location-tracking
Yes is possible, but not using Expo, there are two modules that I've seen:
This is a comercial one, you have to buy a license https://github.com/transistorsoft/react-native-background-geolocation
And this https://github.com/mauron85/react-native-background-geolocation
Webkit is currently evaluating a Javascript-only solution. You can add your voice here
For a fully documented proof-of-concept example please see Brotkrumen.
The most popular RN geolocation library is https://github.com/react-native-geolocation/react-native-geolocation, and it supports this quite easily. I prefer this library over others because it automatically handles asking for permissions and such, and seems to have the simplest API.
Just do this:
Geolocation.watchPosition((position)=>{
const {latitude, longitude} = position.coords;
// Do something.
})
This requires no additional setup other than including the background modes fetch and location, and also the appropriate usage descriptions.
I find this more usable than Expo's API because it doesn't require any weird top level code and also doesn't require me to do anything other than create a watch position handler, which is really nice.
EDIT 2023!:
These days I would highly recommend using Expo's library instead of any of the other community libraries (mainly because our app started crashing when android got an OS update b/c of the lib I was using).
In fact, if you have to choose between expo and non expo library, always choose the expo library if only for the stability. Setting up expo's background location watching isn't super well documented but here's what I did to get it working in our app:
import { useEffect, useRef } from "react";
import * as Location from "expo-location";
import { LatLng } from "react-native-maps";
import * as TaskManager from "expo-task-manager";
import { LocationObject } from "expo-location";
import { v4 } from "uuid";
type Callback = (coords: LatLng) => void;
const BACKGROUND_TASK_NAME = "background";
const executor: (body: TaskManager.TaskManagerTaskBody<object>) => void = (
body
) => {
const data = body.data as unknown as { locations: LocationObject[] };
const l = data?.locations[0];
if (!l) return;
for (const callback of Object.values(locationCallbacks)) {
callback({
latitude: l.coords.latitude,
longitude: l.coords.longitude,
});
}
};
TaskManager.defineTask(BACKGROUND_TASK_NAME, executor);
const locationCallbacks: { [key: string]: Callback } = {};
const hasStartedBackgroundTaskRef = {
hasStarted: false,
};
function startBackgroundTaskIfNecessary() {
if (hasStartedBackgroundTaskRef.hasStarted) return;
Location.startLocationUpdatesAsync(BACKGROUND_TASK_NAME, {
accuracy: Location.Accuracy.Balanced,
}).catch((e) => {
hasStartedBackgroundTaskRef.hasStarted = false;
});
hasStartedBackgroundTaskRef.hasStarted = true;
}
function addLocationCallback(callback: Callback) {
const id = v4() as string;
locationCallbacks[id] = callback;
return {
remove: () => {
delete locationCallbacks[id];
},
};
}
export default function useLocationChangeListener(
callback: Callback | null,
active: boolean = true
) {
const callbackRef = useRef<null | Callback>(callback);
callbackRef.current = callback;
useEffect(() => {
if (!active) return;
if (!callback) return;
Location.getLastKnownPositionAsync().then((l) => {
if (l)
callback({
latitude: l.coords.latitude,
longitude: l.coords.longitude,
});
});
startBackgroundTaskIfNecessary();
const watch = Location.watchPositionAsync({}, (location) => {
callback({
latitude: location.coords.latitude,
longitude: location.coords.longitude,
});
});
const subscription = addLocationCallback(callback);
return () => {
subscription.remove();
watch.then((e) => {
e.remove();
});
};
}, [callback, active]);
useEffect(() => {
if (__DEV__) {
addLocationCallback((coords) => {
console.log("Location changed to ");
console.log(coords);
});
}
}, []);
}
You need to ask for background location permissions before this, BTW. Follow expos guide.
It's pretty risky trusting community libraries for stuff like this because of the fact that breaking android OS updates can happen at any moment and with open source maintainers they may or may not stay on top of it (you can more or less trust expo too, though)

Trying to Map Yelp API repsonse

I am using the Yelp API (by making requests to it using https://cors-anywhere.herokuapp.com/ as a proxy—because the Yelp API itself isn’t CORS-enabled) to attempt to create an app very similar to a Yelp search for my own practice. I am able to get the response into my browser's console. The response has an array named businesses with the businesses that match the search. I am trying to use .(map) to map through the businesses array. However, I keep getting Cannot read property 'map' of undefined.
The reponse I receive from the Yelp API is below. Thanks
Yelp API Response Image
Also if there is any other tips about javascript that come to mind when looking at my code please share as I am very early into my programming career.
const Yelp = {
getAccessToken: function() {
if(accessToken) {
return new Promise(resolve => resolve(accessToken));
};
return fetch(accessTokenUrl, {
method: 'POST'
}).then(response => response.json()).then(jsonResponse => {
accessToken = jsonResponse.access_token;
})
},
search: function(term,location,sortBy) {
return this.getAccessToken().then(() => {
const yelpRetrieveUrl = `${corsAnywhereUrl}https://api.yelp.com/v3/businesses/search?term=${term}&location=${location}&sort_by=${sortBy}`;
return fetch(yelpRetrieveUrl, {
headers: {Authorization: `Bearer ${accessToken}`}
});
}).then(jsonResponse => {
if(1 > 0){
console.log('true!!!!');
return jsonResponse.businesses.map(business => {
return {
id: business.id,
imageSrc: business.image_url,
name: business.name,
address: business.location.address1,
city: business.location.city,
state: business.location.state,
zipCode: business.location.zip_code,
category: business.categories,
rating: business.rating,
reviewCount: business.review_count
};
});
} else {
console.log('FALSE')
}
})
}
};
fetch(myRequest).then(function(response) {
var contentType = response.headers.get("content-type");
if(contentType && contentType.includes("application/json")) {
return response.json();
}
throw new TypeError("Oops, we haven't got JSON!");
})
.then(function(json) { /* process your JSON further */ })
.catch(function(error) { console.log(error); });
You might search for the jsonResponse#json function to turn your json dataset into a real object that you can process in JavaScript.
And because you asked for it: As you are using Promises, make use of the Promise#Catch function to handle upcoming errors. Don't let the browser handle them, because they can have different behaviors in different browsers.
And probably remove the 1 > 0 check, because it will always be true, but I think this was only for test purposes.
I hope I could help you! I might append the code later since I'm currently on mobile.

Ionic Cordova can not share video on social sites

I am trying to use the cordova social sharing plugin for sharing video on social sites. So far what I have achieved is, I have successfully captured video using following code -
var options = {
limit: 1,
duration: 15
};
$cordovaCapture.captureVideo(options).then(function (videoData) {
$scope.videoUrl = videoData[0].fullPath;
}, function (err) {
// An error occurred. Show a message to the user
//alert("video error : "+err);
});
I can successfully find the captured video files url but unfortunately I can not share them to the social media sites. I have tried both of the following methods -
$cordovaSocialSharing
.share(message, subject, file, link)
and
$cordovaSocialSharing
.shareViaTwitter(message, image, link)
Now my question is -
Is there any way to share video through this approach?
If not, please let me know if there is any possible way for this.
N.B. : I have already bothered the Google a lot.
Thanks in advance.
my problem was passing a bad filePath, so i found a solution like below :
import {CaptureError, MediaFile, MediaCapture, CaptureImageOptions, Transfer} from "ionic-native";`
declare let cordova: any;
private static options = {
message: '', // not supported on some apps (Facebook, Instagram)
subject: '', // for email
files: [''], // an array of filenames either locally or remotely
url: ''
};
videoOptions: CaptureImageOptions = {limit: 1};
videoData: any;
captureVideo() {
MediaCapture.captureVideo(this.videoOptions)
.then(
(data: MediaFile[]) => {
this.videoData = data[0];
const fileTransfer = new Transfer();
fileTransfer.download(this.videoData.fullPath, cordova.file.applicationStorageDirectory + 'fileDir/filename.mp4').then((entry) => {
this.options.message = " Your message";
this.options.subject = "Your Subject";
this.options.files = [entry.toURL()];
this.options.url = "https://www.google.com.tr/";
SocialSharing.shareWithOptions(this.options);
}, (error) => {
});
},
(err: CaptureError) => {
}
);
}
As you see above, i just copy my video file to applicationStorageDirectory

How to make a list of failed specs using jasmine custom reporter to post to slack?

I am trying to work on a custom jasmine reporter and get a list of all the failed specs in the specDone function:
specDone: function(result) {
if(result.status == 'failed') {
failedExpectations.push(result.fullName);
console.log(failedExpectations);
}
}
where failedExpectations will store an entire list of the failed specs and i need to access this in the afterLaunch function in the protractor config file. But due to the fact that the config file loads everytime a new spec runs it basically gets overwritten and scoping is such that I cannot access it in the afterLaunch function, that is where I am making the call to the slack api. Is there a way to achieve this?
This is what i have it based on : http://jasmine.github.io/2.1/custom_reporter.html
I think the best way is to post the results asynchronously after each spec (*or every "it" and "describe") using #slack/web-api. This way you don't have to worry about overwriting. Basically you "collect" all the results during the test run and send it before the next suite starts.
Keep in mind all of this should be done as a class.
First you prepare your you '#slack/web-api', so install it (https://www.npmjs.com/package/#slack/web-api).
npm i -D '#slack/web-api'
Then import it in your reporter:
import { WebClient } from '#slack/web-api';
And initialize it with your token. (https://slack.com/intl/en-pl/help/articles/215770388-Create-and-regenerate-API-tokens):
this.channel = yourSlackChannel;
this.slackApp = new WebClient(yourAuthToken);
Don't forget to invite your slack app to the channel.
Then prepare your result "interface" according to your needs and possibilities. For example:
this.results = {
title: '',
status: '',
color: '',
successTests: [],
fails: [],
};
Then prepare a method / function for posting your results:
postResultOnSlack = (res) => {
try {
this.slackApp.chat.postMessage({
text: `Suit name: ${res.title}`,
icon_emoji: ':clipboard:',
attachments: [
{
color: res.color,
fields: [
{
title: 'Successful tests:',
value: ` ${res.successTests}`,
short: false
},
{
title: 'Failed tests:',
value: ` ${res.fails}`,
short: false
},
]
}
],
channel: this.channel
});
console.log('Message posted!');
} catch (error) {
console.log(error);
}
When you got all of this ready it's time to "collect" your results.
So on every 'suitStart' remember to "clear" the results:
suiteStarted(result) {
this.results.title = result.fullName;
this.results.status = '';
this.results.color = '';
this.results.successTests = [];
this.results.fails = [];
}
Then collect success and failed tests:
onSpecDone(result) {
this.results.status = result.status
// here you can push result messages or whole stack or do both:
this.results.successTests.push(`${test.passedExpectations}`);
for(var i = 0; i < result.failedExpectations.length; i++) {
this.results.fails.push(test.failedExpectations[i].message);
}
// I'm not sure what is the type of status but I guess it's like this:
result.status==1 ? this.results.color = #DC143C : this.results.color = #048a04;
}
And finally send them:
suiteDone() {
this.postResultOnSlack(this.results);
}
NOTE: It is just a draft based on reporter of mine. I just wanted to show you the flow. I was looking at Jasmine custom reporter but this was based on WDIO custom reporter based on 'spec reporter'. They are all very similar but you probably have to adjust it. The main point is to collect the results during the test and send them after each part of test run.
*You can look up this explanation: https://webdriver.io/docs/customreporter.html
I highly recommend this framework, you can use it with Jasmine on top.

Categories