So I'm creating a custom backend (build on top of the http module) for fun. But I stumbled upon a problem with getting/parsing the body. I have solved it, but it still isn't what I am looking for. This is what I have now:
this.body = new Promise((resolve, reject) => {
let _d;
req.on('data', (chunk) => {
_d += chunk;
});
req.on('end', () => {
if(_d === undefined) return resolve(null);
resolve(JSON.parse(_d.replace('undefined', '')));
});
req.on('error', (err) => {
reject(err);
});
});
I have tried this without using promises but then the body will be undefined. Every time I want to access body I have to do this:
let body = req.body.then(value => {return value});
Is there a better way to do this.
Thank you for your time.
request.js:
const {IncomingMessage} = require('http');
const url = require('url');
class Request {
/**
* #param {IncomingMessage} req
*/
constructor(req) {
/**
* #type {IncomingMessage}
*/
this.http = req;
/**
* #type {Promise<{}>}
* #returns {{}}
*/
this.body = new Promise((resolve, reject) => {
let _d;
req.on('data', (chunk) => {
_d += chunk;
});
req.on('end', () => {
if(_d === undefined) return resolve(null);
resolve(JSON.parse(_d.replace('undefined', '')));
});
req.on('error', (err) => {
reject(err);
});
});
}
/**
* Get query
* #returns {import('querystring').ParsedUrlQuery}
*/
get query() {
return url.parse(this.http.url, true).query;
}
}
/**
* Get the custom request
* #param {IncomingMessage} req
* #returns {Request}
*/
function getReq(req) {
return new Request(req);
}
module.exports = {
Request,
getReq
}
backend.js:
const {Path} = require('./routes');
const {getReq, Request} = require('./request');
const {getRes, Response} = require('./response');
const {Server} = require('http');
class App {
constructor() {
this.http = require('http');
/**
* #type {Array<Path>}
* #private
*/
this.urls = []
/**
* #type {Server}
* #private
*/
this.server = new this.http.createServer(async (req, res) => {
for(var i = 0; i < this.urls.length; i++) {
/**
* #type {Path}
*/
let path = this.urls[i];
if((path.url === req.url && path.method === req.method) || (path.url === "*" && path.method === req.method)) {
path.callback(getReq(req), getRes(res));
return;
}
if(path.method === 'ALL' && (path.url === req.url || path.url === "*")) {
path.callback(getReq(req), getRes(res));
return;
}
}
});
}
/**
* #callback Callback
* #param {Request} req
* #param {Response} res
*/
/**
* Listen for GET requests
* #param {String} url
* #param {Callback} callback
*/
get(url, callback) {
this.urls.push(new Path('GET', url, callback));
}
/**
* Listen for POST requests
* #param {String} url
* #param {Callback} callback
*/
post(url, callback) {
this.urls.push(new Path('POST', url, callback));
}
/**
* Listen for all requests
* #param {String} url
* #param {Callback} callback
*/
all(url, callback) {
this.urls.push(new Path('ALL', url, callback));
}
/**
* Start the server
* #param {Number} port
* #param {Function} callback
*/
listen(port, callback = () => {}) {
this.server.listen(port);
callback();
}
/**
* Add middleware
* #param {Callback} callback
*/
middleware(callback) {
this.urls.push(new Path('ALL', '*', callback));
}
}
/**
* Create the app
* #returns {App}
*/
function createServer() {
return new App();
}
module.exports = createServer;
Related
I'm currently working with the cardano serialization library to code crypto transactions in typescript. I'm referencing a wallet connector template that has example code for transactions but is all written in Javascript. So in translating it over I've received a number of syntax errors that have generally been easy to resolve until now. I have a number of ts(2532) errors (Object is possibly 'undefined' that are solved through '?' chaining, I know that's not my best solution but for now I'm trying to get it error free first. When I utilize a ? operator chain though I then get the property does not exist on type 'never' error in the following code
getNetworkId = async () => {
try {
const networkId = await this?.API?.getNetworkId()
this.setState({ networkId })
} catch (err) {
console.log(err)
} }
Below I've included the majority of the code down until that error
export default class App extends React.Component<any, any> {
API: undefined
protocolParams: {
linearFee: { minFeeA: string; minFeeB: string }
minUtxo: string
poolDeposit: string
keyDeposit: string
maxValSize: number
maxTxSize: number
priceMem: number
priceStep: number
coinsPerUtxoWord: string
}
constructor(props: any) {
super(props)
this.state = {
selectedTabId: '1',
whichWalletSelected: undefined,
walletFound: false,
walletIsEnabled: false,
walletName: undefined,
walletIcon: undefined,
walletAPIVersion: undefined,
wallets: [],
networkId: undefined,
Utxos: undefined,
CollatUtxos: undefined,
balance: undefined,
changeAddress: undefined,
rewardAddress: undefined,
usedAddress: undefined,
txBody: undefined,
txBodyCborHex_unsigned: '',
txBodyCborHex_signed: '',
submittedTxHash: '',
addressBech32SendADA:
'addr_test1qrt7j04dtk4hfjq036r2nfewt59q8zpa69ax88utyr6es2ar72l7vd6evxct69wcje5cs25ze4qeshejy828h30zkydsu4yrmm',
lovelaceToSend: 3000000,
assetNameHex: '4c494645',
assetPolicyIdHex:
'ae02017105527c6c0c9840397a39cc5ca39fabe5b9998ba70fda5f2f',
assetAmountToSend: 5,
addressScriptBech32:
'addr_test1wpnlxv2xv9a9ucvnvzqakwepzl9ltx7jzgm53av2e9ncv4sysemm8',
datumStr: '12345678',
plutusScriptCborHex: '4e4d01000033222220051200120011',
transactionIdLocked: '',
transactionIndxLocked: 0,
lovelaceLocked: 3000000,
manualFee: 900000,
}
/**
* When the wallet is connect it returns the connector which is
* written to this API variable and all the other operations
* run using this API object
*/
this.API = undefined
/**
* Protocol parameters
* #type {{
* keyDeposit: string,
* coinsPerUtxoWord: string,
* minUtxo: string,
* poolDeposit: string,
* maxTxSize: number,
* priceMem: number,
* maxValSize: number,
* linearFee: {minFeeB: string, minFeeA: string}, priceStep: number
* }}
*/
this.protocolParams = {
linearFee: {
minFeeA: '44',
minFeeB: '155381',
},
minUtxo: '34482',
poolDeposit: '500000000',
keyDeposit: '2000000',
maxValSize: 5000,
maxTxSize: 16384,
priceMem: 0.0577,
priceStep: 0.0000721,
coinsPerUtxoWord: '34482',
}
this.pollWallets = this.pollWallets.bind(this)
}
/**
* Poll the wallets it can read from the browser.
* Sometimes the html document loads before the browser initialized browser plugins (like Nami or Flint).
* So we try to poll the wallets 3 times (with 1 second in between each try).
*
* Note: CCVault and Eternl are the same wallet, Eternl is a rebrand of CCVault
* So both of these wallets as the Eternl injects itself twice to maintain
* backward compatibility
*
* #param count The current try count.
*/
pollWallets = (count = 0) => {
const wallets = []
for (const key in window.cardano) {
if (window.cardano[key].enable && wallets.indexOf(key) === -1) {
wallets.push(key)
}
}
if (wallets.length === 0 && count < 3) {
setTimeout(() => {
this.pollWallets(count + 1)
}, 1000)
return
}
this.setState(
{
wallets,
whichWalletSelected: wallets[0],
},
() => {
this.refreshData()
}
)
}
/**
* Handles the tab selection on the user form
* #param tabId
*/
handleTabId = (tabId: any) => this.setState({ selectedTabId: tabId })
/**
* Handles the radio buttons on the form that
* let the user choose which wallet to work with
* #param obj
*/
handleWalletSelect = (obj: { target: { value: any } }) => {
const whichWalletSelected = obj.target.value
this.setState({ whichWalletSelected }, () => {
this.refreshData()
})
}
/**
* Generate address from the plutus contract cborhex
*/
generateScriptAddress = () => {
// cborhex of the alwayssucceeds.plutus
// const cborhex = "4e4d01000033222220051200120011";
// const cbor = Buffer.from(cborhex, "hex");
// const blake2bhash = blake.blake2b(cbor, 0, 28);
const script = PlutusScript.from_bytes(
Buffer.from(this.state.plutusScriptCborHex, 'hex')
)
// const blake2bhash = blake.blake2b(script.to_bytes(), 0, 28);
const blake2bhash =
'67f33146617a5e61936081db3b2117cbf59bd2123748f58ac9678656'
const scripthash = ScriptHash.from_bytes(Buffer.from(blake2bhash, 'hex'))
const cred = StakeCredential.from_scripthash(scripthash)
const networkId = NetworkInfo.testnet().network_id()
const baseAddr = EnterpriseAddress.new(networkId, cred)
const addr = baseAddr.to_address()
const addrBech32 = addr.to_bech32()
// hash of the address generated from script
console.log(Buffer.from(addr.to_bytes(), 'utf8').toString('hex'))
// hash of the address generated using cardano-cli
const ScriptAddress = Address.from_bech32(
'addr_test1wpnlxv2xv9a9ucvnvzqakwepzl9ltx7jzgm53av2e9ncv4sysemm8'
)
console.log(Buffer.from(ScriptAddress.to_bytes(), 'utf8').toString('hex'))
console.log(ScriptAddress.to_bech32())
console.log(addrBech32)
}
/**
* Checks if the wallet is running in the browser
* Does this for Nami, Eternl and Flint wallets
* #returns {boolean}
*/
checkIfWalletFound = () => {
const walletKey = this.state.whichWalletSelected
const walletFound = !!window?.cardano?.[walletKey]
this.setState({ walletFound })
return walletFound
}
/**
* Checks if a connection has been established with
* the wallet
* #returns {Promise<boolean>}
*/
checkIfWalletEnabled = async () => {
let walletIsEnabled = false
try {
const walletName = this.state.whichWalletSelected
walletIsEnabled = await window.cardano[walletName].isEnabled()
} catch (err) {
console.log(err)
}
this.setState({ walletIsEnabled })
return walletIsEnabled
}
/**
* Enables the wallet that was chosen by the user
* When this executes the user should get a window pop-up
* from the wallet asking to approve the connection
* of this app to the wallet
* #returns {Promise<boolean>}
*/
enableWallet = async () => {
const walletKey = this.state.whichWalletSelected
try {
this.API = await window.cardano[walletKey].enable()
} catch (err) {
console.log(err)
}
return this.checkIfWalletEnabled()
}
/**
* Get the API version used by the wallets
* writes the value to state
* #returns {*}
*/
getAPIVersion = () => {
const walletKey = this.state.whichWalletSelected
const walletAPIVersion = window?.cardano?.[walletKey].apiVersion
this.setState({ walletAPIVersion })
return walletAPIVersion
}
/**
* Get the name of the wallet (nami, eternl, flint)
* and store the name in the state
* #returns {*}
*/
getWalletName = () => {
const walletKey = this.state.whichWalletSelected
const walletName = window?.cardano?.[walletKey].name
this.setState({ walletName })
return walletName
}
/**
* Gets the Network ID to which the wallet is connected
* 0 = testnet
* 1 = mainnet
* Then writes either 0 or 1 to state
* #returns {Promise<void>}
*/
getNetworkId = async () => {
try {
const networkId = await this?.API?.getNetworkId()
this.setState({ networkId })
} catch (err) {
console.log(err)
}
}
/**
* Gets the UTXOs from the user's wallet and then
* stores in an object in the state
* #returns {Promise<void>}
*/
getUtxos = async () => {
let Utxos = []
try {
const rawUtxos = await this?.API?.getUtxos()
for (const rawUtxo of rawUtxos) {
const utxo = TransactionUnspentOutput.from_bytes(
Buffer.from(rawUtxo, 'hex')
)
const input = utxo.input()
const txid = Buffer.from(
input.transaction_id().to_bytes(),
'utf8'
).toString('hex')
const txindx = input.index()
const output = utxo.output()
const amount = output.amount().coin().to_str() // ADA amount in lovelace
const multiasset = output.amount().multiasset()
let multiAssetStr = ''
if (multiasset) {
const keys = multiasset.keys() // policy Ids of thee multiasset
const N = keys.len()
// console.log(`${N} Multiassets in the UTXO`)
for (let i = 0; i < N; i++) {
const policyId = keys.get(i)
const policyIdHex = Buffer.from(
policyId.to_bytes(),
'utf8'
).toString('hex')
// console.log(`policyId: ${policyIdHex}`)
const assets = multiasset.get(policyId)
const assetNames = assets.keys()
const K = assetNames.len()
// console.log(`${K} Assets in the Multiasset`)
for (let j = 0; j < K; j++) {
const assetName = assetNames.get(j)
const assetNameString = Buffer.from(
assetName.name(),
'utf8'
).toString()
const assetNameHex = Buffer.from(
assetName.name(),
'utf8'
).toString('hex')
const multiassetAmt = multiasset.get_asset(policyId, assetName)
multiAssetStr += `+ ${multiassetAmt.to_str()} + ${policyIdHex}.${assetNameHex} (${assetNameString})`
// console.log(assetNameString)
// console.log(`Asset Name: ${assetNameHex}`)
}
}
}
I'm attempting to dynamically set properties for an HTTP response using the getL function, but it gives me back a Promise and I cannot set async over the forEach, since that breaks it (see the generateLinksForList function). Is there another way to do this?
/**
* #module domain/helper
*/
const {
complement,
compose,
isNil,
pickBy
} = require('ramda');
const notNull = compose(complement(isNil));
/**
* #function cleanData
* #returns {undefined}
*/
const cleanData = (entity) => pickBy(notNull, entity);
/**
* #name generateRelations
* #returns {undefined}
*/
const generateRelations = () => ([]);
async function getL(repo, entityContext) {
let d = await repo.nextItem(entityContext);
console.log(d)
return d;
}
/**
* #name generateLinksForList
* #returns {undefined}
*/
const generateLinksForList = (entityContext, type, entityName, repo) => {
const relations = require(`./${entityName}/relations.js`)
const namespace = type + 'Relations';
const _relations = relations[namespace];
let host = 'http://localhost:4000';
let relationsList = [];
_relations.forEach((linkRelation) => {
Object.keys(linkRelation).forEach((key) => {
if (linkRelation.hasOwnProperty('next')) {
let relation = {};
let l = getL(repo, entityContext);
console.log('---- getL');
console.log(l);
if (l.length) {
relation.next = linkRelation.next
.replace('{nextId}', l[0].id)
}
relation.next = linkRelation.next
.replace('{fullhost}', host)
.replace('{id}', entityContext.id);
relationsList.push(relation);
}
if (linkRelation.hasOwnProperty('self')) {
let relation = {};
relation.self = linkRelation['self']
.replace('{fullhost}', host)
.replace('{id}', entityContext.id);
relationsList.push(relation);
}
})
});
return relationsList;
};
/**
* #function generateLinksForItem
* #static
* #returns {array}
*/
const generateLinksForItem = (entityContext, type, entityName) => {
const relations = require(`./${entityName}/relations.js`)
const namespace = type + 'Relations';
const _relations = relations[namespace];
let host = 'http://localhost:4000';
let token, eventToken;
let _list = [];
_relations.forEach((relRef) => {
Object.keys(relRef).forEach((keyRef) => {
if (relRef[keyRef].includes('{id}')) {
relRef[keyRef] = relRef[keyRef].replace(/{id}/, entityContext.id);
}
if (relRef[keyRef].includes('{fullhost}')) {
relRef[keyRef] = relRef[keyRef].replace(/{fullhost}/, host);
}
if (relRef[keyRef].includes('{token}')) {
relRef[keyRef] = relRef[keyRef].replace(/{token}/, token);
}
if (relRef[keyRef].includes('{eventToken}')) {
relRef[keyRef] = relRef[keyRef].replace(/{eventToken}/, eventToken);
}
_list.push({
rel: keyRef,
href: relRef[keyRef]
});
});
});
return _list;
};
/**
* #function generateClassList
* #static
* #returns {array}
*/
const generateClassList = (context) => (['organization']);
/**
* #function generateEntities
* #param {object} repo Repostory for generating entities under a resource.
* #returns {array}
*/
const generateEntities = (repo) => {
return repo.getAll()
.then((documentSnapshots) => {
return documentSnapshots.map((doc) => {
if (doc.exists) {
let data = doc.data()
return {
//class: generateClassList(data.id),
//rel: generateRelations(data.id),
properties: data
//links: generateLinksForItem(data.id, 'item', 'user')
};
}
});
});
};
/**
* #function generateActions
* #static
* #returns {array}
* #description
* Ok, so here we're basically scripting with CasperJS. The presumption is
* that there will be a generic client through which we can automate browser
* behaviors and platform capabilities (Cordova). We can think of hypermedia
* controls as projections from rhetorical relations over a domain knowledge
* graph that represents the plurality of connections or links in hypermedia
* ensembles (differentiations of numerical and other kinds of identity).
*/
const generateActions = (_itemForms, entity, entityName) => {
let host = `http://localhost:4000/api/${entityName}`;
_itemForms.forEach(function (itemRef, key) {
Object.keys(itemRef).forEach(function (keyRef) {
if (itemRef[keyRef].includes('{id}')) {
itemRef[keyRef] = itemRef[keyRef].replace(/{id}/, entity.id);
}
if (itemRef[keyRef].includes('{fullhost}')) {
itemRef[keyRef] = itemRef[keyRef].replace(/{fullhost}/, host);
}
if (itemRef.hasOwnProperty('properties')) {
itemRef.properties.forEach((p) => {
Object.keys(p).forEach((k) => {
if (p[k].includes('{status}')) {
p[k] = p[k].replace(/{status}/, 'pending');
}
});
});
}
});
});
return _itemForms;
};
module.exports = {
cleanData,
generateLinksForItem,
generateLinksForList,
generateClassList,
generateActions,
generateEntities
};
// EOF
This is where I'm using the generateLinksForList function, hence why I cannot use async for the containing map:
/**
* #module app/place/get
* #description
* Get all places.
*/
const { itemForms } = require('../../domain/place/transitions');
const { Place } = require('../../domain/place');
const {
generateActions,
generateLinksForList,
} = require('../../domain/helper.js');
module.exports = ({ placeRepository }) => {
const all = () => {
return Promise
.resolve()
.then(() => placeRepository.getAll()
.then((documentSnapshots) => {
return documentSnapshots.map((doc) => {
let properties = doc.data() || {};
let place = Place(properties);
let links = generateLinksForList(place, 'item', 'place', placeRepository);
let actions = generateActions(itemForms, doc, 'places');
let props = Object.assign({}, place, {});
let hypermediaResponse = {
actions: actions,
links: links,
properties: props,
class: ['place'],
entities: []
};
if (doc.exists) {
return hypermediaResponse;
}
});
})
)
.catch(error => {
throw new Error(error);
})
}
return {
all
};
};
// EOF
The repo function:
/**
* #module infrastructure/repositories/place/index
* #description
* Repo/methods for places.
*/
const { toEntity } = require('./transform');
module.exports = ({ model, database }) => {
/**
* #name getAll
* #param {Object} args - Firestore clause (where, etc.);
* #returns {Object}
*/
const _getAll = async (...args) =>
await model
.listDocuments().then((docRefs) => database.firestore.getAll(...docRefs));
/**
* #name getAll
* #returns {Promise}
*/
const getAll = async () =>
await model.listDocuments()
.then((docRefs) => database.firestore.getAll(...docRefs));
/**
* #name prev
* #returns {undefined}
*/
const prev = async (...args) => {
let payload = args[0];
let docRef = model.doc(payload.id);
let snapshot = await docRef.get();
let last = snapshot.docs[snapshot.docs.length - 1];
return last.data();
};
/**
* #name next
* #returns {undefined}
*/
const nextItem = async (...args) => {
let payload = args[0];
const docRef = model.doc(payload.id);
const snapshot = (await docRef.get()).data();
const start = await model
.orderBy('createdAt')
.endBefore(snapshot.createdAt)
.limitToLast(1)
//.startAt(snapshot)
//.offset(1)
//.limit(1)
.get();
let list = [];
start.forEach((d) => {
list.push(d.data());
});
return list;
};
/**
* #name create
* #returns {object}
*/
const create = async (...args) => {
let payload = args[0];
let docRef = await model.doc(payload.id);
await docRef.set(payload);
return docRef.get().then((documentSnapshot) => {
return documentSnapshot.data();
})
};
/**
* #name update
* #returns {object}
*/
const update = async (...args) =>
await model.doc(args.id).update(...args);
/**
* #name findById
* #returns {object}
*/
const findById = async (...args) =>
await model.where('id', '==', args.id).get();
/**
* #name findOne
* #returns {object}
*/
const findOne = async (...args) => await model
.limit(1)
.get();
/**
* #name destroy
* #returns {object}
*/
const destroy = async (...args) =>
await model.doc(args.id).delete();
return {
getAll,
create,
update,
findById,
findOne,
destroy,
nextItem,
prev
};
};
// EOF
.forEach won't wait for an async function. Use a normal loop:
for ( const linkRelation of relations ) {
for ( const key in linkRelation) {
...
const l = await getL(repo, entityContext);
...
}
}
I have a test file with a single describe that I want to run it twice but each time I want to pass different parameters into it.
My code looks like that:
MochaRunner.js
'use strict';
const fs = require('fs');
const path = require('path');
const Mocha = require('mocha');
const uuidv1 = require('uuid/v1');
class MochaRunner {
/**
* #param {Object} options
*/
constructor(options = {}) {
this._tmps = [];
this._mocha = new Mocha(options);
}
/**
* Run tests
* #param {Array} tests
* #returns {Promise}
*/
run(tests) {
return new Promise((resolve, reject) => {
const Config = require('../config').create();
let counter = 0;
tests.forEach(test => {
const testDir = path.dirname(test.testPath);
const tmpTest = path.join(testDir,`${uuidv1()}.spec.js`);
fs.writeFileSync(tmpTest, fs.readFileSync(test.testPath));
this._tmps.push(tmpTest);
this._mocha.addFile(tmpTest);
Config.setOptions('var ' + counter++);
});
this._mocha.run((err) => {
return err ? reject(err) : resolve();
});
}).catch(() => {
return this.cleanup();
});
}
/**
* Get mocha instance
* #returns {Mocha}
*/
getMocha() {
return this._mocha;
}
/**
* Remove tmp test files
* #returns {Promise}
*/
cleanup() {
this._tmps.forEach(tmpTest => {
fs.unlinkSync(tmpTest);
});
return Promise.resolve();
}
}
module.exports = MochaRunner;
Config.js
class Config {
constructor() {
this.options = {};
}
setOptions(options){
this.options = options;
}
}
module.exports = {
_instance: null,
create: function(){
if (this._instance){
return this._instance;
}
this._instance= new Config();
return this._instance;
}
};
test.e2e.test.js
const chai = require('chai');
const Config = require('./config').create();
const assertionStyles = {
assert: chai.assert,
expect: chai.expect,
should: chai.should,
};
setImmediate(async () => {
describe('describe blah blha', function () {
console.log(Config.options);
it('test case', function () {
assertionStyles.expect(true).to.be.true;
});
});
run();
});
I do this when I want to run the above code:
let tests = ['test.e2e.test.js', 'test.e2e.test.js'];
let mocha = new MochaRunner({});
await mochaRunner.run(tests).then(() => {
mochaRunner.cleanup();
});
In the above example, I want to pass different data when the test will run for second time.
For example, I want the first file read the param from Config with the value:
'var 0'
and the second file read the param with the value:
'var 1'
Any ideas please?
Any help would be greatly appreciated!
I have a React component that I've migrated over from a JS Component. I'm migrating over and checking the tests and I've got lots of failures because stubbing doesn't seem to be working anymore. Here's my component...
import DeleteButton from "./delete-button.jsx"
import Dialogs from "../../dialogs";
import React from "react";
import UrlHelper from "../../helpers/url-helper";
export default class ActiveDeleteButton extends React.Component {
/**
* Creates an instance of ActiveDeleteButton.
*
* #param {object} props The react props collection.
*
* #memberOf ActiveDeleteButton
*/
constructor(props) {
super (props);
this.handleConfirmDelete = this.handleConfirmDelete.bind(this);
}
handleConfirmDelete() {
$.ajax({
url: this.props.deleteUri,
type: `DELETE`,
contentType: `application/json; charset=utf-8`,
cache: false,
success: (xhr) => {
let successUri = this.props.successUri;
if (!successUri && xhr && xhr.uri) { successUri = xhr.uri; }
if (successUri) { UrlHelper.redirect(successUri); }
},
error: (xhr, status) => {
this.showFailed();
}
});
}
/**
* Shows failure of deletion.
*
* #memberOf ActiveDeleteButton
*/
showFailed() {
Dialogs.alert(this.props.errorMessage);
}
/**
* Renders the component to the DOM.
*
* #returns the HTML to render.
*
* #memberOf ActiveDeleteButton
*/
render() {
return (
<DeleteButton text = {this.props.text}
title = {this.props.title}
cancelText = {this.props.cancelText}
confirmText = {this.props.confirmText}
message = {this.props.message}
onConfirmDelete = {this.handleConfirmDelete} />
);
}
}
And here's the test (condensed)...
describe("performs a DELETE AJAX request", () => {
it ("for specified URLs", sinon.test(function() {
let wrapper = shallow(<ActiveDeleteButton text = "Click Me" />);
let instance = wrapper.instance();
let ajaxStub = this.stub($, 'ajax');
instance.forceUpdate()
wrapper.update()
instance.handleConfirmDelete();
console.log(ajaxStub.getCall(0));
let options = ajaxStub.getCall(0).args[0];
assert.equal(options.url, objUt.deleteUri);
assert.equal(options.type, "DELETE");
}));
}));
The issue I have is that 'ajaxStub.getCall(0)' returns null. This should return the Ajax call so I can check the args (and it used to before in my old JS component). The stub is never called although it (to my mind) clearly should be.
Am I missing something here?
This is more a workaround than anything else so a better answer would be great. In the end I built a workaround for this as follows...
Firstly I created a new class to handle Ajax Requests.
/**
* An AJAX request wrapper.
* Usage of this enables testing AJAX calls.
*
* #export AjaxRequest
* #class AjaxRequest
* #extends {AjaxRequest}
*/
export default class AjaxRequest {
/**
* Creates an instance of AjaxRequest.
* #param {any} { url, type, contentType, cache, data, successCallback, errorCallback }
*
* #memberOf AjaxRequest
*/
constructor({ url, type, contentType, cache, data, successCallback, errorCallback }) {
Guard.throwIf(url, "url");
let emptyFunc = () => {};
this.url = url;
this.type = type.toUpperCase() || "GET";
this.contentType = contentType || "application/json; charset=utf-8";
this.dataType = "json";
this.cache = cache || false;
this.data = data ? JSON.stringify(data) : undefined;
this.successCallback = successCallback || emptyFunc;
this.errorCallback = errorCallback || emptyFunc;
}
/**
* Executes the AJAX request.
*
*
* #memberOf AjaxRequest
*/
execute() {
$.ajax({
url: this.url,
type: this.type,
contentType: this.contentType,
dataType: this.dataType,
cache: this.cache,
data: this.data,
success: this.successCallback,
error: this.errorCallback
});
}
/**
* Gets a GET request.
*
* #static
* #param {string} url
* #param {function} successCallback
* #param {function} errorCallback
* #returns an AjaxRequest
*
* #memberOf AjaxRequest
*/
static get(url, successCallback, errorCallback) {
return new AjaxRequest({
url: url,
type: 'GET',
successCallback: successCallback,
errorCallback: errorCallback
});
}
/**
* Gets a POST request.
*
* #static
* #param {string} url
* #param {object} data
* #param {function} successCallback
* #param {function} errorCallback
* #returns an AjaxRequest
*
* #memberOf AjaxRequest
*/
static post(url, data, successCallback, errorCallback) {
return new AjaxRequest({
url: url,
data: data,
type: 'POST',
successCallback: successCallback,
errorCallback: errorCallback
});
}
/**
* Gets a PUT request.
*
* #static
* #param {string} url
* #param {object} data
* #param {function} successCallback
* #param {function} errorCallback
* #returns an AjaxRequest
*
* #memberOf AjaxRequest
*/
static put(url, data, successCallback, errorCallback) {
return new AjaxRequest({
url: url,
data: data,
type: 'PUT',
successCallback: successCallback,
errorCallback: errorCallback
});
}
/**
* Gets a DELETE request.
*
* #static
* #param {string} url
* #param {function} successCallback
* #param {function} errorCallback
* #returns an AjaxRequest
*
* #memberOf AjaxRequest
*/
static delete(url, successCallback, errorCallback) {
return new AjaxRequest({
url: url,
type: 'DELETE',
successCallback: successCallback,
errorCallback: errorCallback
});
}
}
(i feel there's a certain amount of wheel re-inventing here). I then updated my method call in my component as follows...
handleConfirmDelete() {
AjaxRequest.delete(this.props.deleteUri,
(xhr) => {
let successUri = this.props.successUri;
if (!successUri && xhr && xhr.uri) { successUri = xhr.uri; }
if (successUri) { UrlHelper.redirect(successUri); }
},
(xhr, status) => {
this.showFailed();
}
).execute();
}
I can now test as follows...
describe("performs a DELETE AJAX request", () => {
let wrapper = null;
let instance = null;
let ajaxStub = null;
let urlHelperRedirectStub = null;
beforeEach(() => {
ajaxStub = sinon.stub(AjaxRequest.prototype, 'execute');
urlHelperRedirectStub = sinon.stub(UrlHelper, 'redirect');
wrapper = shallow(<ActiveDeleteButton text = "Click Me" />);
instance = wrapper.instance();
});
afterEach(() => {
ajaxStub.restore();
urlHelperRedirectStub.restore();
});
it ("for default URLs", sinon.test(function() {
instance.handleConfirmDelete();
sinon.assert.called(ajaxStub);
let requestInfo = ajaxStub.getCall(0).thisValue;
assert.equal(UrlHelper.current.url().split('?')[0], requestInfo.url);
}));
it ("for specified URLs", sinon.test(function() {
wrapper = shallow(<ActiveDeleteButton text = "Click Me" deleteUri="http://localhost/items/12" />);
instance = wrapper.instance();
instance.handleConfirmDelete();
sinon.assert.called(ajaxStub);
let requestInfo = ajaxStub.getCall(0).thisValue;
assert.equal("http://localhost/items/12", requestInfo.url);
}));
}));
Using the thisValue property of the stub call I can get the callbacks and execute them manually to test them depending on different input. Not an ideal solution given the effort needed but it works and its reusable.
I feel there must be a better way however.
I am trying to write a jest test for following function, that sets sessionStorage entry:
/**
* #desc create authenticated user session
* #param {String} [email='']
* #param {Date} [expires=Date.now()]
* #param {String} [hash='']
* #param {Array} users
* #return {Session}
*/
export const setSession = (
email = '',
expires = Date.now(),
hash = '',
users: []) => {
const session = {
email,
expires,
hash,
users
};
sessionStorage.setItem('MY_SESSION', JSON.stringify(session));
};
I am confused to where to start i.e. I get jest error saying that session storage is not defined in a simple test like this:
it('takes in session parameters and returns sessionStorage', () => {
console.log(setSession('test#me.io', Date.now(), 'ha_sh', []));
});
You will have to mock the session storage in your unit test to be abble to call it.
One way to do this is :
var myStorage = function() {
var sessionStorage = {};
return {
getItem: function(key) {
return sessionStorage[key];
},
setItem: function(key, value) {
sessionStorage[key] = value.toString();
},
clear: function() {
sessionStorage = {};
}
};
};
Object.defineProperty(window, 'sessionStorage', { value: myStorage });