Some months ago I created a Js library that I published on Npm.
Now I would like to rename some functions.
I read this post and I think it's very useful.
Suppose I have a file A:
export function function1(param1, param2) {
return param1 + param2
}
The exported functions that are usable from library users are in index.js file:
export { function1 } from './A'
and I want to rename it as sum(param1, param2).
I create this obsolete function:
function obsolete(newFunction, oldFnName, newFnName) {
const wrapper = function () {
console.warn(
`Obsolete function called. Function '${oldFnName}' has been deprecated, please use the new '${newFnName}' function instead.`
)
newFunction.apply(this, arguments)
}
wrapper.prototype = newFunction.prototype
return wrapper
}
Now what I have to do?
I suppose I have to modify the A file in this way:
/** #deprecated since version 2.0 */
export function function1(param1, param2) {
return sum(param1, param2)
}
export function sum(param1, param2) {
return param1 + param2
}
and add the sum function to the index file:
export { function1, sum } from './A'
And then? How can I use the obsolete function?
Ok, so this question seems to have been prompted by your comment (now deleted it seems):
Suppose I have a function fun1 that I want to make it deprecated and
the new function is fun2. How can I use this obsolete function? It
seems interesting
From this question.
So firstly, your getting a JSdoc (/** #deprecated since version 2.0 */) annotation and this function mixed up.
JSDoc is a markup language used to annotate JavaScript source code files
source
So this is only useful if your planning on creating a JSdoc annotation. Which I'm presuming your not. If your using JSdoc then this should work as is?
So ignoring that I'm going to go back to your question on this code.
Looking at the code you could use it like (not 100% sure as I didn't write it):
// the function from the previous question
function obsolete(oldFunc, newFunc) {
const wrapper = function() {
console.warn(`WARNING! Obsolete function called. Function ${oldFunc.name} has been deprecated, please use the new ${newFunc.name} function instead!`)
newFunc.apply(this, arguments)
}
wrapper.prototype = newFunc.prototype
return wrapper
}
// this is the function that is the replacement for obsfunc
function newfunc(){
console.log('new called');
}
// this is the function that is being made "obsolete"
function obsfunc(p) {
return obsolete(obsfunc, newfunc)();
}
// when you call the obsolete function it's actually the new function
// that gets triggered and an error is displayed in the console.
obsfunc();
in my case functions have parameters and a return value
this code doesn't support that. There is no official way to "obsolete" a function in the Js spec. TBH this is the correct (now deleted) answer to that question IMO. So you'll need to write your own solution. It's not really clear what your definition of "obsolete" is either? Decorators is a good option
There is no way of "setting" a function as deprecated. You just make a function similar to this:
function function1 () {
obsolete(function1(), "function1", "newFunction1")
}
I realize this might not be super useful, but if you are working with babel you might want to look into es7 decorators, they are meant exactly for use-cases like this one. although they still have no native browser support, hence Babel
function obsolete(newFuncGetter)
{
return (prototype, key, descriptor) => {
descriptor.value = (...args) => {
const newFunc = newFuncGetter();
console.warn(`'${key}' has been deprecated, please use '${newFunc.name}' instead!`);
return newFunc(...args);
};
return descriptor;
};
}
class Foo
{
static newfunc()
{
return 'new called';
}
#obsolete(() => Bar.newfunc)
static obsfunc()
{
return 'old value';
}
}
console.log(Bar.obsfunc());
I discovered babel only allows decorators on classes and their items, but I believe the actual decorators should also allow separate functions
The usage of your obsolete function is very simple:
All you have to do is to pass the new function to it:
/** #deprecated since version 2.0 */
export const function1 = obsolete(function function1(param1, param2) {
return param1 + param2
}, 'function1', 'sum')
One more minor tweak you could add to your obsolete declaration is a name setting so that the obsolete function will look the same as the original (that would avoid breaking anyone's code - or not anyone's?):
function obsolete(newFunction, oldFnName, newFnName) {
const wrapper = function () {
console.warn(
`Obsolete function called. Function '${oldFnName}' has been deprecated, please use the new '${newFnName}' function instead.`
)
newFunction.apply(this, arguments)
}
wrapper.prototype = newFunction.prototype
Object.defineProperty(wrapper, 'name', {
value: oldName,
enumerable: true,
configurable: true
}
return wrapper
}
However, if you just rename a method, you can avoid redefinition by passing the reference of the new one to the obsolete:
export function sum(param1, param2) {
return param1 + param2
}
/** #deprecated since version 2.0 */
export const function1 = obsolete(sum, 'function1', 'sum')
With the name fix from above, this will even rename your deprecated version of the new function to its old name, without manually creating a wrapper.
Related
I am trying to make my code shorter and more optimized, and want to make it look clearer.
So far I did this :
function id(a) {
return document.getElementById(a);
}
function cl(a) {
return document.getElementsByClassName(a);
}
function tg(a) {
return document.getElementsByTagName(a);
}
function qs(a) {
return document.querySelector(a);
}
function qa(a) {
return document.querySelectorAll(a);
}
Now I have the possibility to call qs("#myElement"). Now I want to attach a event to the specified element just like qs("#myElement").addEventListener("click", callBack). It works great for me. But when I try to make this :
function ev(e, call) {
return addEventListener(e, callback);
}
And then try to call qs("#init-scrap").ev("click", someFunction) then it pops up the following error :
Uncaught (in promise) TypeError: qs(...).ev is not a function.. I don't know what is the problem, do I have to try method chaining ? or any other way I can resolve this problem.
Note : I don't want to use any libraries or frameworks liek Jquery etc.
If you wish to use syntax qs("#init-scrap").ev("click", someFunction), you need to wrap object returned by querySelector into another object that has ev function.
class jQueryLite {
constructor(el) {
this.el = el;
}
ev(e, callback) {
this.el.addEventListener(e, callback);
return this;
}
}
qs(a) {
return new jQueryLite(document.querySelector(a));
}
It's called Fluent interface, if you wish to look it up.
Just pass the element/nodelist in as the first argument and attached the listener to it.
function ev(el, e, call) {
return el.addEventListener(e, callback);
}
As an alternative, but not something I would recommend, you could add ev as a new Node prototype function:
function qs(selector) {
return document.querySelector(selector);
}
if (!Node.prototype.ev) {
Node.prototype.ev = function(e, cb) {
return this.addEventListener(e, cb);
};
}
qs('button').ev('click', handleClick);
let count = 0;
function handleClick() {
console.log(count++);
}
<button>Count+=1</button>
Note I've only tested this with document.querySelector. You might have to alter the code to work with document.querySelectorAll etc as they don't return single elements.
There is an error in your ev method. It should be
const ev = document.addEventListener.bind(document);
So instead of creating new functions that wrap the original, you can alias the actual function itself.
You should do the same for your other aliases if you want to go with this approach.
const qs = document.querySelector.bind(document);
const qa = document.querySelectorAll.bind(document);
My final word of advise would be to not alias these methods at all. The abbreviated method names hurt the readability of your code. Readability almost always trumps brevity as it comes to code.
I looked into the previous answers as an inspiration and created my take on it.
Core
const $ = (selector, base = document) => {
return base.querySelector(selector);
};
Node.prototype.on = function(type, listener) {
return this.addEventListener(type, listener);
};
It supports a base value in case you have another element than document but it's optional.
I like $ and on so that's what I use, just like jQuery.
Call it like below
$('button').on('click', (e) => {
console.log(e.currentTarget);
});
I've been trying to set up some .d.ts definitions for a JavaScript project to add auto-completion to Intellij IDEA.
An example of the JavaScript code I'm trying to define is:
var testObj = {
tests: function (it) {
it("Should...", function (screen) {
})
}
};
where the 'screen' callback parameter should have auto-completion.
I've been successful in doing this by adding some JSDoc comments that link to the .d.ts interfaces:
var testObj = {
/** #param {function(*,function(TestNamespace.IScreen))} it */
tests: function (it) {
it("Should...", function (screen) {
})
}
};
and
var testObj = {
tests: function (it) {
it("Should...",
/** #param {TestNamespace.IIt} it */
function (screen) {
})
}
};
The problem with the first solution being that it looks overly complicated and I'm sure it can be simplified with something like /** #param {TestNamespace.IIt} it */ but when linking to this interface the 'screen' object loses all relevant auto-completion..
The problem with the second solution is that I would have to repeat the comment on every 'it' block that gets created.
So what I'm wanting is to be able to use the first solution but without the long, complicated JSDoc comment but instead using a shorter JSDoc comment that links straight to the TestNamespace.IIt interface. I'm thinking that I've made a mistake with the IIt interface definition but I can't seem to figure out what I've done wrong. Hopefully what I want is possible. Any help would be greatly appreciated!
A simplified version of the current .d.ts file I have is:
export = TestNamespace;
declare namespace TestNamespace {
export interface IIt {
(description: string, callback: (screen: IScreen) => void): void
}
export interface IScreen {
doSomething(): void
}
}
You can specify the interface as type parameter and not use jsdoc at all:
var testObj = {
tests: function(it) {
it('Should...', function(screen: TestNamespace.IScreen) {});
}
};
I'm trying to mock an ES6 class with a constructor that receives parameters, and then mock different class functions on the class to continue with testing, using Jest.
Problem is I can't find any documents on how to approach this problem. I've already seen this post, but it doesn't resolve my problem, because the OP in fact didn't even need to mock the class! The other answer in that post also doesn't elaborate at all, doesn't point to any documentation online and will not lead to reproduceable knowledge, since it's just a block of code.
So say I have the following class:
//socket.js;
module.exports = class Socket extends EventEmitter {
constructor(id, password) {
super();
this.id = id;
this.password = password;
this.state = constants.socket.INITIALIZING;
}
connect() {
// Well this connects and so on...
}
};
//__tests__/socket.js
jest.mock('./../socket');
const Socket = require('./../socket');
const socket = new Socket(1, 'password');
expect(Socket).toHaveBeenCalledTimes(1);
socket.connect()
expect(Socket.mock.calls[0][1]).toBe(1);
expect(Socket.mock.calls[0][2]).toBe('password');
As obvious, the way I'm trying to mock Socket and the class function connect on it is wrong, but I can't find the right way to do so.
Please explain, in your answer, the logical steps you make to mock this and why each of them is necessary + provide external links to Jest official docs if possible!
Thanks for the help!
Update:
All this info and more has now been added to the Jest docs in a new guide, "ES6 Class Mocks."
Full disclosure: I wrote it. :-)
The key to mocking ES6 classes is knowing that an ES6 class is a function. Therefore, the mock must also be a function.
Call jest.mock('./mocked-class.js');, and also import './mocked-class.js'.
For any class methods you want to track calls to, create a variable that points to a mock function, like this: const mockedMethod = jest.fn();. Use those in the next step.
Call MockedClass.mockImplementation(). Pass in an arrow function that returns an object containing any mocked methods, each set to its own mock function (created in step 2).
The same thing can be done using manual mocks (__mocks__ folder) to mock ES6 classes. In this case, the exported mock is created by calling jest.fn().mockImplementation(), with the same argument described in (3) above. This creates a mock function. In this case, you'll also need to export any mocked methods you want to spy on.
The same thing can be done by calling jest.mock('mocked-class.js', factoryFunction), where factoryFunction is again the same argument passed in 3 and 4 above.
An example is worth a thousand words, so here's the code.
Also, there's a repo demonstrating all of this, here:
https://github.com/jonathan-stone/jest-es6-classes-demo/tree/mocks-working
First, for your code
if you were to add the following setup code, your tests should pass:
const connectMock = jest.fn(); // Lets you check if `connect()` was called, if you want
Socket.mockImplementation(() => {
return {
connect: connectMock
};
});
(Note, in your code: Socket.mock.calls[0][1] should be [0][0], and [0][2] should be [0][1]. )
Next, a contrived example
with some explanation inline.
mocked-class.js. Note, this code is never called during the test.
export default class MockedClass {
constructor() {
console.log('Constructed');
}
mockedMethod() {
console.log('Called mockedMethod');
}
}
mocked-class-consumer.js. This class creates an object using the mocked class. We want it to create a mocked version instead of the real thing.
import MockedClass from './mocked-class';
export default class MockedClassConsumer {
constructor() {
this.mockedClassInstance = new MockedClass('yo');
this.mockedClassInstance.mockedMethod('bro');
}
}
mocked-class-consumer.test.js - the test:
import MockedClassConsumer from './mocked-class-consumer';
import MockedClass from './mocked-class';
jest.mock('./mocked-class'); // Mocks the function that creates the class; replaces it with a function that returns undefined.
// console.log(MockedClass()); // logs 'undefined'
let mockedClassConsumer;
const mockedMethodImpl = jest.fn();
beforeAll(() => {
MockedClass.mockImplementation(() => {
// Replace the class-creation method with this mock version.
return {
mockedMethod: mockedMethodImpl // Populate the method with a reference to a mock created with jest.fn().
};
});
});
beforeEach(() => {
MockedClass.mockClear();
mockedMethodImpl.mockClear();
});
it('The MockedClassConsumer instance can be created', () => {
const mockedClassConsumer = new MockedClassConsumer();
// console.log(MockedClass()); // logs a jest-created object with a mockedMethod: property, because the mockImplementation has been set now.
expect(mockedClassConsumer).toBeTruthy();
});
it('We can check if the consumer called the class constructor', () => {
expect(MockedClass).not.toHaveBeenCalled(); // Ensure our mockClear() is clearing out previous calls to the constructor
const mockedClassConsumer = new MockedClassConsumer();
expect(MockedClass).toHaveBeenCalled(); // Constructor has been called
expect(MockedClass.mock.calls[0][0]).toEqual('yo'); // ... with the string 'yo'
});
it('We can check if the consumer called a method on the class instance', () => {
const mockedClassConsumer = new MockedClassConsumer();
expect(mockedMethodImpl).toHaveBeenCalledWith('bro');
// Checking for method call using the stored reference to the mock function
// It would be nice if there were a way to do this directly from MockedClass.mock
});
For me this kind of Replacing Real Class with mocked one worked.
// Content of real.test.ts
jest.mock("../RealClass", () => {
const mockedModule = jest.requireActual(
"../test/__mocks__/RealClass"
);
return {
...mockedModule,
};
});
var codeTest = require("../real");
it("test-real", async () => {
let result = await codeTest.handler();
expect(result).toMatch(/mocked.thing/);
});
// Content of real.ts
import {RealClass} from "../RealClass";
export const handler = {
let rc = new RealClass({doing:'something'});
return rc.realMethod("myWord");
}
// Content of ../RealClass.ts
export class RealClass {
constructor(something: string) {}
async realMethod(input:string) {
return "The.real.deal "+input;
}
// Content of ../test/__mocks__/RealClass.ts
export class RealClass {
constructor(something: string) {}
async realMethod(input:string) {
return "mocked.thing "+input;
}
Sorry if I misspelled something, but I'm writing it on the fly.
I am trying to create a webpack plugin, that will parse the code for a certain function and replace it with another function, that plugin will also expose the new function as a global.
class someName {
constructor(local, domain, translationFile, options) {
}
apply(compiler) {
// exposing ngt function as a global
compiler.plugin('make', function(compilation, callback) {
var childCompiler = compilation.createChildCompiler('someNameExpose');
childCompiler.apply(new webpack.DefinePlugin({
ngt: function(singular , plural, quantity) {
return quantity == 1 ? singular : plural;
}
}));
childCompiler.runAsChild(callback);
});
// searching for the getValue function
compiler.parser.plugin(`call getValue`, function someNameHandler(expr) {
// create a function to replace getValue with
let results = 'ngt('+ expr.arguments +')';
const dep = new ConstDependency(results, expr.range);
dep.loc = expr.loc;
this.state.current.addDependency(dep);
return true;
});
}
}
module.exports = someName;
update / rephrase
I have an issue here, when compiler.parser.plugin('call getValue', function someNameHandler(expr) {...} block is commented the ngt function exist as a global.
when its not commented, i get an error, ngt is undefined.
commented i mean /**/
I found a workaround for that but its far then idea. right now what I do is I export an anonymous function that does what i want.
You can see the plugin here:
Github
You can override the method based on environment. Let's say you have a method
function a(){
//original defination
}
Now based on the environment, if it's a production you could do something like this
if (environment.production) {
function a(){
//overridden defination
}
}
You can use the webpack-merge plugin, it's very useful to do exactly what do you want.
https://github.com/survivejs/webpack-merge
I write a unit-test for doB function of my module.
I want to stub the function doA without exporting it, I prefer not change the way doB accesses doA.
I understand that it cannot be simply stubed because it isnt in the exported object.
How do I stub doA (sinon or any other tool?)
function doA (value) {
/* do stuff */
}
function doB (value) {
let resultA = doA(value);
if (resultA === 'something') {
/* do some */
} else {
/* do otherwise */
}
}
module.exports = exports = {
doB
}
I did it using rewire too. This is what I came up with
const demographic = rewire('./demographic')
const getDemographicsObject = { getDemographics: demographic.__get__('getDemographics') };
const stubGetDemographics = sinon
.stub(getDemographicsObject, 'getDemographics')
.returns(testScores);
demographic.__set__('getDemographics', stubGetDemographics);
Hope this helps
I've ended up using rewire, I can either just __get__ an internal function from the module and the stub it with sinon or userewire's __with__ utility to invoke a function with replaced internal values
Actually, if you don't care about fetching the original function, but rather just want to stub it, the first part is unnecessary. You can do something like this instead:
function stubPrivateMethod(modulePath, methodName) {
const module = rewire(modulePath);
const stub = sinon.stub();
module.__set__(methodName, stub);
return stub;
}