lets say i have the following interface:
export interface CMSData {
id: number;
url: string;
htmlTag: string;
importJSComponent: string;
componentData: ComponentAttribute[];
}
Then i have a method that returns an array of this object type:
public async GetContent(url: string): Promise<CMSData[]>{
const response = await super.get<ICMSContentData[]>(url, {});
try {
if (response?.parsedBody) {
return this.ProcessResponse(response.parsedBody);
} else {
this.handleHTTPError(new Error("Error"));
return [];
}
} catch (e) {
this.handleHTTPError(e);
return [];
}
}
Then i want to test that this is the case so i write the following test:
import {ContentIOService} from "..";
import {CMSData} from "../IOServices/ContentIOService";
require('es6-promise').polyfill();
require('isomorphic-fetch');
test('Get Content', async () => {
const service = ContentIOService.getInstance();
const data = await service.GetContent("https://1c7207fb14fd3b428c70cc406f0c27d9.m.pipedream.net");
console.log(data)
expect(data).toBeInstanceOf(CMSData[]);
});
However here i get the following error:
'CMSData' only refers to a type, but is being used as a value here.
So how can i test that the data i get back is valid and of the right type?
If the type you're looking for is a call, the .toBeInstanceOf(Class) method accepts a parameter which MUST BE a JavaScript class constructor instead of TS type.
You should let TSC check whether you receive the correct type of data at compile time. Code written inside test suites and test cases is executed at runtime, .toBeInstanceOf(Class) is a runtime check, NOT compiler time.
At runtime, you may want to use expect.objectContaining(object) matches any received object that recursively matches the expected properties.
Related
I have the following classes
export class Init {
constructor(url: URL) {}
getClient(url: URL): Client {
return new Client(url);
}
}
and the client is defined like
export class Client {
constructor(readonly url: URL) {}
foo(s: String): Promise<void> {}
// many other methods
}
and now I am trying to test this like so - with Jasmine
it('foo should be invoked', done => {
const init = new Init('test-url');
spyOn(init.getClient,'foo');
...
}
On the spy definition I get this error
Argument of type 'string' is not assignable to parameter of type 'never'
Why can I resolve this? Init's getClient method returns a Client object. Shouldn't the spy be able to identify this type?
My end result should look like this
it('foo should be invoked', done => {
const init = new Init('test-url');
spyOn(init.getClient,'foo');
expect(initCommand.getClient.foo).toHaveBeenCalledTimes(1);
}
You can only spyOn public methods. I would do this:
// mock `getClient` object however you like (some examples below)
// Return the object/value right away
spyOn(init, 'getClient').and.returnValue({ foo: (s: string) => Promise.resolve(s) });
// call a fake function every time init.getClient is called
spyOn(init, 'getClient').and.callFake((url) => return {});
I have been using the class-validator decorator library to validate dto objects and would like to create a domain whitelist for the #IsEmail decorator. The library includes a #Contains decorator, which can be used for a single string (seed), but I would like to input an array of valid strings instead. Is this possible?
current
#Contains('#mydomain.com')
#IsEmail()
public email: string;
desired
#Contains(['#mydomain, #yourdomain, #wealldomain, #fordomain'])
#IsEmail()
public email: string;
If this is not possible, I would appreciate any direction on how to implement a custom decorator which serves this purpose.
Thanks.
Consider this example:
const Contains = <Domain extends `#${string}`, Domains extends Domain[]>(domains: [...Domains]) =>
(target: Object, propertyKey: string) => {
let value: string;
const getter = () => value
const setter = function (newVal: Domain) {
if (domains.includes(newVal)) {
value = newVal;
}
else {
throw Error(`Domain should be one of ${domains.join()}`)
}
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter
});
}
class Greeter {
#Contains(['#mydomain', '#wealldomain'])
greeting: string;
constructor(message: string) {
this.greeting = message;
}
}
const ok = new Greeter('#mydomain'); // ok
const error = new Greeter('234'); // runtime error
Playground
If you provide invalid email domain it will trigger runtime error. It is up to you how you want to implement validation logic. It might be not necessary a runtime error
I created a Fetch function to consume a JSON API and have defined types for the JSON object. I am confused about how to define the return type for the getCurrentJobAPI function since I do a bunch of .then() afterwards. Is the return value the last .then()? In my code, the last .then() is a setState, so what would the type be for that?
getCurrentJobAPI = (): {} => {
const url: string = `dummy_url&job_id=${this.props.currentJob}`;
return fetch(url, {credentials: 'include'})
.then((response) => {
return response.json();
})
.then((json: CurrentJob) => {
console.log(json);
const location = json.inventoryJob.location;
const ref_note = json.inventoryJob.note;
const id = json.inventoryJob.id;
const models = json.inventoryJobDetails.map((j) => {
return Object.assign({}, {
code: j.code,
qty: j.qty
})
});
this.setState({ currentCodes: models, location: location, ref_note: ref_note, id: id})
return json
})
.then((json: CurrentJob) => {
const barcodes = json.inventoryJob.history;
if (barcodes.length > 0) {
this.setState({apiBarcodes: barcodes})
}
this.calculateRows();
this.insertApiBarcodes();
this.setState({ initialLoad: true });
})
};
UPDATE:
Although I understand that I am supposed to define Promise<type> as the return value of getCurrentJobAPI (see Gilad's answer and comments), I am still unsure why I can't write Promise<CurrentJob> if the Fetch resolves as the JSON response.
[I have condensed my .then() statements per loganfsmyth's recommondation.]
Here are the type definitions for CurrentJob:
type Job = {
user_id: number,
status: 'open' | 'closed',
location: 'string',
history: {[number]: string}[],
note: string,
} & CommonCurrentJob;
type JobDetails = {
iaj_id: number,
code: number,
} & CommonCurrentJob;
type CommonCurrentJob = {
id: number,
qty: number,
qty_changed: number,
created_at: string,
updated_at: string
}
So first off, a disclaimer, I am a TypeScript user but I find that this question is actually applicable to both languages and has the same answer.
I created a Fetch function to consume a JSON API and have defined types for the JSON object. I am confused about how to define the return type for the getCurrentJobAPI function since I do a bunch of .then() afterwards. Is the return value the last .then()? In my code, the last .then() is a setState, so what would the type be for that?
TL;DR: Promise<void> (see note). As you suspect, this is in fact the return type of the last top-level .then in the promise chain.
Now lets dig a bit deeper
Here is your example, reworked very slightly to leverage type inference instead of annotating callback parameters that are declared as any by their receivers.
As an aside, these callback parameter annotations amount to unsafe implicit casts, or type assertions as we call them in TypeScript, and they lie about the shape of the code. They look like this
declare function takesFn(fn: (args: any) => any): void;
So I have minimized these since they form a subtle trap
// #flow
import React from 'react';
type CurrentJob = {
inventoryJob: Job,
inventoryJobDetails: JobDetails[]
}
export default class A extends React.Component<{currentJob:JobDetails}, any> {
getCurrentJobAPI: () => Promise<void> = () => {
const url = `dummy_url&job_id=${String(this.props.currentJob)}`;
return fetch(url, {credentials: 'include'})
.then(response => {
return (response : {json(): any}).json();
}) // --> Promise<any>
.then(json => {
const currentJob = (json: CurrentJob); // make the assumption explicit.
console.log(currentJob);
const {location, id, note: ref_note} = currentJob.inventoryJob;
const currentCodes = currentJob.inventoryJobDetails
.map(({code, qty}) => ({
code,
qty
}));
this.setState({currentCodes, location, ref_note, id});
return currentJob;
}) // --> Promise<CurrentJob>
.then(currentJob => {
const apiBarcodes = currentJob.inventoryJob.history;
if (apiBarcodes.length > 0) {
this.setState({apiBarcodes});
}
this.setState({initialLoad: true});
}); // --> Promise<void>
};
}
So I am making assertions about the promises in each then call above but those assertions are all validated by type inference with the exception of the initial type cast on the response value.
As further evidence, if we remove the type declaration from the getCurrentJobAPI property of A, flow will infer that its type is in fact Promise<void>.
Bonus: simplifying with async/await. I've used several ESNext features above to shorten the code and make it a bit more pleasant, but we can leverage a specific feature, async/await to make it easier to understand control flow and types in Promise based code.
Consider this revision.
// #flow
import React from 'react';
type CurrentJob = {
inventoryJob: Job,
inventoryJobDetails: JobDetails[]
}
export default class A extends React.Component<{currentJob:JobDetails}, any> {
getCurrentJobAPI = async () => {
const url = `dummy_url&job_id=${String(this.props.currentJob)}`;
const response = await fetch(url, {credentials: 'include'});
const json = await response.json();
const currentJob = (json: CurrentJob); // make the assumption explicit.
console.log(currentJob);
const {location, id, note: ref_note} = currentJob.inventoryJob;
const currentCodes = currentJob.inventoryJobDetails.map(({code, qty}) => ({
code,
qty
}));
this.setState({currentCodes, location, ref_note, id});
const apiBarcodes = currentJob.inventoryJob.history;
if (apiBarcodes.length > 0) {
this.setState({apiBarcodes});
}
this.setState({initialLoad: true});
};
}
Clearly, this is a void function. It has no return statements. However, as an async function, it inherently returns a Promise, just as it did when written as an explicit Promise chain.
Note: void is a construct that has been found useful in Flow and TypeScript to represent the semantic intent of function that do not return values but in reality such functions actually return undefined because, well, this is JavaScript. Flow does not seem to recognize undefined as a type, but under TypeScript, the function could equally be annotated as returning Promise<undefined>. Irregardless, Promise<void> is preferable thanks to the clarity of intent it provides.
Remarks: I worked through this using a combination of https://flow.org/try and the flow binary for Windows. The experience on Windows is really terrible and hopefully it will improve.
When chaining then's, the result will always be a promise.
When calling then, the return value is another promise, otherwise chaining then's wouldn't have been possible.
You can see that easily by using console.log() surrounding the entire chain.
I am trying to create an API on NodeJS with typescript
I have the following interfaces :
export interface ISqlResonse<T> {
success?: string
error?: string
data: Array<T> //Nothing | array of object when are db operations
}
export interface IApiResponse<T> {
status: 'error' | 'success'
message: string
data: Array<T>
}
Each api call call a function that call an generic class name DB that select/insert/update/delate data from an database
For example the update function look like :
async updateData(input: IUpdateParam) : Promise<ISqlResonse<object>> {
...
...
}
API function call DB and look like :
async update(req): Promise<IApiResponse<IAccessPointsTableStructure>> {
let data = req.body ;
let updateObj = {
data ,
table: 'accessPoints',
excludeColumns: 'loggedUserId',
additionalColumns: { modifiedBy: '1', modifiedAt: crtDate },
validationRules,
where: `id=${data.id}`,
returningData: true
}
let sqlResults = await db.updateData(updateObj) ; // !!!
if(typeof sqlResults.error==="string") {
logger.log('error','Error on updating Access Points!',{sql: db.getQuery(), error: sqlResults.error});
return({status:'error',message: 'Error on updating Access Points!',data: sqlResults.data});
}
logger.log('success', 'Access Points data updated with success!');
return({status: 'error', message: 'Access Points data updated with success!', data: sqlResults.data})
}
My question is : how can I call the function db.updateData() and tell this function that I want to receive in data from ISqlResponse an array with objects like interface IAccessPointsTableStructure.
With other words i want to control the returning type of function. I teste several times with different approaches . (Replace wit in db.updateData(...) <..>...
Thank you in advice.
You haven't included the definition of IUpdateParam, but I will assume that its table property is what decides the type of thing updateData() returns. Everywhere I've commented "guess" is just for example; you should change them to fit your use cases.
You should be able to modify the signature for the updateData() to reflect the relationship between the type of IUpdateParam passed in and the type of Promise<ISqlResponse<{}>> returned. Here's one way to do it, using generics (you could use overloads instead). First, declare a type to represent the mapping between the table names and the data type for each table. For example:
export type TableNameToTypeMapping = {
accessPoints: IAccessPointsTableStructure,
otherThings: IOtherThingsTableStructure, // guess
// ...
}
Now, you can change the definition of IUpdateParam so that it only accepts the right values for table:
export interface IUpdateParam<K extends keyof TableNameToTypeMapping> {
data: any, // guess
table: K,
excludeColumns: string, // guess
additionalColumns: any, // guess
validationRules: any, // guess
where: string // guess
}
So an IUpdateParam<'accessPoints'> object is meant to deal with the accessPoints table, and it is different from an IUpdateParam<'otherThings'> object.
Now the signature for updateData() can be changed to:
async updateData<K extends keyof TableNameToTypeMapping>(
input: IUpdateParam<K>
): Promise<ISqlResonse<TableNameToTypeMapping[K]>> {
// implement this! The implementation is likely not
// type-safe unless you use runtime type guards
}
This means if you call updateData with a parameter of type IUpdateParam<'accessPoints'>, you will get back a Promise<ISqlResponse<TableNameToTypeMapping['accessPoints']>>. But TableNameToTypeMapping['accessPoints'] is just IAccessPointsTableStructure, so you are getting back a Promise<ISqlResponse<IAccessPointsTableStructure>> as desired.
Note that the object literal updateObj will have its table property inferred as type string, which is too wide. To make sure the call to updateData() works as desired, you will either need to assert that the updateObj.table property is of literal type 'accessPoints', like this:
let updateObj = {
data,
table: 'accessPoints' as 'accessPoints', // assertion
excludeColumns: 'loggedUserId',
additionalColumns: { modifiedBy: '1', modifiedAt: crtDate },
validationRules,
where: `id=${data.id}`,
returningData: true
}
or you will need to declare that updateObj is of type IUpdateParam<'accessPoints'>, like this:
// type declaration
let updateObj:IUpdateParam<'accessPoints'> = {
data ,
table: 'accessPoints',
excludeColumns: 'loggedUserId',
additionalColumns: { modifiedBy: '1', modifiedAt: crtDate },
validationRules,
where: `id=${data.id}`,
returningData: true
}
Either way should work.
Hope that helps; good luck!
I am trying to define an API using TypeScript such that it can work like this:
// Create new user (Working)
var user : IUser = new Api.User({ firstName: "John", lastName: "Smith" });
// Delete existing user (Working)
Api.User.Delete(1);
// Load existing user (Unsure how to implement)
var user = Api.User(123);
My TypeScript:
module Api
{
export class User
{
constructor(id : number)
constructor(user : IUser)
constructor(user? : any)
{
// ...
}
static Delete(id : number) {}
}
}
I am not sure how to have a static method Api.User() i.e. not use new. I don't know what to call this type of construct, which makes it difficult to research. :(
I did try adding an unnamed static to the User class, but this isn't right.
static (id : number)
{
// ...
}
Option 1: export a function directly on the Api module
You can export a function on the Api module to retrieve/create a User instance:
module Api
{
export class User
{
}
export function GetUser(id: number):User {
return new User();
}
// or a slightly different syntax (which generates different JavaScript):
export var Delete = (id: number) => {
};
}
You can't have the class named User and the function also be User, so I've changed it to GetUser in the example.
You could then call:
Api.GetUser(1234)
or
Api.Delete(1234);
Option 2: Using an interface
You could also approach this by using an interface if you wanted to limit the ability of
calling code from being able to instantiate instances of the inner class by using an interface instead. Below I've created a simple ISuperUser interface and and implementation of the class called SuperUserImpl. As the SuperUserImpl isn't exported, it's not publicly creatable. This is nice in that you could use simple Api.SuperUser(2345) to return new instances of a class that implements the ISuperUser interface.
module Api {
export interface ISuperUser {
id: Number;
name: String;
}
class SuperUserImpl implements ISuperUser
{
constructor(public id: Number) {
}
public name: String;
}
export var SuperUser = (id:Number):ISuperUser => {
return new SuperUserImpl(id);
}
}
var su : Api.ISuperUser = Api.SuperUser(5432);
alert(su.id);
Option 3: JavaScript and instanceof
There's a trick that is often used in JavaScript class constructor wherein the function/constructor for a new object checks to see whether it is of the right type (was the function called or created), and if not created, returns a new instance:
if (!(this instanceof User)) {
return new User(id);
}
While correct, TypeScript when trying to call a constructor with that will cause a compiler warning. This will work, but with the compiler warning:
constructor(id: Number) {
if (!(this instanceof User)) {
return new User(id);
}
}
And later calling:
var u: Api.User = Api.User(543);
A compiler warning suggests, "did you forget to use 'new'?" It does produce valid JavaScript, just with a warning. I'd probably go with the static-like method approach to avoid the TypeScript compiler warning.