I have a Vue component with one method...
methods: {
launchOpinionLab () {
initOpinionLab({
clientId: this.clientId,
flow: this.flow,
srcCorrelationId: this.srcCorrelationId
})
window.OOo.inlineFeedbackShow()
}
initOpinionLab() is a exported function as such
initOpinionLab.js
/**
* Initializes the OpinionLab library with custom data.
*
* #param {Object} data - any data we want to attach to user feedback submissions
*/
export default function initOpinionLab (data) {
const opinionLab = window.OOo
/**
* Callback to launch the feedback comment card.
* This assigns our custom data to the OpinionLab instance.
*
* #param {Event} event - window event
*/
opinionLab.inlineFeedbackShow = (event) => {
let replacePattern = '://' + window.location.host
if (window.location.host !== 'checkout.mastercard.com') {
replacePattern = '://test.checkout.mastercard.com'
}
opinionLab.oo_feedback = new opinionLab.Ocode({
referrerRewrite: {
searchPattern: /:\/\/[^/]*/,
replacePattern: replacePattern
},
customVariables: data
})
// Now that oo_feedback has been re-initialized with the custom
// var and context of the current page, launch the comment card
opinionLab.oo_launch(event, 'oo_feedback')
}
/**
* Launches opinionLab.
*
* #param {Event} event
* #param {Object} feedback - opinionLab-structured feedback object
*/
opinionLab.oo_launch = (event, feedback) => {
opinionLab[feedback].show(event || window.event)
}
}
At the top of my unit test is the mock of that module, jest.mock('./initOpinionLab', () => jest.fn()).
My goal is to assert that initOpinionLab({}) is called with the correct props. When I try to spy on it, I get "Cannot spy the initOpinionLab property because it is not a function; undefined given instead". How do I write a robust test for this?
Or, what if I moved initOpinionLab() to mounted? Can I better test it then?
component.spec.js
it('[positive] initOpinionLab should be initialized with props' , () => {
const initOpinionLab = jest.fn()
jest.spyOn(wrapper.vm, 'initOpinionLab')
wrapper.vm.launchOpinionLab()
expect(initOpinionLab).toHaveBeenCalled()
})
Related
I have a json file that stores data which is displayed on my page using javascript. This json file and its key val pairs are not visible or accessible in Chrome's Dev Tools. This component manages json files:
/**
* Takes a filename and a JS object and initiates a download through the browser
* #param {String} filename
* #param {any} object JSON serializable object
* #return {undefined}
*/
export const downloadJson = (filename, object) => {
const content = JSON.stringify(object, null, 2);
const el = document.createElement('a');
el.setAttribute('href', `data:application/json;charset=utf-8,${encodeURIComponent(content)}`);
el.setAttribute('download', filename);
el.hidden = true;
document.body.appendChild(el);
el.click();
document.body.removeChild(el);
};
/**
* Gets the `target.result` property from an event, or returns null
* if it fails at any point
* #type {Function}
* #param {Event} event load Event
* #return {File}
*/
const getFileResult = propPathOr(null, ['target', 'result']);
/**
* Takes a file and reads it as JSON, resolving the JSON-parsed
* file contents
* #param {File} file
* #return {Promise<[Object]>} Returns Promise of Array of Archive Entries
*/
export const readFileAsJson = file => {
const reader = new FileReader();
const promise = new Promise((resolve, reject) => {
reader.onload = compose(resolve, JSON.parse, getFileResult);
reader.onerror = reject;
});
reader.readAsText(file);
return promise;
};
export const readFileListAsJson = files =>
Promise.all(
Array.from(files)
.map(readFileAsJson)
)
.catch(console.error);
This is the database component:
// DATABASE functions
import { get, set, keys } from 'idb-keyval';
import { sha1 } from './hash.js';
const getKey = key => get(key);
export const getAllEntries = async () =>
await Promise.all((await keys()).map(getKey));
export const writeMultipleEntries = entries =>
entries.forEach(writeSingleEntry);
/**
* #typedef {Object} ArchiveEntry
* #property {String} date
* #property {String} passage
* #property {String} question
* #property {String} answer
*/
/**
* Writes a single archive entry to idb
* #param {ArchiveEntry} entry
* #return {ArchiveEntry}
*/
export const writeSingleEntry = async ({ date, passage, question, answer }) => {
const hash = await hashEntry({ date, passage, question });
await set(hash, { date, passage, question, answer });
return { date, passage, question, answer };
};
/**
* Generates a hash of an entry to use as it's idb key
* #param {ArchiveEntry} entry
* #return {string}
*/
const hashEntry = ({ date, passage, question }) =>
sha1(`${date}-${passage}-${question}`);
Values are stored using this function:
const updateDb =
({ passage, question }) =>
(answer) =>
writeSingleEntry({ date: new Date(), answer, passage, question });
Storage is handled by its own script:
export const storeOnInput = key => ({ target: { value } }) => writeValue(key, value);
export const readValue = key => localStorage.getItem(key);
export const writeValue = (key, val) => localStorage.setItem(key, val);
It is called in several components. Here to write and read the value of a text passage:
onActiveChanged(active) {
this.passage = readValue('passage-input');
}
onKeyup(event) {
writeValue('passage-input', event.target.value);
}
Here to write and record a question:
onActiveChanged(active) {
this.question = readValue("question-input");
this.passage = readValue("passage-input");
}
onKeyup(event) {
writeValue("question-input", event.target.value);
}
Here to provide an answer and reset the form:
const answer = document.getElementById('answer');
const write = document.getElementById('write');
const question = document.getElementById('question');
const onAnswerSubmitted = ({ detail: answer }) => {
writeValue('answer', answer);
};
onActiveChanged(active) {
if (!active) return;
this.answer = readValue('answer');
}
resetQuestion() {
this.dispatchEvent(new CustomEvent('reset-question'));
writeValue('question-input', '');
writeValue('answer', '');
}
resetWrite() {
this.resetQuestion();
this.dispatchEvent(new CustomEvent('reset-passage'));
writeValue('passage-input', '');
}
Here to get entries:
onActiveChanged(active) {
if (active) this.getEntries();
}
async getEntries() {
this.entries = await getAllEntries();
this.entry = new URLSearchParams(location.search.substring(1)).get("date");
console.log("here are the dates: \n", prettyDate(this.entries[0].date));
console.log("here is an answer: \n", this.entries[0].answer);
}
Here to download and upload the JSON file:
async exportBackup() {
downloadJson(`backup ${new Date()}.json`, await getAllEntries());
}
async importBackup({ target: { files } }) {
return readFileListAsJson(files)
.then(map(writeMultipleEntries));
}
Unlike this question, nothing is showing in Storage > Local Storage, and it is not
a Chrome UI design flaw issue.
It is possible to confirm the values have been written and are are accessible from the json file using functions like:
console.log(this.entries[0].date)
console.log(this.entries[0].answer)
but I would like to be able to debug by viewing the entire json file.
I had the same problem today while working on my webapp :
I could access some data i registered on the localstorage via the console (JSON.parse(localStorage["my-storage-key"])
But in the Chrome dev tools, in the Application tab, the https://localhost:4200 entry was totaly empty, just like in the screen capture you provided.
What fixed the problem for me was to click "Restore defaults and reload" in the preferences of the chrome DevTools, and i could see the entries in the table again.
It doesn't appear as though you have loaded the JSON file into local storage at any point. Perhaps there is driver code which you can share so that your issue can be more easily debugged.
In the meantime, checkout the documentation for localstorage on mdn. I think you may find the answer by reading up on how to set local storage.
I am seeing this issue a lot in my code and I've seen similar posts about it on here but they don't seem linked to my issue specifically, which makes me think I'm missing something somewhere.
The comment 'await' has no effect on the type of this expression.ts(80007) comes up (as an example case) in the async method below...
/**
* Deletes the account for the currently logged in user.
*/
async deleteAccount() {
Logger.log('Deleting the currently logged in user', 'Auth')
const service = ServiceFactory.profile()
const operationState = await service.deleteUser()
if (!operationState.succeeded)
operationState.throw()
},
Dropping through the stack, this calls through as follows to this method in the ProfileService class which is returned above in ServiceFactory.profile()...
/**
* Deletes a user.
* #returns {ServiceOperation} The operation result.
* #memberof ProfileService
*/
async deleteUser() {
const operationState = new ServiceOperation('Delete User', true)
const url = `${ this.baseUrl }?SchemeId=${ this.schemeId }`
Logger.log(url, 'Request [DELETE]')
try {
return this.processResponse(await Axios.delete(url), operationState, 'deleteUser')
} catch (err) {
return this.failOperation(err, operationState, 'deleteUser')
}
}
The issue seems related to the processResponse method on the base class that transforms my JSON responses from my back end into ServiceOperation classes which tracks running state, success, merges multiple results and transforms different types of error responses to be consistent for the app to handle - the code for which is largely irrelevant to add here...
/**
* Handles a succesfull data changing operation and processes the response safely.
*
* #param {Object} response The service response.
* #param {ServiceOperation} operationState
* #param {string} [methodName='?'] The name of the calling method.
* #returns {ServiceOperation} The modified operation state.
*
* #memberof Service
*/
processResponse(response, operationState, methodName = '?') {
if (response.data !== false && !response.data || response.data == null) {
operationState.complete(true)
} else {
operationState.complete(true, response.data)
}
Logger.logObject(response, `Operation completed [${ methodName }]`, this.loggingCategory)
return operationState
}
failOperation is simply processResponse fail call method...
/**
* Handles a failed operation.
*
* #param {Error} error
* #param {ServiceOperation} operationState
* #param {string} [methodName='?'] The name of the calling method.
* #returns {ServiceOperation} The modified operation state.
*
* #memberof Service
*/
failOperation(error, operationState, methodName = '?') {
operationState.fail(error)
Logger.logObject(operationState, `Data service failed [${ methodName }]`, this.loggingCategory)
return operationState
}
Both of the above methods are part of the Service class which is extended to form the ProfileService class earlier on that contains the deleteUser function (The Service class acts as a base class for each of the service classes in the app.
Where I'm a bit confused here is that processResponse isn't async (and doesn't need to be). The awaited call is awaited in the method that calls it. Is this just a case of the editor not picking it up or, as I suspect, is it that I'm losing the underlying promise when I process the data - If that's the case how can I get the promise to surface up for the method result that processResponse needs?
processResponse is a generic method that handles all the service method results I have in my app so merging the methods isn't viable. I've considered making processResponse itself async and using await on it but that seems logically like it would do absolutely nothing as there is nothing to really await.
Functionally the app code seems to be working fine so I'm loathe to start pulling it apart at this level until I understand exactly why it's moaning.
EXTRA (but probably irrelevant) INFO
This is the definition of the ServiceOperation class that the Service class and it's derivatives make use of. This is for completion only and is largely irrelevant.
/**
* Defines an operation performed through a service.
*
* #export
* #class ServiceOperation
*/
export default class ServiceOperation {
/**
*Creates an instance of ServiceOperation.
* #param {string} name
* #param {boolean} [started=false]
* #memberof ServiceOperation
*/
constructor(name, started = false) {
this.name = name
this.running = started
this.completed = false
this.succeeded = false
}
/**
* Completes a service operation.
*
* #param {boolean} succeeded
* #param {object} data Any data or error info returned
* #returns {ServiceOperation} itself.
* #memberof ServiceOperation
*/
complete(succeeded, data) {
this.running = false
this.completed = true
this.succeeded = succeeded
if (data !== undefined && data !== null || this.data !== undefined && this.data !== null)
this.data = data
return this
}
/**
* Fails a service operation.
*
* #param {string} error An error
* #returns {ServiceOperation} itself.
* #memberof ServiceOperation
*/
fail(error) {
this.complete(false)
if (error && error.response && error.response.data) {
this.data = error.response.data
this.errorObject = error
this.error = error.response.data.message ? error.response.data.message : error.message
this.errorObject.message = this.error
} else {
this.error = error && error.message ? error.message : error
this.errorObject = error
}
return this
}
/**
* Throws the wrapped error object back for interrogation.
*
* #memberof ServiceOperation
*/
throw() {
if (this.errorObject)
throw this.errorObject
else if (this.error)
throw new Error(this.error)
else
throw new Error('Service Operation error')
}
/**
* Merges another operation into this one, updating it's info.
*
* #param {ServiceOperation} operation
* #param {boolean} includeData If not set then the data item will be eradicated if it is present.
* #memberof ServiceOperation
*/
updateMergeFrom(operation, includeData = false) {
this.running = operation.running
this.completed = operation.completed
this.succeeded = operation.succeeded
if (!includeData && this.data)
delete this.data
else if (includeData && operation.data)
this.data = operation.data
if (operation.stillPending !== undefined)
this.stillPending = operation.stillPending
if (!this.error && operation.error)
this.error = operation.error
}
/**
* Generates an immediately completed operation.
*
* #static
* #type {ServiceOperation}
* #memberof ServiceOperation
*/
static get immediate() {
return new ServiceOperation('Immediate').complete(true)
}
}
I found the answer to this digging through this article...
https://github.com/microsoft/TypeScript/issues/34508
This seems to be a warning generated by the TypeScript compiler (although my project isn't even using TypeScript so - {shrug})
In order to make it go away you need to edit your JSDoc entry as follows...
/**
* Deletes a user.
* #returns {ServiceOperation} The operation result.
* #memberof ProfileService
*/
async deleteUser() { }
becomes
/**
* Deletes a user.
* #returns {Promise<ServiceOperation>} The operation result.
* #memberof ProfileService
*/
async deleteUser() { }
Thanks to #Bergi for getting my brain to look in the right place.
So I'm trying to recreate this app on SAP Web IDE:
But I'm continuously getting this error:
This is my App.Controller.js code:
sap.ui.define([
"pt/procensus/ui5rv/controller/BaseController",
"sap/ui/model/json/JSONModel"
], function (BaseController, JSONModel) {
"use strict";
return BaseController.extend("pt.procensus.ui5rv.controller.App", {
onInit : function () {
var oViewModel,
fnSetAppNotBusy,
oListSelector = this.getOwnerComponent().oListSelector,
iOriginalBusyDelay = this.getView().getBusyIndicatorDelay();
oViewModel = new JSONModel({
busy : true,
delay : 0
});
this.setModel(oViewModel, "appView");
fnSetAppNotBusy = function() {
oViewModel.setProperty("/busy", false);
oViewModel.setProperty("/delay", iOriginalBusyDelay);
};
this.getOwnerComponent().oWhenMetadataIsLoaded.
then(fnSetAppNotBusy, fnSetAppNotBusy);
// Makes sure that master view is hidden in split app
// after a new list entry has been selected.
oListSelector.attachListSelectionChange(function () {
this.byId("idAppControl").hideMaster();
}, this);
// apply content density mode to root view
this.getView().addStyleClass(this.getOwnerComponent().getContentDensityClass());
}
});
}
);
This is my Base.Controller.js code:
/*global history */
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/core/routing/History"
], function(Controller, History) {
"use strict";
return Controller.extend("pt.procensus.ui5rv.controller.BaseController", {
/**
* Convenience method for accessing the router in every controller of the application.
* #public
* #returns {sap.ui.core.routing.Router} the router for this component
*/
getRouter: function() {
return this.getOwnerComponent().getRouter();
},
/**
* Convenience method for getting the view model by name in every controller of the application.
* #public
* #param {string} sName the model name
* #returns {sap.ui.model.Model} the model instance
*/
getModel: function(sName) {
return this.getView().getModel(sName);
},
/**
* Convenience method for setting the view model in every controller of the application.
* #public
* #param {sap.ui.model.Model} oModel the model instance
* #param {string} sName the model name
* #returns {sap.ui.mvc.View} the view instance
*/
setModel: function(oModel, sName) {
return this.getView().setModel(oModel, sName);
},
/**
* Convenience method for getting the resource bundle.
* #public
* #returns {sap.ui.model.resource.ResourceModel} the resourceModel of the component
*/
getResourceBundle: function() {
return this.getOwnerComponent().getModel("i18n").getResourceBundle();
},
/**
* Event handler for navigating back.
* It checks if there is a history entry. If yes, history.go(-1) will happen.
* If not, it will replace the current entry of the browser history with the master route.
* #public
*/
onNavBack: function() {
var sPreviousHash = History.getInstance().getPreviousHash();
if (sPreviousHash !== undefined) {
// The history contains a previous entry
history.go(-1);
} else {
// Otherwise we go backwards with a forward history
var bReplace = true;
this.getRouter().navTo("master", {}, bReplace);
}
}
});
});
My App Folders:
And I can't understand why is this happening. I've already removed the 'then' part and it gives me more errors... :/
Any help will be very much appreciated :)
Can you try this? At least this is how it works in my App.controller:
this.getOwnerComponent().getModel().metadataLoaded()
.then(fnSetAppNotBusy, fnSetAppNotBusy);
I'm currently working on a website with vue.js, and I use vue-route.
There's a subroute (/blog/post-slug) that load a script (Prism: http://prismjs.com/) and fires it in the ready() state :
import Prism from 'prismjs'
module.exports = {
Route: {
waitForData: true
},
data () {
return {
post: { }
}
},
ready () {
Prism.highlightAll()
}
}
When I reach the page from a link inside the site, it’s all good, loaded and working, the result is ok in the page.
But if I reload the page or access it with a direct link from an external link, it’s not good.
Here’s my route config, if it helps:
/**
* Router config
*/
module.exports = {
/**
* Config
* #type {Object}
*/
config: {
hasbang: false,
history: true,
saveScrollPosition: true,
linkActiveClass: 'site-menu__link--active',
transitionOnLoad: true
},
/**
* Before route starts transitioning
* #param {Object} options.from Route we are transitioning from
* #param {Object} options.to Route we are transitioning to
* #param {Function} options.next Progress to the next step of the transition
* #param {Function} options.abort Cancel / Reject the transition
* #param {Function} options.redirect Cancel and redirect to a different route
* #return {void}
*/
before ({from, to, next, abort, redirect}) {
next()
},
/**
* After route has transitioned
* #param {Object} options.from Route we are transitioning from
* #param {Object} options.to Route we are transitioning to
* #return {void}
*/
after ({from, to}) {
if (typeof to.title !== 'undefined') {
document.title = to.title
}
}
}
I guess something in particular happen when the view is loaded from inside the app, and not directly, but can’t find what.
What am I missing?
Thanks in advance for any hint.
I have an issue using prototype in node with context.
/**
* Constructor.
*
* #param object opts The options for the api.
* #param object config The application's configuration.
* #param object db The database handler.
* #return void
*/
var clientModel = function ( opts, config, db )
{
this.opts = opts;
this.config = config;
this.db = db;
};
/**
* Get a list of items.
*
* #param function cb Callback function.
* #return void
*/
clientModel.prototype.getList = function( cb )
{
this.db.query(
"SELECT FROM " + this.db.escape("client"),
function ( err, rows, fields )
{
if( err.code && err.fatal )
{
cb(
{
message: "SQL error locating client."
});
return;
}
if(! rows.length )
{
cb(
{
message: "Unable to locate client."
});
return;
}
cb( false, rows, fields );
});
};
/**
* Default http request for getting a list of items.
*
*
* #param object req The http request.
* #param object res The http response.
* #return void
*/
clientModel.prototype.httpGetList = function ( req, res )
{
this.getList( function ( err, rows, fields )
{
res.end("Got a list");
});
}
// - Append model to output.
module = module.exports = clientModel;
Basically node express framework calls httpGetList and "this" doesn't have getList due to "this" being express due to context, is there any way of improving my code in order to do this correctly, I am guessing if it got to the this.getList then this.db would also be out of context?
Any help appreciated.
You can bind your functions to an object, so that no matter how they get called, this will be as you expect it. You can find more information here.
You can bind the methods in your constructor. The underscore library has an useful bindAll method to help you with that.
I suggest you make the instance inside the module and export the functions that handle the requests.
/**
* Exports.
*
* #param object opts The options for the api.
* #param object config The application's configuration.
* #param object db The database handler.
* #return void
*/
module = module.exports = function ( opts, config, db )
{
var instance = new clientModel( opts, config, db );
return {
/**
* Default http request for getting a list of items.
*
*
* #param object req The http request.
* #param object res The http response.
* #return void
*/
httpGetList : function ( req, res )
{
instance.getList( function ( err, rows, fields )
{
res.end("Got a list");
});
}
};
};