Sharkitek base: model class and basic types dans behaviors.
This commit is contained in:
parent
9d72c909cb
commit
ff1d55da78
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
# IDEA
|
||||
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
# JS library
|
||||
|
||||
coverage/
|
||||
lib/
|
||||
.parcel-cache/
|
||||
.yarn/
|
||||
.yarnrc*
|
||||
yarn-error.log
|
||||
.pnp*
|
||||
|
||||
yarn.lock
|
9
jest.config.js
Normal file
9
jest.config.js
Normal file
@ -0,0 +1,9 @@
|
||||
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
|
||||
module.exports = {
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "node",
|
||||
|
||||
roots: [
|
||||
"./tests",
|
||||
],
|
||||
};
|
29
package.json
Normal file
29
package.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "core",
|
||||
"version": "1.0.0",
|
||||
"description": "Sharkitek core models library.",
|
||||
"repository": "https://git.madeorsk.com/Sharkitek/core",
|
||||
"author": "Madeorsk <madeorsk@protonmail.com>",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "parcel build",
|
||||
"dev": "parcel watch",
|
||||
"test": "jest"
|
||||
},
|
||||
"main": "lib/index.js",
|
||||
"source": "src/index.ts",
|
||||
"types": "lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"reflect-metadata": "^0.1.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@parcel/packager-ts": "2.6.2",
|
||||
"@parcel/transformer-typescript-types": "2.6.2",
|
||||
"@types/jest": "^28.1.6",
|
||||
"jest": "^28.1.3",
|
||||
"parcel": "^2.6.2",
|
||||
"ts-jest": "^28.0.7",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"packageManager": "yarn@3.2.2"
|
||||
}
|
246
src/Model/Model.ts
Normal file
246
src/Model/Model.ts
Normal file
@ -0,0 +1,246 @@
|
||||
import {Type} from "./Types/Type";
|
||||
import "reflect-metadata";
|
||||
import {ConstructorOf} from "./Types/ModelType";
|
||||
|
||||
/**
|
||||
* Key of Sharkitek property metadata.
|
||||
*/
|
||||
const sharkitekMetadataKey = Symbol("sharkitek");
|
||||
|
||||
/**
|
||||
* Key of Sharkitek model identifier.
|
||||
*/
|
||||
const modelIdentifierMetadataKey = Symbol("modelIdentifier");
|
||||
|
||||
/**
|
||||
* Sharkitek property metadata interface.
|
||||
*/
|
||||
interface SharkitekMetadataInterface
|
||||
{
|
||||
/**
|
||||
* Property type instance.
|
||||
*/
|
||||
type: Type<any, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Property decorator to define a Sharkitek model identifier.
|
||||
*/
|
||||
export function Identifier(obj: Model, propertyName: string): void
|
||||
{
|
||||
// Register the current property as identifier of the current model object.
|
||||
Reflect.defineMetadata(modelIdentifierMetadataKey, propertyName, obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Property decorator for Sharkitek models properties.
|
||||
* @param type - Type of the property.
|
||||
*/
|
||||
export function Property<SerializedType, SharkitekType>(type: Type<SerializedType, SharkitekType>): PropertyDecorator
|
||||
{
|
||||
// Return the decorator function.
|
||||
return (obj: ConstructorOf<Model>, propertyName) => {
|
||||
// Initializing property metadata.
|
||||
const metadata: SharkitekMetadataInterface = {
|
||||
type: type,
|
||||
};
|
||||
// Set property metadata.
|
||||
Reflect.defineMetadata(sharkitekMetadataKey, metadata, obj, propertyName);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* A Sharkitek model.
|
||||
*/
|
||||
export abstract class Model
|
||||
{
|
||||
/**
|
||||
* Get the Sharkitek model identifier.
|
||||
* @private
|
||||
*/
|
||||
private getModelIdentifier(): string
|
||||
{
|
||||
return Reflect.getMetadata(modelIdentifierMetadataKey, this);
|
||||
}
|
||||
/**
|
||||
* Get the Sharkitek metadata of the property.
|
||||
* @param propertyName - The name of the property for which to get metadata.
|
||||
* @private
|
||||
*/
|
||||
private getPropertyMetadata(propertyName: string): SharkitekMetadataInterface
|
||||
{
|
||||
return Reflect.getMetadata(sharkitekMetadataKey, this, propertyName);
|
||||
}
|
||||
/**
|
||||
* Calling a function for a defined property.
|
||||
* @param propertyName - The property for which to check definition.
|
||||
* @param callback - The function called when the property is defined.
|
||||
* @param notProperty - The function called when the property is not defined.
|
||||
* @protected
|
||||
*/
|
||||
protected propertyWithMetadata(propertyName: string, callback: (propertyMetadata: SharkitekMetadataInterface) => void, notProperty: () => void = () => {}): unknown
|
||||
{
|
||||
// Getting the current property metadata.
|
||||
const propertyMetadata = this.getPropertyMetadata(propertyName);
|
||||
if (propertyMetadata)
|
||||
// Metadata are defined, calling the right callback.
|
||||
return callback(propertyMetadata);
|
||||
else
|
||||
// Metadata are not defined, calling the right callback.
|
||||
return notProperty();
|
||||
}
|
||||
/**
|
||||
* Calling a function for each defined property.
|
||||
* @param callback - The function to call.
|
||||
* @protected
|
||||
*/
|
||||
protected forEachModelProperty(callback: (propertyName: string, propertyMetadata: SharkitekMetadataInterface) => unknown): any|void
|
||||
{
|
||||
for (const propertyName of Object.keys(this))
|
||||
{ // For each property, checking that its type is defined and calling the callback with its type.
|
||||
const result = this.propertyWithMetadata(propertyName, (propertyMetadata) => {
|
||||
// If the property is defined, calling the function with the property name and metadata.
|
||||
const result = callback(propertyName, propertyMetadata);
|
||||
|
||||
// If there is a return value, returning it directly (loop is broken).
|
||||
if (typeof result !== "undefined") return result;
|
||||
|
||||
// Update metadata if they have changed.
|
||||
Reflect.defineMetadata(sharkitekMetadataKey, propertyMetadata, this, propertyName);
|
||||
});
|
||||
|
||||
// If there is a return value, returning it directly (loop is broken).
|
||||
if (typeof result !== "undefined") return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The original properties values.
|
||||
* @protected
|
||||
*/
|
||||
protected _originalProperties: Record<string, any> = {};
|
||||
|
||||
/**
|
||||
* The original (serialized) object.
|
||||
* @protected
|
||||
*/
|
||||
protected _originalObject: any = null;
|
||||
|
||||
/**
|
||||
* Determine if the model is new or not.
|
||||
*/
|
||||
isNew(): boolean
|
||||
{
|
||||
return !this._originalObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model is dirty or not.
|
||||
*/
|
||||
isDirty(): boolean
|
||||
{
|
||||
return this.forEachModelProperty((propertyName, propertyMetadata) => (
|
||||
// For each property, checking if it is different.
|
||||
propertyMetadata.type.propertyHasChanged(this._originalProperties[propertyName], (this as any)[propertyName])
|
||||
// There is a difference, we should return false.
|
||||
? true
|
||||
// There is not difference, returning nothing.
|
||||
: undefined
|
||||
)) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get model identifier.
|
||||
*/
|
||||
getIdentifier(): unknown
|
||||
{
|
||||
return (this as any)[this.getModelIdentifier()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current properties values as original values.
|
||||
*/
|
||||
resetDiff()
|
||||
{
|
||||
this.forEachModelProperty((propertyName, propertyMetadata) => {
|
||||
// For each property, set its original value to its current property value.
|
||||
this._originalProperties[propertyName] = (this as any)[propertyName];
|
||||
propertyMetadata.type.resetDiff((this as any)[propertyName]);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Serialize the difference between current model state and original one.
|
||||
*/
|
||||
serializeDiff(): any
|
||||
{
|
||||
// Creating a serialized object.
|
||||
const serializedDiff: any = {};
|
||||
|
||||
this.forEachModelProperty((propertyName, propertyMetadata) => {
|
||||
// For each defined model property, adding it to the serialized object if it has changed.
|
||||
if (this.getModelIdentifier() == propertyName
|
||||
|| propertyMetadata.type.propertyHasChanged(this._originalProperties[propertyName], (this as any)[propertyName]))
|
||||
// Adding the current property to the serialized object if it is the identifier or its value has changed.
|
||||
serializedDiff[propertyName] = propertyMetadata.type.serializeDiff((this as any)[propertyName]);
|
||||
})
|
||||
|
||||
return serializedDiff; // Returning the serialized object.
|
||||
}
|
||||
/**
|
||||
* Get difference between original values and current ones, then reset it.
|
||||
* Similar to call `serializeDiff()` then `resetDiff()`.
|
||||
*/
|
||||
save(): any
|
||||
{
|
||||
// Get the difference.
|
||||
const diff = this.serializeDiff();
|
||||
|
||||
// Once the difference has been gotten, reset it.
|
||||
this.resetDiff();
|
||||
|
||||
return diff; // Return the difference.
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Serialize the model.
|
||||
*/
|
||||
serialize(): void
|
||||
{
|
||||
// Creating a serialized object.
|
||||
const serializedObject: any = {};
|
||||
|
||||
this.forEachModelProperty((propertyName, propertyMetadata) => {
|
||||
// For each defined model property, adding it to the serialized object.
|
||||
serializedObject[propertyName] = propertyMetadata.type.serialize((this as any)[propertyName]);
|
||||
});
|
||||
|
||||
return serializedObject; // Returning the serialized object.
|
||||
}
|
||||
|
||||
/**
|
||||
* Special operations on parse.
|
||||
* @protected
|
||||
*/
|
||||
protected parse(): void
|
||||
{} // Nothing by default. TODO: create a event system to create functions like "beforeDeserialization" or "afterDeserialization".
|
||||
|
||||
/**
|
||||
* Deserialize the model.
|
||||
*/
|
||||
deserialize(serializedObject: any): this
|
||||
{
|
||||
this.forEachModelProperty((propertyName, propertyMetadata) => {
|
||||
// For each defined model property, assigning its deserialized value to the model.
|
||||
(this as any)[propertyName] = propertyMetadata.type.deserialize(serializedObject[propertyName]);
|
||||
});
|
||||
|
||||
// Reset original property values.
|
||||
this.resetDiff();
|
||||
|
||||
this._originalObject = serializedObject; // The model is not a new one, but loaded from a deserialized one.
|
||||
|
||||
return this; // Returning this, after deserialization.
|
||||
}
|
||||
}
|
53
src/Model/Types/ArrayType.ts
Normal file
53
src/Model/Types/ArrayType.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import {Type} from "./Type";
|
||||
|
||||
/**
|
||||
* Type of an array of values.
|
||||
*/
|
||||
export class ArrayType<SerializedValueType, SharkitekValueType> extends Type<SerializedValueType[], SharkitekValueType[]>
|
||||
{
|
||||
/**
|
||||
* Constructs a new array type of Sharkitek model property.
|
||||
* @param valueType - Type of the array values.
|
||||
*/
|
||||
constructor(protected valueType: Type<SerializedValueType, SharkitekValueType>)
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
serialize(value: SharkitekValueType[]): SerializedValueType[]
|
||||
{
|
||||
return value.map((value) => (
|
||||
// Serializing each value of the array.
|
||||
this.valueType.serialize(value)
|
||||
));
|
||||
}
|
||||
|
||||
deserialize(value: SerializedValueType[]): SharkitekValueType[]
|
||||
{
|
||||
return value.map((serializedValue) => (
|
||||
// Deserializing each value of the array.
|
||||
this.valueType.deserialize(serializedValue)
|
||||
));
|
||||
}
|
||||
|
||||
serializeDiff(value: SharkitekValueType[]): any
|
||||
{
|
||||
// Serializing diff of all elements.
|
||||
return value.map((value) => this.valueType.serializeDiff(value));
|
||||
}
|
||||
|
||||
resetDiff(value: SharkitekValueType[]): void
|
||||
{
|
||||
// Reset diff of all elements.
|
||||
value.forEach((value) => this.valueType.resetDiff(value));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of an array of values.
|
||||
* @param valueType - Type of the array values.
|
||||
*/
|
||||
export function SArray<SerializedValueType, SharkitekValueType>(valueType: Type<SerializedValueType, SharkitekValueType>)
|
||||
{
|
||||
return new ArrayType<SerializedValueType, SharkitekValueType>(valueType);
|
||||
}
|
22
src/Model/Types/DecimalType.ts
Normal file
22
src/Model/Types/DecimalType.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import {Type} from "./Type";
|
||||
|
||||
/**
|
||||
* Type of decimal numbers.
|
||||
*/
|
||||
export class DecimalType extends Type<string, number>
|
||||
{
|
||||
deserialize(value: string): number
|
||||
{
|
||||
return parseFloat(value);
|
||||
}
|
||||
|
||||
serialize(value: number): string
|
||||
{
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of decimal numbers;
|
||||
*/
|
||||
export const SDecimal = new DecimalType();
|
55
src/Model/Types/ModelType.ts
Normal file
55
src/Model/Types/ModelType.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import {Type} from "./Type";
|
||||
import {Model} from "../Model";
|
||||
|
||||
/**
|
||||
* Type definition of the constructor of a specific type.
|
||||
*/
|
||||
export type ConstructorOf<T> = { new(): T; }
|
||||
|
||||
/**
|
||||
* Type of a Sharkitek model value.
|
||||
*/
|
||||
export class ModelType<M extends Model> extends Type<any, M>
|
||||
{
|
||||
/**
|
||||
* Constructs a new model type of a Sharkitek model property.
|
||||
* @param modelConstructor - Constructor of the model.
|
||||
*/
|
||||
constructor(protected modelConstructor: ConstructorOf<M>)
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
serialize(value: M): any
|
||||
{
|
||||
// Serializing the given model.
|
||||
return value.serialize();
|
||||
}
|
||||
|
||||
deserialize(value: any): M
|
||||
{
|
||||
// Deserializing the given object in the new model.
|
||||
return (new this.modelConstructor()).deserialize(value);
|
||||
}
|
||||
|
||||
serializeDiff(value: M): any
|
||||
{
|
||||
// Serializing the given model.
|
||||
return value.serializeDiff();
|
||||
}
|
||||
|
||||
resetDiff(value: M): void
|
||||
{
|
||||
// Reset diff of the given model.
|
||||
value.resetDiff();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of a Sharkitek model value.
|
||||
* @param modelConstructor - Constructor of the model.
|
||||
*/
|
||||
export function SModel<M extends Model>(modelConstructor: ConstructorOf<M>)
|
||||
{
|
||||
return new ModelType(modelConstructor);
|
||||
}
|
22
src/Model/Types/NumericType.ts
Normal file
22
src/Model/Types/NumericType.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import {Type} from "./Type";
|
||||
|
||||
/**
|
||||
* Type of any numeric value.
|
||||
*/
|
||||
export class NumericType extends Type<number, number>
|
||||
{
|
||||
deserialize(value: number): number
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
serialize(value: number): number
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of any numeric value.
|
||||
*/
|
||||
export const SNumeric = new NumericType();
|
22
src/Model/Types/StringType.ts
Normal file
22
src/Model/Types/StringType.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import {Type} from "./Type";
|
||||
|
||||
/**
|
||||
* Type of any string value.
|
||||
*/
|
||||
export class StringType extends Type<string, string>
|
||||
{
|
||||
deserialize(value: string): string
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
serialize(value: string): string
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of any string value.
|
||||
*/
|
||||
export const SString = new StringType();
|
55
src/Model/Types/Type.ts
Normal file
55
src/Model/Types/Type.ts
Normal file
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Abstract class of a Sharkitek model property type.
|
||||
*/
|
||||
export abstract class Type<SerializedType, SharkitekType>
|
||||
{
|
||||
/**
|
||||
* Serialize the given value of a Sharkitek model property.
|
||||
* @param value - Value to serialize.
|
||||
*/
|
||||
abstract serialize(value: SharkitekType): SerializedType;
|
||||
|
||||
/**
|
||||
* Deserialize the given value of a serialized Sharkitek model.
|
||||
* @param value - Value to deserialize.
|
||||
*/
|
||||
abstract deserialize(value: SerializedType): SharkitekType;
|
||||
|
||||
/**
|
||||
* Serialize the given value only if it has changed.
|
||||
* @param value - Value to deserialize.
|
||||
*/
|
||||
serializeDiff(value: SharkitekType): SerializedType|null
|
||||
{
|
||||
return this.serialize(value); // By default, nothing changes.
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the difference between the original value and the current one.
|
||||
* @param value - Value for which reset diff data.
|
||||
*/
|
||||
resetDiff(value: SharkitekType): void
|
||||
{
|
||||
// By default, nothing to do.
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the property value has changed.
|
||||
* @param originalValue - Original property value.
|
||||
* @param currentValue - Current property value.
|
||||
*/
|
||||
propertyHasChanged(originalValue: SharkitekType, currentValue: SharkitekType): boolean
|
||||
{
|
||||
return originalValue != currentValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the serialized property value has changed.
|
||||
* @param originalValue - Original serialized property value.
|
||||
* @param currentValue - Current serialized property value.
|
||||
*/
|
||||
serializedPropertyHasChanged(originalValue: SerializedType, currentValue: SerializedType): boolean
|
||||
{
|
||||
return originalValue != currentValue;
|
||||
}
|
||||
}
|
10
src/index.ts
Normal file
10
src/index.ts
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
|
||||
export * from "./Model/Model";
|
||||
|
||||
export * from "./Model/Types/Type";
|
||||
export * from "./Model/Types/ArrayType";
|
||||
export * from "./Model/Types/DecimalType";
|
||||
export * from "./Model/Types/ModelType";
|
||||
export * from "./Model/Types/NumericType";
|
||||
export * from "./Model/Types/StringType";
|
146
tests/Model.test.ts
Normal file
146
tests/Model.test.ts
Normal file
@ -0,0 +1,146 @@
|
||||
import {SArray, SDecimal, SModel, SNumeric, SString, Identifier, Model, Property} from "../src";
|
||||
|
||||
/**
|
||||
* Another test model.
|
||||
*/
|
||||
class Author extends Model
|
||||
{
|
||||
@Property(SString)
|
||||
name: string = undefined;
|
||||
|
||||
@Property(SString)
|
||||
firstName: string = undefined;
|
||||
|
||||
@Property(SString)
|
||||
email: string = undefined;
|
||||
|
||||
constructor(name: string = undefined, firstName: string = undefined, email: string = undefined)
|
||||
{
|
||||
super();
|
||||
|
||||
this.name = name;
|
||||
this.firstName = firstName;
|
||||
this.email = email;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A test model.
|
||||
*/
|
||||
class Article extends Model
|
||||
{
|
||||
@Property(SNumeric)
|
||||
@Identifier
|
||||
id: number = undefined;
|
||||
|
||||
@Property(SString)
|
||||
title: string = undefined;
|
||||
|
||||
@Property(SArray(SModel(Author)))
|
||||
authors: Author[] = [];
|
||||
|
||||
@Property(SString)
|
||||
text: string = undefined;
|
||||
|
||||
@Property(SDecimal)
|
||||
evaluation: number = undefined;
|
||||
}
|
||||
|
||||
it("deserialize", () => {
|
||||
expect((new Article()).deserialize({
|
||||
id: 1,
|
||||
title: "this is a test",
|
||||
authors: [
|
||||
{ name: "DOE", firstName: "John", email: "test@test.test" },
|
||||
{ name: "TEST", firstName: "Another", email: "another@test.test" },
|
||||
],
|
||||
text: "this is a long test.",
|
||||
evaluation: "25.23",
|
||||
}).serialize()).toStrictEqual({
|
||||
id: 1,
|
||||
title: "this is a test",
|
||||
authors: [
|
||||
{ name: "DOE", firstName: "John", email: "test@test.test" },
|
||||
{ name: "TEST", firstName: "Another", email: "another@test.test" },
|
||||
],
|
||||
text: "this is a long test.",
|
||||
evaluation: "25.23",
|
||||
});
|
||||
});
|
||||
|
||||
it("create and check state then serialize", () => {
|
||||
const article = new Article();
|
||||
article.id = 1;
|
||||
article.title = "this is a test";
|
||||
article.authors = [
|
||||
new Author("DOE", "John", "test@test.test"),
|
||||
];
|
||||
article.text = "this is a long test.";
|
||||
article.evaluation = 25.23;
|
||||
|
||||
expect(article.isNew()).toBeTruthy();
|
||||
expect(article.getIdentifier()).toStrictEqual(1);
|
||||
|
||||
expect(article.serialize()).toStrictEqual({
|
||||
id: 1,
|
||||
title: "this is a test",
|
||||
authors: [
|
||||
{ name: "DOE", firstName: "John", email: "test@test.test" },
|
||||
],
|
||||
text: "this is a long test.",
|
||||
evaluation: "25.23",
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("deserialize then save", () => {
|
||||
const article = (new Article()).deserialize({
|
||||
id: 1,
|
||||
title: "this is a test",
|
||||
authors: [
|
||||
{ name: "DOE", firstName: "John", email: "test@test.test" },
|
||||
{ name: "TEST", firstName: "Another", email: "another@test.test" },
|
||||
],
|
||||
text: "this is a long test.",
|
||||
evaluation: "25.23",
|
||||
});
|
||||
|
||||
expect(article.isNew()).toBeFalsy();
|
||||
expect(article.isDirty()).toBeFalsy();
|
||||
expect(article.evaluation).toStrictEqual(25.23);
|
||||
|
||||
article.text = "Modified text.";
|
||||
|
||||
expect(article.isDirty()).toBeTruthy();
|
||||
|
||||
expect(article.save()).toStrictEqual({
|
||||
id: 1,
|
||||
text: "Modified text.",
|
||||
});
|
||||
});
|
||||
|
||||
it("save with modified submodels", () => {
|
||||
const article = (new Article()).deserialize({
|
||||
id: 1,
|
||||
title: "this is a test",
|
||||
authors: [
|
||||
{ name: "DOE", firstName: "John", email: "test@test.test" },
|
||||
{ name: "TEST", firstName: "Another", email: "another@test.test" },
|
||||
],
|
||||
text: "this is a long test.",
|
||||
evaluation: "25.23",
|
||||
});
|
||||
|
||||
article.authors = article.authors.map((author) => {
|
||||
author.name = "TEST";
|
||||
return author;
|
||||
});
|
||||
|
||||
expect(article.save()).toStrictEqual({
|
||||
id: 1,
|
||||
authors: [
|
||||
{ name: "TEST", },
|
||||
{}, //{ name: "TEST", firstName: "Another", email: "another@test.test" },
|
||||
],
|
||||
});
|
||||
});
|
21
tsconfig.json
Normal file
21
tsconfig.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"files": ["src/index.ts"],
|
||||
"compilerOptions": {
|
||||
"outDir": "./lib/",
|
||||
"noImplicitAny": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"module": "ES6",
|
||||
"moduleResolution": "Node",
|
||||
"target": "ES5",
|
||||
|
||||
"lib": [
|
||||
"ESNext",
|
||||
"DOM"
|
||||
]
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user