Importing Stripe.js as an ES Module into Vue - javascript

I'm trying follow the directions from the stripe elements docs and install the ES module into my Vue payment component.
Note, currently the Stripe websites ES module installation tab is down. Here's a substitute.
I ran:
npm install #stripe/stripe-js
Usage
import {loadStripe} from '#stripe/stripe-js';
const stripe = await loadStripe('pk_test_TYooMQauvdEDq54NiTphI7jx');
When I change my code to reflect the installation of the module I get this error:
30:17 error Parsing error: Can not use keyword 'await' outside an async function
import {loadStripe} from '#stripe/stripe-js';
let stripe = await loadStripe(`pk_test_mypin`)
elements = stripe.elements()
card = undefined;
export default {
mounted: function () {
card = elements.create('card', {
});
card.mount(this.$refs.card);
},
data () {
return {
cardHolderName: '',
stripeErrorMessage: null,
serverErrorMessage: null,
}
},
computed: {
},
methods: {
processPayment(){
let self = this;
stripe.createPaymentMethod(
'card', card, {
billing_details: { name: this.cardHolderName }
}).then(function(result) {
if(self.subscribitionCheckout){
self.submitPaymentForm(result.paymentMethod);
} else if (self.changePaymentMethod){
self.changePaymentMethod(result.paymentMethod)
}
if (result.error) {
self.stripeErrorMessage = result.error.message;
self.hasCardErrors = true;
self.$forceUpdate(); // Forcing the DOM to update so the Stripe Element can update.
return;
}
});
},
},
}
Before I had this
let stripe = Stripe(`pk_test_mypin`),
elements = stripe.elements(),
card = undefined;
Also, I based my code on this tutorial

First, put the expected top level vars in data:
stripe: {}, // or whatever data type
elements: {}, // or whatever data type
card: {}, // or whatever data type
Second, make a created lifecycle hook and load the content there:
created()
{
loadStripe(`pk_test_TYooMQauvdEDq54NiTphI7jx`).
then ( (result) =>
{
this.elements = result.elements
// do stuff with card if you have too...
},
},

Related

How to store nuxtjs dynamically generated routes in vuex store

I'm trying to leverage nuxtjs SSG capabilities by creating a static web site where the pages content and navigation are fetched from an API.
I already found my way around on how to dynamically generate the routes by defining a module where I use the generate:before hook to fetch the pages content and routes. When creating the routes I store the page content as the route payload. The following code does just that and works as intended.
modules/dynamicRoutesGenerator.js
const generator = function () {
//Before hook to generate our custom routes
this.nuxt.hook('generate:before', async (generator, generatorOptions) => {
generator.generateRoutes(await generateDynamicRoutes())
})
}
let generateDynamicRoutes = async function() {
//...
return routes
}
export default generator
Now the problem I'm facing is that I have some navigation components that need the generated routes and I was thinking to store them into the vuex store.
I tried the generate:done hook but I don't know how to get the vuex store context from there. What I ended up using was the nuxtServerInit() action because as stated in the docs:
If nuxt generate is ran, nuxtServerInit will be executed for every dynamic route generated.
This is exactly what I need so I'm trying to use it with the following code:
store/index.js
export const actions = {
nuxtServerInit (context, nuxtContext) {
context.commit("dynamicRoutes/addRoute", nuxtContext)
}
}
store/dynamicRoutes.js
export const state = () => ({
navMenuNivel0: {}
})
export const mutations = {
addRoute (state, { ssrContext }) {
//Ignore static generated routes
if (!ssrContext.payload || !ssrContext.payload.entrada) return
//If we match this condition then it's a nivel0 route
if (!ssrContext.payload.navMenuNivel0) {
console.log(JSON.stringify(state.navMenuNivel0, null, 2));
//Store nivel0 route, we could use url only but only _id is guaranteed to be unique
state.navMenuNivel0[ssrContext.payload._id] = {
url: ssrContext.url,
entrada: ssrContext.payload.entrada,
navMenuNivel1: []
}
console.log(JSON.stringify(state.navMenuNivel0, null, 2));
//Nivel1 route
} else {
//...
}
}
}
export const getters = {
navMenuNivel0: state => state.navMenuNivel0
}
The action is indeed called and I get all the expected values, however it seems like that with each call of nuxtServerInit() the store state gets reset. I printed the values in the console (because I'm not sure even if it's possible to debug this) and this is what they look like:
{}
{
"5fc2f4f15a691a0fe8d6d7e5": {
"url": "/A",
"entrada": "A",
"navMenuNivel1": []
}
}
{}
{
"5fc2f5115a691a0fe8d6d7e6": {
"url": "/B",
"entrada": "B",
"navMenuNivel1": []
}
}
I have searched all that I could on this subject and altough I didn't find an example similar to mine, I put all the pieces I could together and this was what I came up with.
My idea was to make only one request to the API (during build time), store everything in vuex then use that data in the components and pages.
Either there is a way of doing it better or I don't fully grasp the nuxtServerInit() action. I'm stuck and don't know how to solve this problem and can't see another solution.
If you made it this far thanks for your time!
I came up a with solution but I don't find it very elegant.
The idea is to store the the API requests data in a static file. Then create a plugin to have a $staticAPI object that expose the API data and some functions.
I used the build:before hook because it runs before generate:before and builder:extendPlugins which means that by the time the route generation or plugin creation happen, we already have the API data stored.
dynamicRoutesGenerator.js
const generator = function () {
//Add hook before build to create our static API files
this.nuxt.hook('build:before', async (plugins) => {
//Fetch the routes and pages from API
let navMenuRoutes = await APIService.fetchQuery(QueryService.navMenuRoutesQuery())
let pages = await APIService.fetchQuery(QueryService.paginasQuery())
//Cache the queries results into staticAPI file
APIService.saveStaticAPIData("navMenuRoutes", navMenuRoutes)
APIService.saveStaticAPIData("pages", pages)
})
//Before hook to generate our custom routes
this.nuxt.hook('generate:before', async (generator, generatorOptions) => {
console.log('generate:before')
generator.generateRoutes(await generateDynamicRoutes())
})
}
//Here I can't find a way to access via $staticAPI
let generateDynamicRoutes = async function() {
let navMenuRoutes = APIService.getStaticAPIData("navMenuRoutes")
//...
}
The plugin staticAPI.js:
import APIService from '../services/APIService'
let fetchPage = function(fetchUrl) {
return this.pages.find(p => { return p.url === fetchUrl})
}
export default async (context, inject) => {
//Get routes and files from the files
let navMenuRoutes = APIService.getStaticAPIData("navMenuRoutes")
let pages = APIService.getStaticAPIData("pages")
//Put the objects and functions in the $staticAPI property
inject ('staticAPI', { navMenuRoutes, pages, fetchPage })
}
The APIService helper to save/load data to the file:
//...
let fs = require('fs');
let saveStaticAPIData = function (fileName = 'test', fileContent = '{}') {
fs.writeFileSync("./static-api-data/" + fileName + ".json", JSON.stringify(fileContent, null, 2));
}
let getStaticAPIData = function (fileName = '{}') {
let staticData = {};
try {
staticData = require("../static-api-data/" + fileName + ".json");
} catch (ex) {}
return staticData;
}
module.exports = { fetchQuery, apiUrl, saveStaticAPIData, getStaticAPIData }
nuxt.config.js
build: {
//Enable 'fs' module
extend (config, { isDev, isClient }) {
config.node = { fs: 'empty' }
}
},
plugins: [
{ src: '~/plugins/staticAPI.js', mode: 'server' }
],
buildModules: [
'#nuxtjs/style-resources',
'#/modules/staticAPIGenerator',
'#/modules/dynamicRoutesGenerator'
]

How to use an async function as the second parameter to jest.mock?

I need to use jest.mock together with async, await and import() so that I can use a function from another file inside the mocked module. Otherwise I must copy and paste a few hundreds of slocs or over 1000 slocs, or probably it is not even possible.
An example
This does work well:
jest.mock('./myLin.jsx', () => {
return {
abc: 967,
}
});
Everywhere I use abc later it has 967 as its value, which is different than the original one.
This does not work:
jest.mock('./myLin.jsx', async () => {
return {
abc: 967,
}
});
abc seems to not be available.
Actual issue
I need async to be able to do this:
jest.mock('~/config', async () => {
const { blockTagDeserializer } = await import(
'../editor/deserialize' // or 'volto-slate/editor/deserialize'
);
// … here return an object which contains a call to
// blockTagDeserializer declared above; if I can't do this
// I cannot use blockTagDeserializer since it is outside of
// the scope of this function
}
Actual results
I get errors like:
TypeError: Cannot destructure property 'slate' of '((cov_1viq84mfum.s[13]++) , _config.settings)' as it is undefined.
where _config, I think, is the ~/config module object and slate is a property that should be available on _config.settings.
Expected results
No error, blockTagDeserializer works in the mocked module and the unit test is passed.
The unit test code
The code below is a newer not-working code based on this file on GitHub.
import React from 'react';
import renderer from 'react-test-renderer';
import WysiwygWidget from './WysiwygWidget';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
const mockStore = configureStore();
global.__SERVER__ = true; // eslint-disable-line no-underscore-dangle
global.__CLIENT__ = false; // eslint-disable-line no-underscore-dangle
jest.mock('~/config', async () => {
const { blockTagDeserializer } = await import(
'../editor/deserialize' // or 'volto-slate/editor/deserialize'
);
const createEmptyParagraph = () => {
return {
type: 'p',
children: [{ text: '' }],
};
};
return {
settings: {
supportedLanguages: [],
slate: {
elements: {
default: ({ attributes, children }) => (
<p {...attributes}>{children}</p>
),
strong: ({ children }) => {
return <strong>{children}</strong>;
},
},
leafs: {},
defaultBlockType: 'p',
textblockExtensions: [],
extensions: [
(editor) => {
editor.htmlTagsToSlate = {
STRONG: blockTagDeserializer('strong'),
};
return editor;
},
],
defaultValue: () => {
return [createEmptyParagraph()];
},
},
},
};
});
window.getSelection = () => ({});
test('renders a WysiwygWidget component', () => {
const store = mockStore({
intl: {
locale: 'en',
messages: {},
},
});
const component = renderer.create(
<Provider store={store}>
<WysiwygWidget
id="qwertyu"
title="My Widget"
description="My little description."
required={true}
value={{ data: 'abc <strong>def</strong>' }}
onChange={(id, data) => {
// console.log('changed', data.data);
// setHtml(data.data);
}}
/>
</Provider>,
);
const json = component.toJSON();
expect(json).toMatchSnapshot();
});
What I've tried
The code snippets above show partially what I have tried.
I searched the web for 'jest mock async await import' and did not found something relevant.
The question
If jest.mock is not made to work with async, what else can I do to make my unit test work?
Update 1
In the last snippet of code above, the line
STRONG: blockTagDeserializer('strong'),
uses blockTagDeserializer defined here which uses deserializeChildren, createEmptyParagraph (which is imported from another module), normalizeBlockNodes (which is imported from another module) and jsx (which is imported from another module) functions, which use deserialize which uses isWhitespace which is imported from another module and typeDeserialize which uses jsx and deserializeChildren.
Without using await import(...) syntax how can I fully mock the module so that my unit test works?
If you want to dig into our code, please note that the volto-slate/ prefix in the import statements is for the src/ folder in the repo.
Thank you.
I'd advise not doing any "heavy" stuff (whatever that means) in a callback of jest.mock, – it is designed only for mocking values.
Given your specific example, I'd just put whatever the output of blockTagDeserializer('strong') right inside the config:
jest.mock('~/config', () => {
// ...
extensions: [
(editor) => {
editor.htmlTagsToSlate = {
STRONG: ({ children }) => <strong>{children}</strong>, // or whatever this function actually returns for 'strong'
};
return editor;
},
],
// ...
});
This doesn't require anything asynchronous to be done.
If you need this setup to be present in a lot of files, extracting it in a setup file seems to be the next best thing.
I found a solution. I have a ref callback that sets the htmlTagsToSlate property of the editor in the actual code of the module, conditioned by global.__JEST__ which is defined as true in Jest command line usage:
import { htmlTagsToSlate } from 'volto-slate/editor/config';
[...]
testingEditorRef={(val) => {
ref.current = val;
if (val && global.__JEST__) {
val.htmlTagsToSlate = { ...htmlTagsToSlate };
}
}}
Now the jest.mock call for ~/config is simple, there is no need to do an import in it.
I also use this function:
const handleEditorRef = (editor, ref) => {
if (typeof ref === 'function') {
ref(editor);
} else if (typeof ref === 'object') {
ref.current = editor;
}
return editor;
};

Javascript - Cannot use import statement outside a module

So I have major problem with importing from one class to another and what I have done is in my "main" class which I call
detailsPage.js
import { DetailsPage } from '../tests/detailsPageObj';
const utils = require("../utils/utils");
const assert = require('../node_modules/chai').assert;
const userData = require('../globalContent.json');
describe('Details page', function () {
const detailsPage = new DetailsPage();
// The details page is accessible by the specified URL
it(`Is defined by the URL: ${userData.url}`, async function () {
await detailsPage.navigate();
});
// Details page has a form and it can be filled out with user data
it('Has a form that can receive user data', async function () {
await detailsPage.fillFormWithUserData(); // If you want, make the user data passable to the method
await utils.click(detailsPage.form.buttons.nextStep);
});
if (detailsPage.hasStockConflict) {
// Details page allows the user to fix conflicts in stocks
it('Enables resolution of stock conflicts', async function () {
// Wait for stock to fully load
await browser.sleep(2000);
await detailsPage.clickAllRemoveButtons();
await detailsPage.clickAllDecreaseButtons();
});
}
// Details page allows the user to proceed to the next stage when all conflicts (if any) has been resolved
it('Allows the user to proceed to the next stage of purchasing', async function () {
const nextStepButton = detailsPage.form.buttons.nextStep;
await utils.elementToBeClickable(nextStepButton);
await utils.click(nextStepButton);
});
});
and what I am trying tod o is to get DetailsPage from another script which is called:
detailsPageObj
import { element, by } from 'protractor';
const utils = require("../utils/utils");
const userData = require('../globalContent.json');
export class DetailsPage {
get pageUtils() {
return {
qtyRegex: /^Sorry.*?(\d+)/
}
}
private get fields() {
return {
email: element(by.id('email')),
firstName: element(by.id('firstName')),
lastName: element(by.id('lastName')),
postalCode: element(by.id('postalCode')),
addressOne: element(by.id('addressOne')),
addressTwo: element(by.id('addressTwo')),
phone: element(by.id('phone')),
businessCustomerCB: element(by.id('isBusinessCustomer')),
company: element(by.id('company')),
GST: element(by.id('gst')),
}
}
private get groups() {
return {
address: element(by.css('div#addressGroup.input-container.showHiddenGroup'));
company: element(by.id('companyGroup')),
}
}
private get modals() {
return {
contactModalLink: element(by.id('contactModalLink')),
cross: element(by.className('modal-cross')),
}
}
private get formButtons() {
return {
nextStep: element(by.id('submitIdentityFormButton')),
mobile: this.mobileFormButtons
}
}
private get mobileFormButtons() {
return {
continue: element(by.id('stock-conflict-continue-button')),
removeOutOfStockItems: element(by.css('button[id="removeOutOfStockItems"]')), // I just assumed that this is a part of the form
}
}
private get productFrameMobileButtons() {
return {
stockControll: element.all(by.className('stock-controller mobile')),
remove: element.all(by.className('btn btn-remove btn-outlined mobile')),
}
}
private get productFrameDesktopButtons() {
return {
stockControll: element.all(by.className('stock-controller desktop')),
remove: element.all(by.className('btn btn-remove btn-outlined desktop')),
}
}
get form() {
return {
fields: this.fields,
groups: this.groups,
buttons: this.formButtons,
modals: this.modals
}
}
get productFrame() {
return {
buttons: {
decrease: element.all(by.className("btn left")).first(),
mobile: this.productFrameMobileButtons,
desktop: this.productFrameDesktopButtons
}
}
}
get errors() {
return {
stockConflict: element(by.className('generic-error-heading')),
}
}
}
and what I am trying to do is in detailsPage.js im trying to import detailsPageObj.js but whenever I am trying to do it I do get SyntaxError: Cannot use import statement outside a module.
What am I doing wrong
I don't know what is your environment like, but I experienced a similar problem where my environment used a full build step for creating the target JS code from my sources (e.g. from TypeScript or from ES6+) to a bundled/plain JS.
But then my test environment did not have any build step. So when I executed the tests, it only understood plain JS, which by default in a node.js environment does not recognize import but only require.
You can use import in your node.js code without a build step, but you need to follow some steps, e.g. rename your file from *.js to *.mjs. More details here.

JavaScript warning: Assignment to property of function parameter

I compiled vue-flash-message component from sources and got the following warning:
✘ http://eslint.org/docs/rules/no-param-reassign Assignment to property of function parameter 'Vue'
src\components\vue-flash-message\index.js:173:5
Vue.prototype[options.storage] = FlashBus;
in the following code:
export default {
install(Vue, config = {}) {
const defaults = {
method: 'flash',
storage: '$flashStorage',
createShortcuts: true,
name: 'flash-message',
};
const options = Object.assign(defaults, config);
...
const FlashBus = new Vue({
data() {
return {
storage: {
},
};
},
methods: {
flash(msg, type, opts) {
return new FlashMessage(FlashBus, msg, type, opts);
},
push(id, message) {
Vue.set(this.storage, id, message);
},
destroy(id) {
Vue.delete(this.storage, id);
},
destroyAll() {
Vue.set(this, 'storage', {});
},
},
});
...
Vue.prototype[options.storage] = FlashBus;
...
},
};
is it possible to correct the code and make it compile without warnings?
This is not an issue.
You have an ES Lint rule setup for no-param-reassign. This conflicts with Vue's way of creating plugins, where you are directed to write to the prototype directly. You can see my statement reinforced here
Your only choice is to fork that project, and ignore the line with your linter if it's bugging you that much.

How to fake a user in unit tests in Meteor using practicalmeteor:mocha?

I'm having a difficult time trying to test a methods in meteor that requires a connected user. Basically I need to test if a user of the app can add an article to it's cart. The methods will tests if a user is connected and, in order to test that will use Meteor.userId(). This seems to be a problem in unit testing as I get the error:
"Meteor.userId can only be invoked in method calls or publications."
So far, I tried to do what's proposed in this post: How to unit test a meteor method with practicalmeteor:mocha but I don't understand what the solution is doing.
Here is my testing method:
import { Meteor } from 'meteor/meteor';
import { Random } from 'meteor/random';
import { assert } from 'meteor/practicalmeteor:chai';
import { sinon } from 'meteor/practicalmeteor:sinon';
import { Carts } from '/imports/api/carts/carts.js';
import { Articles } from '/imports/api/articles/articles.js';
import '/imports/api/carts/carts.methods.js';
import { SecurityEnsurer } from '/lib/security/security.js';
function randomInt (low, high) {
return Math.floor(Math.random() * (high - low) + low);
}
if (Meteor.isServer) {
describe('Carts', () => {
describe('methods', () => {
let currentUser;
beforeEach(() => {
Factory.define('user', Meteor.users, {
name: "userTest",
currentUser: {
email: 'user#shop.info',
password: '123456',
}
});
currentUser = Factory.create('user');
sinon.stub(Meteor, 'user');
Meteor.user.returns(currentUser);
Articles.remove({});
articleId = Articles.insert({
name : "azerty",
description : "descTest",
modelNumber : "wxcvbn",
categoryName : "CatTest",
price : 1,
advisedPrice: 2,
supplierDiscount : 0,
brandId : "BrandTest",
isAvailable: true,
restockingTime: 42,
color: "Yellow",
technicals: [
{
name : "GPU",
value : "Intel"
},
],
});
Carts.insert({
owner: currentUser,
entries: [],
});
});
afterEach(() => {
Meteor.user.restore();
Articles.remove({});
Carts.remove({});
});
it('can add article', () => {
let quantity = randomInt(1,50);
const addArticleToCart = Meteor.server.method_handlers['carts.addArticle'];
const invocation = {};
addArticleToCart.apply(invocation, [articleId, quantity]);
assert.equal(Cart.find({owner: currentUser, entries: {$elemMatch: {articleId, quantity}}}).count(), 1);
});
});
});
}
If anyone can help me find out how to create my test, this would realy help me.
To fake a user when calling a Meteor Method, the only way I found is to use the mdg:validated-method package which provide a framework around Meteor methods. This framework seems to be the standard now (see the Meteor guide), but it requires to re-write your methods and the in-app calls.
After describing the methods using this framework, you are able to call them with the userId parameter when testing, using this kind of code (which verifies that my method is returning a 403 error):
assert.throws(function () {
updateData._execute({userId: myExternalUserId}, {
id: dataId,
data: {name: "test"}
});
}, Meteor.Error, /403/);
FYI, here are the packages I add when I do automated testing (Meteor 1.6 used):
meteortesting:mocha
dburles:factory
practicalmeteor:chai
johanbrook:publication-collector
Here's how I set up a fake logged in user for testing publish and methods:
1) create a user
2) stub i.e. replace the Meteor.user() and Meteor.userId() functions which return the current logged in user in methods
3) provide that user's _id to PublicationsCollector, which will send it in to your publish function.
Here's how I did it, I hope you can adapt from this:
import { Meteor } from 'meteor/meteor';
import { Factory } from 'meteor/dburles:factory';
import { PublicationCollector } from 'meteor/johanbrook:publication-collector';
import { resetDatabase } from 'meteor/xolvio:cleaner';
import faker from 'faker';
import { Random } from 'meteor/random';
import { chai, assert } from 'meteor/practicalmeteor:chai';
import sinon from 'sinon';
// and also import your publish and collection
Factory.define('user', Meteor.users, {
'name': 'Josephine',
});
if (Meteor.isServer) {
describe('Menus', () => {
beforeEach(function () {
resetDatabase();
const currentUser = Factory.create('user');
sinon.stub(Meteor, 'user');
Meteor.user.returns(currentUser); // now Meteor.user() will return the user we just created
sinon.stub(Meteor, 'userId');
Meteor.userId.returns(currentUser._id); // needed in methods
// and create a Menu object in the Menus collection
});
afterEach(() => {
Meteor.user.restore();
resetDatabase();
});
describe('publish', () => {
it('can view menus', (done) => {
const collector = new PublicationCollector({ 'userId': Meteor.user()._id }); // give publish a value for this.userId
collector.collect(
'menus',
(collections) => {
assert.equal(collections.menus.length, 1);
done();
},
);
});
});
});
}
You can also write a test for calling a Meteor method that relies on Meteor.userId():
expect(() => { Meteor.call('myMethod'); }).to.not.throw(Meteor.Error);

Categories

Resources