I have a very basic static app that has business logic to redirect a user based on what element they click in the UI. I added Mixpanel, and track an event before the user is redirected. I'm trying to create tests using the Jest testing framework, but am having difficulties mocking the track method invocation on mixpanel.
The crux of the issue is I'm unable to mock mixpanel while running tests. I've read the Jest documentation and searched the community for answers, but every time I run tests, it fails with TypeError: Cannot read property 'track' of undefined. You'll have to forgive me if it is something obvious, JavaScript is not my native programming language, so when it comes time to build tests, I'm rusty at it. :)
index.html
<!doctype html>
<html class="no-js" lang="">
<head>
<!-- start Mixpanel -->
<script type="text/javascript">...Mixpanel script</script>
<script src="js/scripts.js"></script>
<script type="text/javascript">
const { sources } = window.parseSourcesFromURL(window.location.href)
const sourceObj = window.convertToObj(sources)
mixpanel.track("Page View", sourceObj)
</script>
<!-- end Mixpanel -->
</head>
<body>
<div>
<div class="age-links">
<button onClick=redirectFunc(true)>YES</button>
<button onClick=redirectFunc(false)>NO</button>
</div>
</div>
</body>
</html>
js/scripts.js
(function() {
const convertToObj = () => ...
const getTrackingData = () => ...
const parseSourcesFromURL = () => ...
const redirectFunc = (p) => {
const { sources, link1, link2 } = parseSourcesFromURL(window.location.href)
const redirect = `${p ? link1 : link2}?${sources.join('&')}`
const mixpanelTrackingData = getTrackingData(sources, p, redirect)
mixpanel.track('Button Clicked', mixpanelTrackingData, () => {
window.location.href = redirect;
})
}
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined')
module.exports = {
....
};
else
window.utmSourcesToObject = utmSourcesToObject;
window.parseReferrerForLinksAndUTM = parseReferrerForLinksAndUTM;
window.redirectBasedOnAge = redirectBasedOnAge;
})();
js/scripts.test.js
const { redirectFunc, parseSourcesFromURL } = require('./js/scripts')
const testURL = 'https://test.com/'
describe('parseReferrerForLinksAndUTM', () => {
beforeEach(() => {
global.window = Object.create(window)
Object.defineProperty(window, 'location', {
value: { href: testURL },
writable: true
})
### THIS DOESNT WORK ###
Object.defineProperty(window, 'mixpanel', {
track: jest.fn()
})
})
const { sources, link1, link2 } = parseSourcesFromURL(testURL)
test('redirect link is correct', () => {
### THIS DOESNT WORK ###
global.mixpanel = Object.create({})
global.mixpanel.track = jest.fn()
expect(link1).toBe('https://link1.com')
})
})
Related
I have a custom form that makes a few requests to a database to verify the user. I noticed that if I have a single google account it works fine but it doesn't with multiple. The other thing I noticed is that the script doesn't throw any error it just doesn't communicate back the result from the custom form.
This is how my custom forms look like:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<div class="container select-client">
<div class="client">Client</div>
<select class="client-select">
<option>Select Client</option>
<!-- ...options -->
</select>
<div class="market">Market</div>
<select class="market-select">
<option>Select Market</option>
<!-- ...options -->
</select>
<div class="error-message"></div>
<button class="button" id="select-button" onclick="handleSelect()">Select</button>
</div>
<script>
// ...code to validate the user
function handleSelect() {
var _client = clients.find(
(client) => client.id === parseInt(selectedClient)
);
var _market = markets.find(
(market) => market.id === parseInt(selectedMarkets)
);
if (!_client && !_market) {
return;
}
if (!_client) {
errorMessageClientMarket.innerHTML = 'Please select client';
return;
}
if (!_market) {
errorMessageClientMarket.innerHTML = 'Please select market';
return;
}
google.script.run
.withSuccessHandler()
.loginData({ token, market: _market, client: _client, user: userInfo, platform });
google.script.host.close();
}
</script>
</body>
</html>
This is how I create the custom form using app script
const loginForm = () => {
const html = HtmlService.createHtmlOutputFromFile('loginFormHtml')
.setSandboxMode(HtmlService.SandboxMode.IFRAME)
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
.setWidth(600)
.setHeight(600);
const ui = SpreadsheetApp.getUi();
ui.showModalDialog(html, `Login`);
};
This is the callback function:
const loginData = (data) => { // <--- this function is ignored when a the users has multiple google accounts
console.log('LOGIN FORM');
const { token, market, user, client, platform } = data;
UserProperties.setProperty('token', token);
UserProperties.setProperty('userId', user.id);
UserProperties.setProperty('clientId', client.id);
UserProperties.setProperty('clientName', client.name);
UserProperties.setProperty('marketId', market.id);
UserProperties.setProperty('marketName', market.code_name);
UserProperties.setProperty('username', `${user.first_name} ${user.last_name}`);
UserProperties.setProperty('userEmailAddress', user.email);
UserProperties.setProperty('platform', platform);
const info = UserProperties.getProperties();
console.log('info ---> ', info)
const ui = SpreadsheetApp.getUi();
getMenu(true);
ui.alert('Logged in Successfully');
};
Does anyone know if there's a away to fix this?
I'm trying to send a message when the 'q' key is pressed from my index.js file to the script on index.html, but I don't really know why It's not working properly.
Here is my js file
const url = require('url');
const path = require('path');
const {app, BrowserWindow, globalShortcut, ipcMain, webContents} = require('electron');
let mainWindow;
app.on('ready', function(){
// Create new window
mainWindow = new BrowserWindow({
backgroundColor: '#000000',
fullscreen : true,
frame : false,
icon : __dirname + "/res/icon.jpg",
webPreferences: {
nodeIntegration : true
}
});
// Load html in window
mainWindow.loadURL(url.format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file:',
slashes:true
}))
globalShortcut.register('Esc', () => {
app.quit();
});
globalShortcut.register('q', () => {
leftLight();
});
});
function leftLight() {
mainWindow && mainWindow.webContents.send('key-pressed-q');
console.log("Sending q pressed to html...");
}
And the html
<!DOCTYPE html>
<html lang="en">
<meta name="viewport" content="width=device-width, initial-scale=1">
<head>
<link rel="stylesheet" href="styles.css">
<title>Document</title>
</head>
<body>
<div class = rect_green> <h2 class=blocktext >LEFT FENCER</h2></div>
<div class = rect_red><h2 class=blocktext> RIGHT FENCER</h2> </div>
<div class = crono> <h2 class=crontext>3:00</h2></div>
</body>
<script type="text/javascript">
var ipc = require('electron').ipcRenderer;
ipc.on('key-pressed-q', (e) => {
//var element = document.getElementsByClassName("rect_green");
//element.style["background-color"] = "yellow";
console.log("q pressed in html file");
});
</script>
</html>
The key pressed is detected, but the message is not received by the ipcRenderer. Any mistakes on my code?
It seems like you've got the syntax of .webContents.send( wrong.
You need to provide a channel for your message, E.g. .webContents.send('channel', 'msg'), then you listen for that channel in your page.
A channel can be whatever you want. For example:
Electron js file:
mainWindow.webContents.send('channelNameCanBeAnything', 'key-pressed-q');
Html file:
ipc.on('channelNameCanBeAnything', (msgStr) => {
console.log("q pressed in html file");
console.log(msgStr); // Will output 'key-pressed-q'.
});
See the docs
I have the following JS that I'm trying to translate to Vue JS. I was told a computed property would be the way to go. I'm a little confused on computed properties still. I know how I want it to function, I want it's input to be the key of the item in the list. And I want the output to be a 'related' array that I can then loop through in sub list. If you are having trouble visualizing, please see this CodePen Any help would be awesome.
// Initialize Firebase
const db = firebase
.initializeApp({
databaseURL: "...firebaseio.com"
})
.database();
const contentRef = db.ref("a");
const relatedRef = db.ref("test");
new Vue({
el: "#app",
data: {
related: {}
},
firebase: {
content: contentRef,
related: relatedRef
},
});
var theKey = "objectb"
function getDiscoverBarContent(key, cb) {
relatedRef.child(key).on("child_added", snap => {
let relateRef = contentRef.child(snap.key);
relateRef.once("value", cb);
});
}
getDiscoverBarContent(theKey, snap => {
var snapVal = snap.val();
console.log(snapVal)
});
HTML
<div id="app">
<div>
<h2>Content</h2>
<pre><code>{{content}}</code></pre>
</div>
<div>
<h2>'Related' Content</h2>
<pre><code>{{related}}</code></pre>
</div>
<div>
<h2>'Results'</h2>
<ul>
<li v-for="thing in content">{{thing.name}}
<ul>
<li v-for="relation in related">{{relation.name}}</li>
</ul>
</li>
</ul>
</div>
</div>
EDIT: My attempt: CodePen
computed: {
relatedThings: function () {
var theKey = "objectb"
var output = []
function getDiscoverBarContent(key, cb) {
relatedRef.child(key).on("child_added", snap => {
let relateRef = contentRef.child(snap.key);
relateRef.once("value", cb);
});
}
getDiscoverBarContent(theKey, snap => {
var snapVal = snap.val();
console.log(snapVal)
var output = snapVal
});
return snapVal;
}
}
And Error:
ReferenceError: snapVal is not defined.
Please note, this is my attempt and I am not certain that it is going to work.
I should add...my JS skills are lacking. I know why this issue is happening but not sure how to fix.
I am learning VueJS 2.0 and I am connecting to an API where I want the value of some data to change on input change. Here is what the output says using the dev tools:
canadianDollar:undefined
europeanEuro:undefined
europeanPound:undefined
usd:"1232"
Whenever I put 1232 in the USD input field it doesn't return anything and leaves those properties as undefined. Here is the code.
new Vue({
el: '#app',
data: {
usd: '',
canadianDollar: '',
europeanPound: '',
europeanEuro: ''
},
// Watch methods
watch: {
usd: function() {
this.convertCurrency()
}
},
// Logic Methods
methods: {
convertCurrency: _.debounce(function() {
var app = this;
if (app.usd.length !== 0) {
// Show that the things are loading in.
app.canadianDollar = 'Searching...'
app.europeanPound = 'Searching...'
app.europeanEuro = 'Searching...'
console.log(app.usd)
axios.get("http://api.fixer.io/latest?base=USD&" + app.usd)
.then(function(response) {
app.canadianDollar = response.data.CAD
app.europeanPound = response.data.GBP
app.europeanEuro = response.data.EUR
})
.catch(function(error){
app.canadianDollar = "ERROR"
app.europeanPound = "ERROR"
app.europeanEuro = "ERROR"
})
}
}, 500)
}
})
and the HTML:
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Vue</title>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<input type="text" name="" value="" v-model="usd">
<ul>
<li>Canadian Dollar: {{canadianDollar}}</li>
<li>European Pound: {{europeanPound}}</li>
<li>European Euro: {{europeanEuro}}</li>
</ul>
</div>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/lodash/4.17.4/lodash.min.js" charset="utf-8"></script>
<script src="index.js" charset="utf-8"></script>
</body>
</html>
When I type in a number it does give me the "Searching" part but disappears and nothing shows up.
I would recommend changing
then(function(response) {
app.canadianDollar = response.data.CAD
app.europeanPound = response.data.GBP
app.europeanEuro = response.data.EUR
})
to
then(function(response) {
console.log(response);
})
that was you can see what is being returned.
Also, axios.get("http://api.fixer.io/latest?base=USD&" + app.usd) should probably have a name like vulue:axios.get("http://api.fixer.io/latest?base=USD&VALUE=" + app.usd), but you'll have to check their api to see what it is meant to be called.
...
response.data.rates.CAD;
you have
response.data.CAD;
...
app.canadianDollar = response.data.rates.CAD * app.usd;
I am relatively new to Vue, so forgive me if this is obvious (or obviously impossible).
I have a set of JSON data (fetched from a RESTful API via vue-resource):
{content: "This is content. <a href='/blog'> Link to blog </a>"}
Right now, the link triggers a page reload. If it were a vue-router v-link, that would not be an issue. However, this doesn't work (quotes are escaped in the data, of course):
{content: "This is content. <a v-link="{ path: '/blog' }"> Link to blog </a>"}
At this point, the template is already parsed, and Vue won't create a v-link anymore (it will just show up as a v-link in the rendered html).
My final result would ideally mean that I could include links in my CMS, either in HTML or Vue format, and have Vue route them correctly as v-links.
Is there something I can do to make Vue interpret the link in the JSON data?
I've answered the question on Vue Chat, and writing it here in case any other people facing similar problem
Simplified example on Codepen
HTML
<div id="app">
<div>
<a v-link= "{path:'/home'}">Go to home</a>
</div>
<router-view></router-view>
</div>
<template id="home">
<div>
<div>
Fetched Content:
</div>
<div>
{{{ fetchedContent }}}
</div>
</div>
</template>
<template id="route1">
<div>
Route1 view
</div>
</template>
<template id="route2">
<div>
Route2 view, this is different from Route1
</div>
</template>
javascript
function getContent (callback) {
var content = 'Click this: Go to route1 and Go to route2'
setTimeout(function () { callback(content) }, 1000)
}
var Home = Vue.component('home',{
template:'#home',
data: function () {
return {
fetchedContent: 'Loading...'
};
},
ready: function () {
var self = this
var router = this.$router
getContent( function (result) {
self.fetchedContent = result;
Vue.nextTick(function () {
var hyperLinks = self.$el.getElementsByTagName('a')
Array.prototype.forEach.call(hyperLinks, function (a) {
a.onclick = function (e) {
e.preventDefault()
router.go({ path: a.getAttribute("href") })
}
})
})
})
}
});
var Route1 = Vue.component('route1', {
template: '#route1'
});
var Route2 = Vue.component('route2', {
template: "#route2"
});
var router = new VueRouter({
hashbang:false,
history:true
});
router.map({
'/home':{
component:Home
},
'/route1':{
component:Route1
},
'/route2':{
component:Route2
}
});
router.start({
}, '#app');
I had a similar solution here: question using a custom dataset in my JSON code and a click listener to process it:
mounted() {
window.addEventListener('click', event => {
let target = event.target
if (target && target.href && target.dataset.url) {
event.preventDefault();
console.log(target.dataset.url);
const url = JSON.parse(target.dataset.url);
console.log(url.name)
this.$router.push(url.name);
}
});
},