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.
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}`)
}
}
}
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;
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 Vue script. When I debug it with PhpStorm, I got Unresolved variable syntax error even though I set the variable redirect_to. Please check image. How can I solve it?
<script>
const timeToRefetch = 2000;
let intervalRef;
const homePageUrl = "/";
let messageCount = 0;
var app = new Vue({
el: "#app",
data: {
status: "",
message: "",
redirect_to: ""
},
created() {
const refThis = this;
intervalRef = setInterval(() => {
fetch(ApiUrl)
.then(response => response.json())
.then(repos => {
refThis.status = repos.status;
this.message = repos.message;
clearInterval(intervalRef);
setTimeout(() => {
window.location.href = repos.redirect_to;
}, 3000);
},
);
}, timeToRefetch);
}
});
</script>
If you use some object with keys only known in runtime (generated, received through the ajax call, etc.) in your code, there is no way for the IDE to resolve them using static code analysis. But you can let the IDE know what your runtime data looks like. Possible solution using JSDoc annotations:
/**
* #typedef {Object} repos
* #property {string} status
* #property {string} message
* #property {string} redirect_to
*
*/
...
var app = new Vue({
el: "#app",
data: {
status: "",
message: "",
redirect_to: ""
},
created() {
const refThis = this;
intervalRef = setInterval(() => {
fetch(ApiUrl)
.then(response => response.json())
.then(
/**
* #param {repos} repos
*/
(repos) => {
refThis.status = repos.status;
this.message = repos.message;
clearInterval(intervalRef);
setTimeout(() => {
window.location.href = repos.redirect_to;
}, 3000);
},
);
}, timeToRefetch);
}
});
See also https://youtrack.jetbrains.com/issue/WEB-17419#comment=27-1058451, https://intellij-support.jetbrains.com/hc/en-us/community/posts/206349469-disable-unresolved-variable-on-json-object-received-by-ajax-call for other possible workarounds
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 });