I'm writing the library in typescript and have to keep some api
_(1).seconds()
the thing is that _ is a module, and in the previous implementation was like that
module.exports = valueFunction;
module.exports.seconds = seconds;
is it possible to implement the same in typescript ?
Here's one way to consider breaking it up, and have code-completion/intellisense work. There are a few options that would be closer to the original JavaScript implementation, however, getting code-completion to work can be a bit challenging.
The primary function _ is exported, and returns an exported class called Chained. It's in this class, where the functions that hang from the return value of _ would exist.
In the implementation file (sample.ts):
export class Chained {
constructor(private val: number) {
}
seconds(): number {
return this.val / 1000;
}
}
export function _(val: number): Chained {
return new Chained(val);
}
And then in use:
/// <reference path="sample.ts" />
import sample = require('./sample');
// create a simple alias for the exported _ function:
import _ = sample._;
var val = _(5000).seconds();
console.log(val);
The output would be 5 as seconds divides the original number by 1000.
If you needed the function to be available like:
_.seconds
as well as:
_().seconds()
Your options become more limited as while TypeScript supports extending a Function instance with properties, intellisense doesn't work:
// this won't work well:
export function _(val:number) : Chained {
return new Chained(val);
}
_["seconds"] = (val:number) : number => {
return val / 1000;
}
Related
Sometimes when I update the snapshots I got an Attribute ngContext and for fix this problem I've to clean and install my node_modules to "fix" this issue.
I've to do this every time that I need to update a snapshot. I've already searched on multiple solutions and nothing worked.
snapshotSerializers: \[
'jest-preset-angular/build/serializers/no-ng-attributes',
'jest-preset-angular/build/serializers/ng-snapshot',
'jest-preset-angular/build/serializers/html-comment',
\],
Can someone help me with this, please?
Here is an image
I've updated the jest versions and also the jest-present-angular too but didn't work.
I just want to have a solution that does not makes me clean install the node_modules every time
This is indeed annoying especially because it tends to change after upgrading angular version. My snapshots are now failing as well because of this difference :-/.
- __ngContext__={[Function LRootView]}
+ __ngContext__="0"
So, having look at the jest configuration, the snapshot serializers are being loaded from 'jest-preset-angular' module.
The relevant plugin here is 'jest-preset-angular/build/serializers/ng-snapshot'. Now, they are two ways what to do to get rid of __ngContext__.
replace the plugin entirely by a modified copy
Create a copy of that file in the same directory and adapt it accordingly (line https://github.com/thymikee/jest-preset-angular/blob/40b769b8eba0b82913827793b6d9fe06d41808d9/src/serializers/ng-snapshot.ts#L69):
const attributes = Object.keys(componentInstance).filter(key => key !== '__ngContext__');
Adapt the configuration:
snapshotSerializers: [
'jest-preset-angular/build/serializers/no-ng-attributes',
'./custom-snapshot-serializer.ts',
'jest-preset-angular/build/serializers/html-comment',
],
The disadvantage of this solution is that you have to maintain the plugin although only one line has been changed.
replace the plugin by a wrapper (preferred solution)
This creates just a wrapper for the original implementation. The idea is to remove __ngContext__ before it moves on down the plugin chain. However, the logic of the original plugin is used for the fixture serialization.
import type { ComponentRef, DebugNode, Type, ɵCssSelectorList } from '#angular/core';
import type { ComponentFixture } from '#angular/core/testing';
import type { Colors } from 'pretty-format';
import { test as origTest, print as origPrint } from 'jest-preset-angular/build/serializers/ng-snapshot';
/**
* The follow interfaces are customized heavily inspired by #angular/core/core.d.ts
*/
interface ComponentDef {
selectors: ɵCssSelectorList;
}
interface IvyComponentType extends Type<unknown> {
ɵcmp: ComponentDef;
}
interface NgComponentRef extends ComponentRef<unknown> {
componentType: IvyComponentType;
_elDef: any; // eslint-disable-line #typescript-eslint/no-explicit-any
_view: any; // eslint-disable-line #typescript-eslint/no-explicit-any
}
interface NgComponentFixture extends ComponentFixture<unknown> {
componentRef: NgComponentRef;
// eslint-disable-next-line #typescript-eslint/no-explicit-any
componentInstance: Record<string, any>;
}
/**
* The following types haven't been exported by jest so temporarily we copy typings from 'pretty-format'
*/
interface PluginOptions {
edgeSpacing: string;
min: boolean;
spacing: string;
}
type Indent = (indentSpaces: string) => string;
type Printer = (elementToSerialize: unknown) => string;
export const print = (fixture: any, print: Printer, indent: Indent, opts: PluginOptions, colors: Colors): any => {
const componentInstance = (fixture as NgComponentFixture).componentInstance;
const instance = { ...componentInstance };
delete instance.__ngContext__;
const modifiedFixture = { ...fixture, componentInstance: { ...instance } };
return origPrint(modifiedFixture, print, indent, opts, colors);
};
// eslint-disable-next-line #typescript-eslint/no-explicit-any, #typescript-eslint/explicit-module-boundary-types
export const test = (val: any): boolean => {
return origTest(val);
};
The configuration is adapted the same way as before.
I want to use a function, which is inside another function (function2) in another js file (2nd.js).
My current code looks something like this:
1st.js
module.exports = {
function1(){
function2(){
//...
}
}
}
2nd.js
const { function2 } = require("1st.js")
function2()
This sadly doesn't work and I have no idea how to solve this problem.
You can work with class approach or some older syntax. Any of these approaches results in the same thing, after all, the class syntax in JavaScript is just a thing for programmers, and does not affect the code at all. It is called syntax sugar.
See in the example below how to solve your problem without classes:
Export a object "container" with your functions:
Just as you referred in your example, 1st.js
export default {
functionOne: () => {
console.log("one");
},
functionTwo: (num) => {
console.log(num);
},
functionThree: ({number}) => {
console.log(number);
},
}
And import it with any name you want
...and so on, 2nd.js
import myFunctions from "../myFunctions";
const { functionOne, functionTwo, functionThree} = myFunctions;
functionOne();
functionTwo("two");
functionThree({number: "three"});
The test is linked to this question here which I raised (& was resolved) a few days ago. My current test is:
// Helpers
function getObjectStructure(runners) {
const backStake = runners.back.stake || expect.any(Number).toBeGreaterThan(0)
const layStake = runners.lay.stake || expect.any(Number).toBeGreaterThan(0)
return {
netProfits: {
back: expect.any(Number).toBeGreaterThan(0),
lay: expect.any(Number).toBeGreaterThan(0)
},
grossProfits: {
back: (runners.back.price - 1) * backStake,
lay: layStake
},
stakes: {
back: backStake,
lay: layStake
}
}
}
// Mock
const funcB = jest.fn(pairs => {
return pairs[0]
})
// Test
test('Should call `funcB` with correct object structure', () => {
const params = JSON.parse(fs.readFileSync(paramsPath, 'utf8'))
const { arb } = params
const result = funcA(75)
expect(result).toBeInstanceOf(Object)
expect(funcB).toHaveBeenCalledWith(
Array(3910).fill(
expect.objectContaining(
getObjectStructure(arb.runners)
)
)
)
})
The object structure of arb.runners is this:
{
"back": {
"stake": 123,
"price": 1.23
},
"lay": {
"stake": 456,
"price": 4.56
}
}
There are many different tests around this function mainly dependent upon the argument that is passed into funcA. For this example, it's 75. There's a different length of array that is passed to funcB dependent upon this parameter. However, it's now also dependent on whether the runners (back and/or lay) have existing stake properties for them. I have a beforeAll in each test which manipulates the arb in the file where I hold the params. Hence, that's why the input for the runners is different every time. An outline of what I'm trying to achieve is:
Measure the array passed into funcB is of correct length
Measure the objects within the array are of the correct structure:
2.1 If there are stakes with the runners, that's fine & the test is straight forward
2.2 If not stakes are with the runners, I need to test that; netProfits, grossProfits, & stakes properties all have positive Numbers
2.2 is the one I'm struggling with. If I try with my attempt below, the test fails with the following error:
TypeError: expect.any(...).toBeGreaterThan is not a function
As with previous question, the problem is that expect.any(Number).toBeGreaterThan(0) is incorrect because expect.any(...) is not an assertion and doesn't have matcher methods. The result of expect.any(...) is just a special value that is recognized by Jest equality matchers. It cannot be used in an expression like (runners.back.price - 1) * backStake.
If the intention is to extend equality matcher with custom behaviour, this is the case for custom matcher. Since spy matchers use built-in equality matcher anyway, spy arguments need to be asserted explicitly with custom matcher.
Otherwise additional restrictions should be asserted manually. It should be:
function getObjectStructure() {
return {
netProfits: {
back: expect.any(Number),
lay: expect.any(Number)
},
grossProfits: {
back: expect.any(Number),
lay: expect.any(Number)
},
stakes: {
back: expect.any(Number),
lay: expect.any(Number)
}
}
}
and
expect(result).toBeInstanceOf(Object)
expect(funcB).toHaveBeenCalledTimes(1);
expect(funcB).toHaveBeenCalledWith(
Array(3910).fill(
expect.objectContaining(
getObjectStructure()
)
)
)
const funcBArg = funcB.mock.calls[0][0];
const nonPositiveNetProfitsBack = funcBArg
.map(({ netProfits: { back } }, i) => [i, back])
.filter(([, val] => !(val > 0))
.map(([i, val] => `${netProfits:back:${i}:${val}`);
expect(nonPositiveNetProfitsBack).toEqual([]);
const nonPositiveNetProfitsLay = ...
Where !(val > 0) is necessary to detect NaN. Without custom matcher failed assertion won't result in meaningful message but an index and nonPositiveNetProfitsBack temporary variable name can give enough feedback to spot the problem. An array can be additionally remapped to contain meaningful values like a string and occupy less space in errors.
I am trying to read a complex document from Firebase in order to do some calculations and then execute a Cloud Function.
Unfortunately, I am unable to read the data in the data types that I would like.
Here is an example:
interface CourseEvent {
coucourseGroupType: string;
date: FirebaseFirestore.Timestamp;
courseGroupTypeFirebaseId: string;
firebaseId: string;
maxParticipants: number;
participants: Participant[];
start: FirebaseFirestore.Timestamp;
waitingList: Participant[];
weekNumber: string;
}
This is where I am using the Cloud Function
import * as functions from "firebase-functions";
import { firestore, Timestamp, FieldValue } from "./setup";
export const registerCustomerFromWaitingList = functions.firestore
.document("CourseEvents/{courseEvent}")
.onUpdate(async (change, context) => {
const currentCourseEvent = change.after.data() as CourseEvent;
const currentMaxParticipants = currentCourseEvent.maxParticipants;
if (typeof currentMaxParticipants === "number") {
console.log(`currentMaxParticipants type is number`);
} else if (typeof currentMaxParticipants === "string") {
console.log(`currentMaxParticipants type is string`);
} else {
console.log(`currentMaxParticipants type is NOT number or string`);
}
}
The console always prints that the currentMaxParticipants is a string. I have a similar problem also with another interface.
I also have experimented with such code
const nu = change.after.data().maxParticipants as number;
But in the end, I am still getting a string.
Any tips?
I found out why I would get always a string for the currentMaxParticipants variable.
In Firestore I did save it as a string. I thought that by using type assertions I would also convert the value. In the end, I did change the value within Firestore to a number and now it is working.
I just read this here:
Essential TypeScript: From Beginner to Pro
CAUTION No type conversion is performed by a type assertion, which only tells the compiler what type it should apply to a value for the
purposes of type checking.
I have this code for deferring the execution of a function
export type DeferredFunction<T> = () => T | PromiseLike<T>;
export class Deferrable<T> {
protected df: DeferredFunction<T>;
constructor(df: DeferredFunction<T>) {
this.df = df;
}
public async execute(): Promise<T> {
return this.df();
}
}
export const defer = <T>(df: DeferredFunction<T>): Deferrable<T> => new Deferrable<T>(df);
That works fine and I can run code like
await defer(() => someFunction('foo', 'bar')).execute();
but I what I want to do is type DeferredFunction in a way that I can specify the inner function's signature but I can't get it working. In generic cases the above works but when I want to limit the arguments such that they are specific to a certain type of function I don't have that kind of control.
For clarity, I want to be able to type the inner function's inputs like (as an example)
export type InnerDeferredFunction<T> = (a: string, b: number, c: SomeObjectType) => T | PromiseLike<T>
Any help would be greatly appreciated!
What "inner function" are you talking about? Is it someFunction? If so then the type of DeferredFunction<T> has no handle on it, since it's a function called by the implementation of DeferredFunction<T>. There's no way in TypeScript to specify "a function whose implementation must call a function of type (x: string, y: number, z: boolean) => string". Implementation details are not part of a function's call signature.
The only way I can imagine to begin to approach this would be for DeferredFunction<T> to accept as a parameter the inner function you want to call, along with the list of arguments to call it with. This might not be what you're looking for, but it's the closest that the type system can represent.
Something like this:
export type InnerDeferredFunction<T, A extends any[]> = (...args: A) => T | PromiseLike<T>;
export type ZeroArgDeferredFunction<T> = InnerDeferredFunction<T, []>
Here I'm keeping A generic but you can specify it to some hardcoded list of arguments. I've renamed your DeferredFunction to ZeroArgDeferredFunction to be explicit that it doesn't need arguments.
But now Deferrable needs to know about T and A:
export class Deferrable<T, A extends any[]> {
protected df: ZeroArgDeferredFunction<T>;
constructor(df: InnerDeferredFunction<T, A>, ...args: A) {
this.df = () => df(...args);
}
public async execute(): Promise<T> {
return this.df();
}
}
And you can see that you have to construct one by passing it the inner function and its arguments, and the ZeroArgDeferredFunction is built inside the constructor and is not passed in.
There are different ways to define defer(). It could be a thin wrapper around new Deferrable the way you had it, or you could imagine splitting it up so that the args come first:
export const defer = <A extends any[]>(...args: A) => <T>(
df: InnerDeferredFunction<T, A>): Deferrable<T, A> => new Deferrable<T, A>(df, ...args);
And then you can test it like this:
function someFunction(x: string, y: string) {
return (x + y).length;
}
function anotherFunction(x: number, y: number) {
return (x * y).toFixed()
}
const deferFooBar = defer('foo', 'bar');
await deferFooBar(someFunction).execute(); // okay
await deferFooBar(anotherFunction); // error! string is not assignable to number
Once you call deferFooBar('foo', 'bar'), the returned value will only accept functions that can be safely called with the arguments foo and 'bar'. That means someFunction will be accepted and anotherFunction will be rejected.
Okay, hope that helps; good luck!
Playground link to code