Compare commits
No commits in common. "main" and "v4.0.0" have entirely different histories.
41 changed files with 1296 additions and 5318 deletions
|
@ -12,5 +12,4 @@ jobs:
|
||||||
with:
|
with:
|
||||||
cache: "yarn"
|
cache: "yarn"
|
||||||
- run: yarn install
|
- run: yarn install
|
||||||
- run: yarn lint
|
|
||||||
- run: yarn coverage
|
- run: yarn coverage
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"useTabs": true,
|
|
||||||
"trailingComma": "all",
|
|
||||||
"bracketSpacing": false
|
|
||||||
}
|
|
204
README.md
204
README.md
|
@ -19,49 +19,39 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img alt="Tests status" src="https://code.zeptotech.net/Sharkitek/Core/badges/workflows/test.yaml/badge.svg?branch=main" />
|
|
||||||
<a href="https://bundlephobia.com/package/@sharkitek/core" target="_blank">
|
|
||||||
<img alt="Bundle size" src="https://badgen.net/bundlephobia/minzip/@sharkitek/core" />
|
|
||||||
</a>
|
|
||||||
<a href="https://www.npmjs.com/package/@sharkitek/core" target="_blank">
|
<a href="https://www.npmjs.com/package/@sharkitek/core" target="_blank">
|
||||||
<img alt="Latest release" src="https://badgen.net/npm/v/@sharkitek/core" />
|
<img alt="Latest release" src="https://code.zeptotech.net/Sharkitek/Core/badges/release.svg" />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://bundlephobia.com/package/@sharkitek/core" target="_blank">
|
<img alt="Tests status" src="https://code.zeptotech.net/Sharkitek/Core/badges/workflows/test.yaml/badge.svg" />
|
||||||
<img alt="Bundle size" src="https://badgen.net/bundlephobia/dependency-count/@sharkitek/core" />
|
|
||||||
</a>
|
|
||||||
<img alt="Latest release" src="https://badgen.net/npm/types/@sharkitek/core" />
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
Sharkitek is a lightweight Javascript / TypeScript library designed to ease development of models.
|
Sharkitek is a lightweight Javascript / TypeScript library designed to ease development of models.
|
||||||
|
|
||||||
```shell
|
|
||||||
yarn add @sharkitek/core
|
|
||||||
```
|
|
||||||
|
|
||||||
With Sharkitek, you define the architecture of your models by specifying their properties and their types.
|
With Sharkitek, you define the architecture of your models by specifying their properties and their types.
|
||||||
Then, you can use the defined methods like `serialize`, `parse`, `patch` or `serializeDiff`.
|
Then, you can use the defined methods like `serialize`, `parse`, `patch` or `serializeDiff`.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class Example {
|
class Example
|
||||||
|
{
|
||||||
|
static model = defineModel({
|
||||||
|
Class: Example,
|
||||||
|
properties: {
|
||||||
|
id: s.property.numeric(),
|
||||||
|
name: s.property.string(),
|
||||||
|
},
|
||||||
|
identifier: "id",
|
||||||
|
});
|
||||||
|
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExampleModel = defineModel({
|
|
||||||
Class: Example,
|
|
||||||
properties: {
|
|
||||||
id: s.property.numeric(),
|
|
||||||
name: s.property.string(),
|
|
||||||
},
|
|
||||||
identifier: "id",
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
**Note**: we usually define our models in a `{ModelName}Model` variable next to the model's class.
|
**Note**: by convention, we define our models in a `model` static variable in the model's class. It is a good way to keep your model declaration near the actual class, and its usage will be more natural.
|
||||||
|
|
||||||
### Model definition
|
### Model definition
|
||||||
|
|
||||||
|
@ -69,35 +59,51 @@ const ExampleModel = defineModel({
|
||||||
/**
|
/**
|
||||||
* A person.
|
* A person.
|
||||||
*/
|
*/
|
||||||
class Person {
|
class Person
|
||||||
|
{
|
||||||
|
static model = defineModel({
|
||||||
|
Class: Person,
|
||||||
|
properties: {
|
||||||
|
id: s.property.numeric(),
|
||||||
|
name: s.property.string(),
|
||||||
|
email: s.property.string(),
|
||||||
|
createdAt: s.property.date(),
|
||||||
|
active: s.property.boolean(),
|
||||||
|
},
|
||||||
|
identifier: "id",
|
||||||
|
});
|
||||||
|
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
active: boolean = true;
|
active: boolean = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A person model manager.
|
|
||||||
*/
|
|
||||||
const PersonModel = defineModel({
|
|
||||||
Class: Person,
|
|
||||||
properties: {
|
|
||||||
id: s.property.numeric(),
|
|
||||||
name: s.property.string(),
|
|
||||||
email: s.property.string(),
|
|
||||||
createdAt: s.property.date(),
|
|
||||||
active: s.property.boolean(),
|
|
||||||
},
|
|
||||||
identifier: "id",
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
/**
|
/**
|
||||||
* An article.
|
* An article.
|
||||||
*/
|
*/
|
||||||
class Article {
|
class Article
|
||||||
|
{
|
||||||
|
static model = defineModel({
|
||||||
|
Class: Article,
|
||||||
|
properties: {
|
||||||
|
id: s.property.numeric(),
|
||||||
|
title: s.property.string(),
|
||||||
|
authors: s.property.array(s.property.model(Person)),
|
||||||
|
text: s.property.string(),
|
||||||
|
evaluation: s.property.decimal(),
|
||||||
|
tags: s.property.array(
|
||||||
|
s.property.object({
|
||||||
|
name: s.property.string(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
},
|
||||||
|
identifier: "id",
|
||||||
|
});
|
||||||
|
|
||||||
id: number;
|
id: number;
|
||||||
title: string;
|
title: string;
|
||||||
authors: Person[] = [];
|
authors: Person[] = [];
|
||||||
|
@ -107,48 +113,26 @@ class Article {
|
||||||
name: string;
|
name: string;
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* An article model manager.
|
|
||||||
*/
|
|
||||||
const ArticleModel = defineModel({
|
|
||||||
Class: Article,
|
|
||||||
properties: {
|
|
||||||
id: s.property.numeric(),
|
|
||||||
title: s.property.string(),
|
|
||||||
authors: s.property.array(s.property.model(PersonModel)),
|
|
||||||
text: s.property.string(),
|
|
||||||
evaluation: s.property.decimal(),
|
|
||||||
tags: s.property.array(
|
|
||||||
s.property.object({
|
|
||||||
name: s.property.string(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
identifier: "id",
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
/**
|
/**
|
||||||
* A model with composite keys.
|
* A model with composite keys.
|
||||||
*/
|
*/
|
||||||
class CompositeKeys {
|
class CompositeKeys
|
||||||
|
{
|
||||||
|
static model = defineModel({
|
||||||
|
Class: CompositeKeys,
|
||||||
|
properties: {
|
||||||
|
id1: s.property.numeric(),
|
||||||
|
id2: s.property.string(),
|
||||||
|
},
|
||||||
|
identifier: ["id1", "id2"],
|
||||||
|
});
|
||||||
|
|
||||||
id1: number;
|
id1: number;
|
||||||
id2: string;
|
id2: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A composite keys model manager.
|
|
||||||
*/
|
|
||||||
const CompositeKeysModel = defineModel({
|
|
||||||
Class: CompositeKeys,
|
|
||||||
properties: {
|
|
||||||
id1: s.property.numeric(),
|
|
||||||
id2: s.property.string(),
|
|
||||||
},
|
|
||||||
identifier: ["id1", "id2"],
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Model functions
|
### Model functions
|
||||||
|
@ -162,14 +146,14 @@ instance.createdAt = new Date();
|
||||||
instance.name = "John Doe";
|
instance.name = "John Doe";
|
||||||
instance.email = "john@doe.test";
|
instance.email = "john@doe.test";
|
||||||
instance.active = true;
|
instance.active = true;
|
||||||
const serialized = PersonModel.model(instance).serialize();
|
const serialized = Person.model.model(instance).serialize();
|
||||||
console.log(serialized); // { id: 1, createdAt: "YYYY-MM-DDTHH:mm:ss.sssZ", name: "John Doe", email: "john@doe.test", active: true }
|
console.log(serialized); // { id: 1, createdAt: "YYYY-MM-DDTHH:mm:ss.sssZ", name: "John Doe", email: "john@doe.test", active: true }
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Deserialization
|
#### Deserialization
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const instance = PersonModel.parse({
|
const instance = Person.model.parse({
|
||||||
id: 1,
|
id: 1,
|
||||||
createdAt: "2011-10-05T14:48:00.000Z",
|
createdAt: "2011-10-05T14:48:00.000Z",
|
||||||
name: "John Doe",
|
name: "John Doe",
|
||||||
|
@ -183,7 +167,7 @@ console.log(instance.createdAt instanceof Date); // true
|
||||||
#### Patch
|
#### Patch
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const instance = PersonModel.parse({
|
const instance = Person.model.parse({
|
||||||
id: 1,
|
id: 1,
|
||||||
createdAt: "2011-10-05T14:48:00.000Z",
|
createdAt: "2011-10-05T14:48:00.000Z",
|
||||||
name: "John Doe",
|
name: "John Doe",
|
||||||
|
@ -194,9 +178,9 @@ const instance = PersonModel.parse({
|
||||||
instance.name = "Johnny";
|
instance.name = "Johnny";
|
||||||
|
|
||||||
// Patch serialized only changed properties and the identifier.
|
// Patch serialized only changed properties and the identifier.
|
||||||
console.log(PersonModel.model(instance).patch()); // { id: 1, name: "Johnny" }
|
console.log(Person.model.model(instance).patch()); // { id: 1, name: "Johnny" }
|
||||||
// If you run it one more time, already patched properties will not be included again.
|
// If you run it one more time, already patched properties will not be included again.
|
||||||
console.log(PersonModel.model(instance).patch()); // { id: 1 }
|
console.log(Person.model.model(instance).patch()); // { id: 1 }
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Identifier
|
#### Identifier
|
||||||
|
@ -205,7 +189,7 @@ console.log(PersonModel.model(instance).patch()); // { id: 1 }
|
||||||
const instance = new CompositeKeys();
|
const instance = new CompositeKeys();
|
||||||
instance.id1 = 5;
|
instance.id1 = 5;
|
||||||
instance.id2 = "foo";
|
instance.id2 = "foo";
|
||||||
const instanceIdentifier = CompositeKeysModel.model(instance).getIdentifier();
|
const instanceIdentifier = CompositeKeys.model.model(instance).getIdentifier();
|
||||||
console.log(instanceIdentifier); // [5, "foo"]
|
console.log(instanceIdentifier); // [5, "foo"]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -224,22 +208,22 @@ Sharkitek defines some basic types by default, in these classes:
|
||||||
- `DateType`: date in the model, ISO formatted date in the serialized object.
|
- `DateType`: date in the model, ISO formatted date in the serialized object.
|
||||||
- `ArrayType`: array in the model, array in the serialized object.
|
- `ArrayType`: array in the model, array in the serialized object.
|
||||||
- `ObjectType`: object in the model, object in the serialized object.
|
- `ObjectType`: object in the model, object in the serialized object.
|
||||||
- `MapType`: map in the model, record object in the serialized object.
|
|
||||||
- `ModelType`: instance of a specific class in the model, object in the serialized object.
|
- `ModelType`: instance of a specific class in the model, object in the serialized object.
|
||||||
|
|
||||||
When you are defining a property of a Sharkitek model, you must provide its type by instantiating one of these classes.
|
When you are defining a property of a Sharkitek model, you must provide its type by instantiating one of these classes.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class Example {
|
class Example
|
||||||
|
{
|
||||||
|
static model = defineModel({
|
||||||
|
Class: Example,
|
||||||
|
properties: {
|
||||||
|
foo: s.property.define(new StringType()),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
foo: string;
|
foo: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExampleModel = defineModel({
|
|
||||||
Class: Example,
|
|
||||||
properties: {
|
|
||||||
foo: s.property.define(new StringType()),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
To ease the use of these classes and reduce read complexity, properties of each type are easily definable with a function for each type.
|
To ease the use of these classes and reduce read complexity, properties of each type are easily definable with a function for each type.
|
||||||
|
@ -251,22 +235,22 @@ To ease the use of these classes and reduce read complexity, properties of each
|
||||||
- `DateType` => `s.property.date`
|
- `DateType` => `s.property.date`
|
||||||
- `ArrayType` => `s.property.array`
|
- `ArrayType` => `s.property.array`
|
||||||
- `ObjectType` => `s.property.object`
|
- `ObjectType` => `s.property.object`
|
||||||
- `MapType` => `s.property.map` or `s.property.stringMap`
|
|
||||||
- `ModelType` => `s.property.model`
|
- `ModelType` => `s.property.model`
|
||||||
|
|
||||||
Type implementers should provide a corresponding function for each defined type. They can even provide multiple functions or constants with predefined parameters. For example, we could define `s.property.stringArray()` which would be similar to `s.property.array(s.property.string())`.
|
Type implementers should provide a corresponding function for each defined type. They can even provide multiple functions or constants with predefined parameters. For example, we could define `s.property.stringArray()` which would be similar to `s.property.array(s.property.string())`.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class Example {
|
class Example
|
||||||
|
{
|
||||||
|
static model = defineModel({
|
||||||
|
Class: Example,
|
||||||
|
properties: {
|
||||||
|
foo: s.property.string(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
foo: string;
|
foo: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExampleModel = defineModel({
|
|
||||||
Class: Example,
|
|
||||||
properties: {
|
|
||||||
foo: s.property.string(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Models
|
### Models
|
||||||
|
@ -279,30 +263,6 @@ Get a model class (which has all the sharkitek models' functions) from a model i
|
||||||
const model = definedModel.model(modelInstance);
|
const model = definedModel.model(modelInstance);
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `assign(object)`
|
|
||||||
|
|
||||||
Assign fields from a provided object to the model instance properties. Fields which are not properties of the target model are silently ignored.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const alteredModelInstance = definedModel.model(modelInstance).assign({
|
|
||||||
anyProperty: "foo",
|
|
||||||
anotherOne: true,
|
|
||||||
not_a_property: "will be ignored",
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `from(object)`
|
|
||||||
|
|
||||||
Initialize a model instance and assign the provided fields to its properties. Fields which are not properties of the target model are silently ignored.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const newModelInstance = definedModel.from({
|
|
||||||
anyProperty: "foo",
|
|
||||||
anotherOne: true,
|
|
||||||
not_a_property: "will be ignored",
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `serialize()`
|
#### `serialize()`
|
||||||
|
|
||||||
Serialize the model.
|
Serialize the model.
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
import js from "@eslint/js";
|
|
||||||
import globals from "globals";
|
|
||||||
import tseslint from "typescript-eslint";
|
|
||||||
import {defineConfig, globalIgnores} from "eslint/config";
|
|
||||||
|
|
||||||
export default defineConfig([
|
|
||||||
globalIgnores([".yarn/**", "coverage/**", "lib/**"]),
|
|
||||||
{
|
|
||||||
files: ["**/*.{js,mjs,cjs,ts,mts,cts}"],
|
|
||||||
plugins: {js},
|
|
||||||
extends: ["js/recommended"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ["**/*.{js,mjs,cjs,ts,mts,cts}"],
|
|
||||||
languageOptions: {globals: {...globals.browser, ...globals.node}},
|
|
||||||
},
|
|
||||||
tseslint.configs.recommended,
|
|
||||||
{
|
|
||||||
rules: {
|
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
25
package.json
25
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@sharkitek/core",
|
"name": "@sharkitek/core",
|
||||||
"version": "4.1.0",
|
"version": "4.0.0",
|
||||||
"description": "TypeScript library for well-designed model architectures.",
|
"description": "TypeScript library for well-designed model architectures.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"deserialization",
|
"deserialization",
|
||||||
|
@ -25,9 +25,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"coverage": "vitest run --coverage",
|
"coverage": "vitest run --coverage"
|
||||||
"format": "prettier . --write",
|
|
||||||
"lint": "eslint"
|
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"source": "src/library.ts",
|
"source": "src/library.ts",
|
||||||
|
@ -37,18 +35,13 @@
|
||||||
"lib/**/*"
|
"lib/**/*"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.30.0",
|
"@types/node": "^22.13.14",
|
||||||
"@types/node": "^24.0.3",
|
"@vitest/coverage-v8": "^3.0.9",
|
||||||
"@vitest/coverage-v8": "^3.2.4",
|
|
||||||
"eslint": "^9.30.0",
|
|
||||||
"globals": "^16.2.0",
|
|
||||||
"prettier": "^3.6.0",
|
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.8.2",
|
||||||
"typescript-eslint": "^8.35.0",
|
"vite": "^6.2.3",
|
||||||
"vite": "^7.0.0",
|
"vite-plugin-dts": "^4.5.3",
|
||||||
"vite-plugin-dts": "^4.5.4",
|
"vitest": "^3.0.9"
|
||||||
"vitest": "^3.2.4"
|
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@4.9.2"
|
"packageManager": "yarn@4.6.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
|
||||||
export * from "./sharkitek-error";
|
export * from "./sharkitek-error";
|
||||||
export * from "./type-error";
|
export * from "./type-error";
|
||||||
export * from "./invalid-type-value-error";
|
export * from "./invalid-type-value-error";
|
||||||
|
|
|
@ -4,15 +4,10 @@ import {Type} from "../model/types/type";
|
||||||
/**
|
/**
|
||||||
* A Sharkitek type error when the passed value is invalid.
|
* A Sharkitek type error when the passed value is invalid.
|
||||||
*/
|
*/
|
||||||
export class InvalidTypeValueError<SerializedType, ModelType> extends TypeError<
|
export class InvalidTypeValueError<SerializedType, ModelType> extends TypeError<SerializedType, ModelType>
|
||||||
SerializedType,
|
{
|
||||||
ModelType
|
constructor(public type: Type<SerializedType, ModelType>, public value: any, message?: string)
|
||||||
> {
|
{
|
||||||
constructor(
|
super(type, message ?? `${JSON.stringify(value)} is an invalid value`)
|
||||||
public type: Type<SerializedType, ModelType>,
|
|
||||||
public value: any,
|
|
||||||
message?: string,
|
|
||||||
) {
|
|
||||||
super(type, message ?? `${JSON.stringify(value)} is an invalid value`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
/**
|
/**
|
||||||
* A Sharkitek error.
|
* A Sharkitek error.
|
||||||
*/
|
*/
|
||||||
export class SharkitekError extends Error {}
|
export class SharkitekError extends Error
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
|
@ -4,13 +4,10 @@ import {Type} from "../model/types/type";
|
||||||
/**
|
/**
|
||||||
* A Sharkitek type error.
|
* A Sharkitek type error.
|
||||||
*/
|
*/
|
||||||
export class TypeError<SerializedType, ModelType> extends SharkitekError {
|
export class TypeError<SerializedType, ModelType> extends SharkitekError
|
||||||
constructor(
|
{
|
||||||
public type: Type<SerializedType, ModelType>,
|
constructor(public type: Type<SerializedType, ModelType>, message?: string)
|
||||||
message?: string,
|
{
|
||||||
) {
|
super(`Error in type ${type.constructor.name}${message ? `: ${message}` : ""}`);
|
||||||
super(
|
|
||||||
`Error in type ${type.constructor.name}${message ? `: ${message}` : ""}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import * as s from "./model";
|
import * as s from "./model";
|
||||||
export * from "./model";
|
export * from "./model";
|
||||||
export * from "./errors";
|
export * from "./errors";
|
||||||
export {s};
|
export { s };
|
||||||
export default s;
|
export default s;
|
||||||
|
|
|
@ -1,77 +0,0 @@
|
||||||
import {
|
|
||||||
defineModel,
|
|
||||||
IdentifierDefinition,
|
|
||||||
ModelDefinition,
|
|
||||||
ModelShape,
|
|
||||||
} from "./model";
|
|
||||||
import {ConstructorOf} from "../utils";
|
|
||||||
import {Definition} from "./property-definition";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Model definition builder.
|
|
||||||
*/
|
|
||||||
export class ModelBuilder<
|
|
||||||
T extends object,
|
|
||||||
Shape extends ModelShape<T>,
|
|
||||||
Identifier extends IdentifierDefinition<T, Shape>,
|
|
||||||
> {
|
|
||||||
/**
|
|
||||||
* The built model definition.
|
|
||||||
*/
|
|
||||||
definition: ModelDefinition<T, Shape, Identifier>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define a new property.
|
|
||||||
* @param name The new property name.
|
|
||||||
* @param definition The new property definition.
|
|
||||||
*/
|
|
||||||
property<
|
|
||||||
SerializedType,
|
|
||||||
PropertyName extends Exclude<keyof T, keyof Shape>,
|
|
||||||
PropertyDefinition extends Definition<SerializedType, T[PropertyName]>,
|
|
||||||
>(name: PropertyName, definition: PropertyDefinition) {
|
|
||||||
(this.definition.properties[name] as Definition<unknown, T[typeof name]>) =
|
|
||||||
definition;
|
|
||||||
|
|
||||||
return this as unknown as ModelBuilder<
|
|
||||||
T,
|
|
||||||
Shape & {[k in PropertyName]: PropertyDefinition},
|
|
||||||
Identifier
|
|
||||||
>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the model identifier.
|
|
||||||
* @param identifier The new model identifier.
|
|
||||||
*/
|
|
||||||
identifier<NewIdentifier extends IdentifierDefinition<T, Shape>>(
|
|
||||||
identifier: NewIdentifier,
|
|
||||||
) {
|
|
||||||
(this.definition.identifier as unknown) = identifier;
|
|
||||||
return this as unknown as ModelBuilder<T, Shape, NewIdentifier>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define a model using the current model definition.
|
|
||||||
*/
|
|
||||||
define() {
|
|
||||||
return defineModel(this.definition);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize a model builder for the provided class.
|
|
||||||
* @param Class The class for which to build a model.
|
|
||||||
*/
|
|
||||||
export function newModel<
|
|
||||||
T extends object,
|
|
||||||
Shape extends ModelShape<T> = object,
|
|
||||||
Identifier extends IdentifierDefinition<T, Shape> = never,
|
|
||||||
>(Class: ConstructorOf<T>): ModelBuilder<T, Shape, Identifier> {
|
|
||||||
const builder = new ModelBuilder<T, Shape, Identifier>();
|
|
||||||
builder.definition = {
|
|
||||||
Class,
|
|
||||||
properties: {} as Shape,
|
|
||||||
};
|
|
||||||
return builder;
|
|
||||||
}
|
|
|
@ -1,8 +1,8 @@
|
||||||
export * as property from "./properties";
|
import * as property from "./properties";
|
||||||
|
export {property};
|
||||||
|
|
||||||
export * from "./model";
|
export * from "./model";
|
||||||
export {Definition} from "./property-definition";
|
export {Definition} from "./property-definition";
|
||||||
export {newModel, ModelBuilder} from "./builder";
|
|
||||||
|
|
||||||
export {ArrayType} from "./types/array";
|
export {ArrayType} from "./types/array";
|
||||||
export {BooleanType} from "./types/boolean";
|
export {BooleanType} from "./types/boolean";
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {Definition, UnknownDefinition} from "./property-definition";
|
import {Definition, UnknownDefinition} from "./property-definition";
|
||||||
import {ConstructorOf, Modify} from "../utils";
|
import {ConstructorOf} from "../utils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A model shape.
|
* A model shape.
|
||||||
|
@ -11,10 +11,7 @@ export type ModelShape<T extends object> = Partial<{
|
||||||
/**
|
/**
|
||||||
* Properties values of a model based on its shape.
|
* Properties values of a model based on its shape.
|
||||||
*/
|
*/
|
||||||
export type ModelPropertiesValues<
|
export type ModelPropertiesValues<T extends object, Shape extends ModelShape<T>> = {
|
||||||
T extends object,
|
|
||||||
Shape extends ModelShape<T>,
|
|
||||||
> = {
|
|
||||||
[k in keyof Shape]: Shape[k]["_sharkitek"];
|
[k in keyof Shape]: Shape[k]["_sharkitek"];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -29,39 +26,20 @@ export type SerializedModel<T extends object, Shape extends ModelShape<T>> = {
|
||||||
* This is an experimental serialized model type declaration.
|
* This is an experimental serialized model type declaration.
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
type ExperimentalSerializedModel<T extends object, Shape extends ModelShape<T>>
|
||||||
type ExperimentalSerializedModel<
|
= Omit<ExperimentalSerializedModelBase<T, Shape>, ExperimentalSerializedModelOptionalKeys<T, Shape>>
|
||||||
T extends object,
|
& Pick<Partial<ExperimentalSerializedModelBase<T, Shape>>, ExperimentalSerializedModelOptionalKeys<T, Shape>>;
|
||||||
Shape extends ModelShape<T>,
|
type ExperimentalSerializedModelBase<T extends object, Shape extends ModelShape<T>> = {
|
||||||
> = Omit<
|
|
||||||
ExperimentalSerializedModelBase<T, Shape>,
|
|
||||||
ExperimentalSerializedModelOptionalKeys<T, Shape>
|
|
||||||
> &
|
|
||||||
Pick<
|
|
||||||
Partial<ExperimentalSerializedModelBase<T, Shape>>,
|
|
||||||
ExperimentalSerializedModelOptionalKeys<T, Shape>
|
|
||||||
>;
|
|
||||||
type ExperimentalSerializedModelBase<
|
|
||||||
T extends object,
|
|
||||||
Shape extends ModelShape<T>,
|
|
||||||
> = {
|
|
||||||
[k in keyof Shape]: Shape[k]["_serialized"];
|
[k in keyof Shape]: Shape[k]["_serialized"];
|
||||||
};
|
};
|
||||||
type ExperimentalSerializedModelOptionalKeys<
|
type ExperimentalSerializedModelOptionalKeys<T extends object, Shape extends ModelShape<T>> = {
|
||||||
T extends object,
|
[k in keyof Shape]: Shape[k]["_serialized"] extends undefined ? k : never
|
||||||
Shape extends ModelShape<T>,
|
|
||||||
> = {
|
|
||||||
[k in keyof Shape]: Shape[k]["_serialized"] extends undefined ? k : never;
|
|
||||||
}[keyof Shape];
|
}[keyof Shape];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A sharkitek model instance, with internal model state.
|
* A sharkitek model instance, with internal model state.
|
||||||
*/
|
*/
|
||||||
export type ModelInstance<
|
export type ModelInstance<T extends object, Shape extends ModelShape<T>, Identifier extends IdentifierDefinition<T, Shape>> = T & {
|
||||||
T extends object,
|
|
||||||
Shape extends ModelShape<T>,
|
|
||||||
Identifier extends IdentifierDefinition<T, Shape>,
|
|
||||||
> = T & {
|
|
||||||
/**
|
/**
|
||||||
* The Sharkitek model state.
|
* The Sharkitek model state.
|
||||||
*/
|
*/
|
||||||
|
@ -71,34 +49,19 @@ export type ModelInstance<
|
||||||
/**
|
/**
|
||||||
* Identifier definition type.
|
* Identifier definition type.
|
||||||
*/
|
*/
|
||||||
export type IdentifierDefinition<
|
export type IdentifierDefinition<T extends object, Shape extends ModelShape<T>> = (keyof Shape)|((keyof Shape)[]);
|
||||||
T extends object,
|
|
||||||
Shape extends ModelShape<T>,
|
|
||||||
> = keyof Shape | (keyof Shape)[];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Identifier type.
|
* Identifier type.
|
||||||
*/
|
*/
|
||||||
export type IdentifierType<
|
export type IdentifierType<T extends object, Shape extends ModelShape<T>, Identifier extends IdentifierDefinition<T, Shape>>
|
||||||
T extends object,
|
= Identifier extends keyof Shape ? Shape[Identifier]["_sharkitek"] : { [K in keyof Identifier]: Identifier[K] extends keyof Shape ? Shape[Identifier[K]]["_sharkitek"] : unknown };
|
||||||
Shape extends ModelShape<T>,
|
|
||||||
Identifier extends IdentifierDefinition<T, Shape>,
|
|
||||||
> = Identifier extends keyof Shape
|
|
||||||
? Shape[Identifier]["_sharkitek"]
|
|
||||||
: {
|
|
||||||
[K in keyof Identifier]: Identifier[K] extends keyof Shape
|
|
||||||
? Shape[Identifier[K]]["_sharkitek"]
|
|
||||||
: unknown;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A model definition object.
|
* A model definition object.
|
||||||
*/
|
*/
|
||||||
export interface ModelDefinition<
|
export interface ModelDefinition<T extends object, Shape extends ModelShape<T>, Identifier extends IdentifierDefinition<T, Shape>>
|
||||||
T extends object,
|
{
|
||||||
Shape extends ModelShape<T>,
|
|
||||||
Identifier extends IdentifierDefinition<T, Shape>,
|
|
||||||
> {
|
|
||||||
/**
|
/**
|
||||||
* Model class.
|
* Model class.
|
||||||
*/
|
*/
|
||||||
|
@ -120,7 +83,8 @@ export interface ModelDefinition<
|
||||||
/**
|
/**
|
||||||
* A model property.
|
* A model property.
|
||||||
*/
|
*/
|
||||||
export interface ModelProperty<T extends object, Shape extends ModelShape<T>> {
|
export interface ModelProperty<T extends object, Shape extends ModelShape<T>>
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* Property name.
|
* Property name.
|
||||||
*/
|
*/
|
||||||
|
@ -140,19 +104,13 @@ export interface ModelProperty<T extends object, Shape extends ModelShape<T>> {
|
||||||
/**
|
/**
|
||||||
* Model properties iterator object.
|
* Model properties iterator object.
|
||||||
*/
|
*/
|
||||||
export type ModelProperties<
|
export type ModelProperties<T extends object, Shape extends ModelShape<T>> = ModelProperty<T, Shape>[];
|
||||||
T extends object,
|
|
||||||
Shape extends ModelShape<T>,
|
|
||||||
> = ModelProperty<T, Shape>[];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Sharkitek model state.
|
* A Sharkitek model state.
|
||||||
*/
|
*/
|
||||||
export class Model<
|
export class Model<T extends object, Shape extends ModelShape<T>, Identifier extends IdentifierDefinition<T, Shape>>
|
||||||
T extends object,
|
{
|
||||||
Shape extends ModelShape<T>,
|
|
||||||
Identifier extends IdentifierDefinition<T, Shape>,
|
|
||||||
> {
|
|
||||||
/**
|
/**
|
||||||
* The model manager instance.
|
* The model manager instance.
|
||||||
*/
|
*/
|
||||||
|
@ -186,14 +144,15 @@ export class Model<
|
||||||
/**
|
/**
|
||||||
* The original serialized object, if there is one.
|
* The original serialized object, if there is one.
|
||||||
*/
|
*/
|
||||||
serialized: SerializedModel<T, Shape> | null;
|
serialized: SerializedModel<T, Shape>|null;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a new model state with the defined properties.
|
* Initialize a new model state with the defined properties.
|
||||||
* @param manager The model manager.
|
* @param manager The model manager.
|
||||||
*/
|
*/
|
||||||
constructor(manager: ModelManager<T, Shape, Identifier>) {
|
constructor(manager: ModelManager<T, Shape, Identifier>)
|
||||||
|
{
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
this.definition = manager.definition;
|
this.definition = manager.definition;
|
||||||
this.properties = manager.properties;
|
this.properties = manager.properties;
|
||||||
|
@ -202,10 +161,11 @@ export class Model<
|
||||||
/**
|
/**
|
||||||
* Initialize the Sharkitek model state for a new instance.
|
* Initialize the Sharkitek model state for a new instance.
|
||||||
*/
|
*/
|
||||||
initInstance(): this {
|
initInstance(): this
|
||||||
|
{
|
||||||
return this.fromInstance(
|
return this.fromInstance(
|
||||||
// Initialize a new model instance.
|
// Initialize a new model instance.
|
||||||
new this.definition.Class(),
|
new (this.definition.Class)()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,7 +173,8 @@ export class Model<
|
||||||
* Initialize the Sharkitek model state for the provided instance.
|
* Initialize the Sharkitek model state for the provided instance.
|
||||||
* @param instance The model instance.
|
* @param instance The model instance.
|
||||||
*/
|
*/
|
||||||
fromInstance(instance: T): this {
|
fromInstance(instance: T): this
|
||||||
|
{
|
||||||
// Initialize the sharkitek model instance.
|
// Initialize the sharkitek model instance.
|
||||||
const sharkitekInstance = instance as ModelInstance<T, Shape, Identifier>;
|
const sharkitekInstance = instance as ModelInstance<T, Shape, Identifier>;
|
||||||
|
|
||||||
|
@ -227,8 +188,8 @@ export class Model<
|
||||||
if (originalInstance)
|
if (originalInstance)
|
||||||
// Share the same original values object.
|
// Share the same original values object.
|
||||||
this.original = originalInstance.original;
|
this.original = originalInstance.original;
|
||||||
else {
|
else
|
||||||
// Initialize a new original values object, based on the current values of the instance.
|
{ // Initialize a new original values object, based on the current values of the instance.
|
||||||
this.original = {
|
this.original = {
|
||||||
properties: undefined,
|
properties: undefined,
|
||||||
serialized: null,
|
serialized: null,
|
||||||
|
@ -243,14 +204,14 @@ export class Model<
|
||||||
* Deserialize provided data to a new model instance.
|
* Deserialize provided data to a new model instance.
|
||||||
* @param serialized Serialized model.
|
* @param serialized Serialized model.
|
||||||
*/
|
*/
|
||||||
deserialize(serialized: SerializedModel<T, Shape>): this {
|
deserialize(serialized: SerializedModel<T, Shape>): this
|
||||||
|
{
|
||||||
// Initialize a new model instance.
|
// Initialize a new model instance.
|
||||||
this.initInstance();
|
this.initInstance();
|
||||||
|
|
||||||
for (const property of this.properties) {
|
for (const property of this.properties)
|
||||||
// For each defined model property, assigning its deserialized value.
|
{ // For each defined model property, assigning its deserialized value.
|
||||||
(this.instance[property.name as keyof T] as any) =
|
(this.instance[property.name as keyof T] as any) = property.definition.type.deserialize(serialized[property.name]);
|
||||||
property.definition.type.deserialize(serialized[property.name]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset original property values.
|
// Reset original property values.
|
||||||
|
@ -264,31 +225,29 @@ export class Model<
|
||||||
/**
|
/**
|
||||||
* Get current model instance identifier.
|
* Get current model instance identifier.
|
||||||
*/
|
*/
|
||||||
getIdentifier(): IdentifierType<T, Shape, Identifier> {
|
getIdentifier(): IdentifierType<T, Shape, Identifier>
|
||||||
if (Array.isArray(this.definition.identifier)) {
|
{
|
||||||
// The identifier is composite, make an array of properties values.
|
if (Array.isArray(this.definition.identifier))
|
||||||
return this.definition.identifier.map(
|
{ // The identifier is composite, make an array of properties values.
|
||||||
(identifier) => this.instance?.[identifier as keyof T],
|
return this.definition.identifier.map(identifier => this.instance?.[identifier as keyof T]) as IdentifierType<T, Shape, Identifier>;
|
||||||
) as IdentifierType<T, Shape, Identifier>;
|
}
|
||||||
} else {
|
else
|
||||||
// The identifier is a simple property, get its value.
|
{ // The identifier is a simple property, get its value.
|
||||||
return this.instance?.[
|
return this.instance?.[this.definition.identifier as keyof Shape as keyof T] as IdentifierType<T, Shape, Identifier>;
|
||||||
this.definition.identifier as keyof Shape as keyof T
|
|
||||||
] as IdentifierType<T, Shape, Identifier>;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get current model instance properties.
|
* Get current model instance properties.
|
||||||
*/
|
*/
|
||||||
getInstanceProperties(): ModelPropertiesValues<T, Shape> {
|
getInstanceProperties(): ModelPropertiesValues<T, Shape>
|
||||||
|
{
|
||||||
// Initialize an empty model properties object.
|
// Initialize an empty model properties object.
|
||||||
const instanceProperties: Partial<ModelPropertiesValues<T, Shape>> = {};
|
const instanceProperties: Partial<ModelPropertiesValues<T, Shape>> = {};
|
||||||
|
|
||||||
for (const property of this.properties) {
|
for (const property of this.properties)
|
||||||
// For each defined model property, adding it to the properties object.
|
{ // For each defined model property, adding it to the properties object.
|
||||||
instanceProperties[property.name] =
|
instanceProperties[property.name] = this.instance?.[property.name as keyof T]; // keyof Shape is a subset of keyof T.
|
||||||
this.instance?.[property.name as keyof T]; // keyof Shape is a subset of keyof T.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return instanceProperties as ModelPropertiesValues<T, Shape>; // Returning the properties object.
|
return instanceProperties as ModelPropertiesValues<T, Shape>; // Returning the properties object.
|
||||||
|
@ -297,12 +256,13 @@ export class Model<
|
||||||
/**
|
/**
|
||||||
* Serialize the model instance.
|
* Serialize the model instance.
|
||||||
*/
|
*/
|
||||||
serialize(): SerializedModel<T, Shape> {
|
serialize(): SerializedModel<T, Shape>
|
||||||
|
{
|
||||||
// Creating an empty serialized object.
|
// Creating an empty serialized object.
|
||||||
const serializedObject: Partial<SerializedModel<T, Shape>> = {};
|
const serializedObject: Partial<SerializedModel<T, Shape>> = {};
|
||||||
|
|
||||||
for (const property of this.properties) {
|
for (const property of this.properties)
|
||||||
// For each defined model property, adding it to the serialized object.
|
{ // For each defined model property, adding it to the serialized object.
|
||||||
serializedObject[property.name] = property.definition.type.serialize(
|
serializedObject[property.name] = property.definition.type.serialize(
|
||||||
// keyof Shape is a subset of keyof T.
|
// keyof Shape is a subset of keyof T.
|
||||||
this.instance?.[property.name as keyof T],
|
this.instance?.[property.name as keyof T],
|
||||||
|
@ -315,22 +275,19 @@ export class Model<
|
||||||
/**
|
/**
|
||||||
* Examine if the model is new (never deserialized) or not.
|
* Examine if the model is new (never deserialized) or not.
|
||||||
*/
|
*/
|
||||||
isNew(): boolean {
|
isNew(): boolean
|
||||||
|
{
|
||||||
return !this.original.serialized;
|
return !this.original.serialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Examine if the model is dirty or not.
|
* Examine if the model is dirty or not.
|
||||||
*/
|
*/
|
||||||
isDirty(): boolean {
|
isDirty(): boolean
|
||||||
for (const property of this.properties) {
|
{
|
||||||
// For each property, check if it is different.
|
for (const property of this.properties)
|
||||||
if (
|
{ // For each property, check if it is different.
|
||||||
property.definition.type.hasChanged(
|
if (property.definition.type.hasChanged(this.original.properties?.[property.name], this.instance?.[property.name as keyof T]))
|
||||||
this.original.properties?.[property.name],
|
|
||||||
this.instance?.[property.name as keyof T],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
// There is a difference: the model is dirty.
|
// There is a difference: the model is dirty.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -342,23 +299,19 @@ export class Model<
|
||||||
/**
|
/**
|
||||||
* Serialize the difference between current model state and the original one.
|
* Serialize the difference between current model state and the original one.
|
||||||
*/
|
*/
|
||||||
serializeDiff(): Partial<SerializedModel<T, Shape>> {
|
serializeDiff(): Partial<SerializedModel<T, Shape>>
|
||||||
|
{
|
||||||
// Creating an empty serialized object.
|
// Creating an empty serialized object.
|
||||||
const serializedObject: Partial<SerializedModel<T, Shape>> = {};
|
const serializedObject: Partial<SerializedModel<T, Shape>> = {};
|
||||||
|
|
||||||
for (const property of this.properties) {
|
for (const property of this.properties)
|
||||||
// For each defined model property, adding it to the serialized object if it has changed or if it is in the identifier.
|
{ // For each defined model property, adding it to the serialized object if it has changed or if it is in the identifier.
|
||||||
const instancePropValue = this.instance?.[property.name as keyof T]; // keyof Shape is a subset of keyof T.
|
const instancePropValue = this.instance?.[property.name as keyof T]; // keyof Shape is a subset of keyof T.
|
||||||
if (
|
if (
|
||||||
property.identifier ||
|
property.identifier ||
|
||||||
property.definition.type.hasChanged(
|
property.definition.type.hasChanged(this.original.properties?.[property.name], instancePropValue)
|
||||||
this.original.properties?.[property.name],
|
) // The property is part of the identifier or its value has changed.
|
||||||
instancePropValue,
|
serializedObject[property.name] = property.definition.type.serializeDiff(instancePropValue);
|
||||||
)
|
|
||||||
)
|
|
||||||
// The property is part of the identifier or its value has changed.
|
|
||||||
serializedObject[property.name] =
|
|
||||||
property.definition.type.serializeDiff(instancePropValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return serializedObject; // Returning the serialized object.
|
return serializedObject; // Returning the serialized object.
|
||||||
|
@ -367,13 +320,13 @@ export class Model<
|
||||||
/**
|
/**
|
||||||
* Set current model properties as original values.
|
* Set current model properties as original values.
|
||||||
*/
|
*/
|
||||||
resetDiff(): void {
|
resetDiff(): void
|
||||||
|
{
|
||||||
this.original.properties = {};
|
this.original.properties = {};
|
||||||
for (const property of this.properties) {
|
for (const property of this.properties)
|
||||||
// For each property, set its original value to the current property value.
|
{ // For each property, set its original value to the current property value.
|
||||||
const instancePropValue = this.instance?.[property.name as keyof T];
|
const instancePropValue = this.instance?.[property.name as keyof T];
|
||||||
this.original.properties[property.name] =
|
this.original.properties[property.name] = property.definition.type.clone(instancePropValue);
|
||||||
property.definition.type.clone(instancePropValue);
|
|
||||||
property.definition.type.resetDiff(instancePropValue);
|
property.definition.type.resetDiff(instancePropValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -382,7 +335,8 @@ export class Model<
|
||||||
* Get difference between original values and current ones, then reset it.
|
* Get difference between original values and current ones, then reset it.
|
||||||
* Similar to call `serializeDiff()` then `resetDiff()`.
|
* Similar to call `serializeDiff()` then `resetDiff()`.
|
||||||
*/
|
*/
|
||||||
patch(): Partial<SerializedModel<T, Shape>> {
|
patch(): Partial<SerializedModel<T, Shape>>
|
||||||
|
{
|
||||||
// Get the difference.
|
// Get the difference.
|
||||||
const diff = this.serializeDiff();
|
const diff = this.serializeDiff();
|
||||||
|
|
||||||
|
@ -395,36 +349,32 @@ export class Model<
|
||||||
/**
|
/**
|
||||||
* Clone the model instance.
|
* Clone the model instance.
|
||||||
*/
|
*/
|
||||||
clone(): ModelInstance<T, Shape, Identifier> {
|
clone(): ModelInstance<T, Shape, Identifier>
|
||||||
|
{
|
||||||
// Initialize a new instance for the clone.
|
// Initialize a new instance for the clone.
|
||||||
const cloned = this.manager.model();
|
const cloned = this.manager.model();
|
||||||
|
|
||||||
// Clone every value of the model instance.
|
// Clone every value of the model instance.
|
||||||
for (const [key, value] of Object.entries(this.instance) as [
|
for (const [key, value] of Object.entries(this.instance) as [keyof T, unknown][])
|
||||||
keyof T,
|
{ // For each [key, value], clone the value and put it in the cloned instance.
|
||||||
unknown,
|
|
||||||
][]) {
|
|
||||||
// For each [key, value], clone the value and put it in the cloned instance.
|
|
||||||
|
|
||||||
// Do not clone ourselves.
|
// Do not clone ourselves.
|
||||||
if (key == "_sharkitek") continue;
|
if (key == "_sharkitek") continue;
|
||||||
|
|
||||||
if (this.definition.properties[key]) {
|
if (this.definition.properties[key])
|
||||||
// The current key is a defined property, clone using the defined type.
|
{ // The current key is a defined property, clone using the defined type.
|
||||||
(cloned.instance[key] as any) = (
|
(cloned.instance[key] as any) = (this.definition.properties[key] as UnknownDefinition).type.clone(value);
|
||||||
this.definition.properties[key] as UnknownDefinition
|
}
|
||||||
).type.clone(value);
|
else
|
||||||
} else {
|
{ // Not a property, cloning the raw value.
|
||||||
// Not a property, cloning the raw value.
|
|
||||||
(cloned.instance[key] as any) = structuredClone(value);
|
(cloned.instance[key] as any) = structuredClone(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clone original properties.
|
// Clone original properties.
|
||||||
for (const property of this.properties) {
|
for (const property of this.properties)
|
||||||
// For each property, clone its original value.
|
{ // For each property, clone its original value.
|
||||||
cloned.original.properties[property.name] =
|
cloned.original.properties[property.name] = property.definition.type.clone(this.original.properties[property.name]);
|
||||||
property.definition.type.clone(this.original.properties[property.name]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clone original serialized.
|
// Clone original serialized.
|
||||||
|
@ -432,78 +382,15 @@ export class Model<
|
||||||
|
|
||||||
return cloned.instance; // Returning the cloned instance.
|
return cloned.instance; // Returning the cloned instance.
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Assign the provided fields to existing properties.
|
|
||||||
* Fields that cannot be matched to existing properties are silently ignored.
|
|
||||||
* @param fields The fields to assign to the model.
|
|
||||||
*/
|
|
||||||
assign(
|
|
||||||
fields: Partial<ModelPropertiesValues<T, Shape>> & {[field: string]: any},
|
|
||||||
): ModelInstance<T, Shape, Identifier> {
|
|
||||||
for (const field in fields) {
|
|
||||||
// For each field, if it's a property, assign its value.
|
|
||||||
if ((this.definition.properties as any)?.[field])
|
|
||||||
// Set the instance value.
|
|
||||||
this.instance[field as keyof T] = fields[field];
|
|
||||||
}
|
|
||||||
return this.instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply a patch to the model instance. All known fields will be deserialized and assigned to the properties.
|
|
||||||
* @param patch The patch object to apply.
|
|
||||||
* @param updateOriginals Indicates if the original properties values must be updated or not. By default, they are reset.
|
|
||||||
*/
|
|
||||||
applyPatch(
|
|
||||||
patch: SerializedModel<T, Shape>,
|
|
||||||
updateOriginals: boolean = true,
|
|
||||||
): ModelInstance<T, Shape, Identifier> {
|
|
||||||
if (updateOriginals) {
|
|
||||||
// If serialized original is null and we need to update it, initialize it.
|
|
||||||
this.original.serialized = this.serialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const serializedField in patch) {
|
|
||||||
// For each field, if it's a property, assign its value.
|
|
||||||
// Get the property definition.
|
|
||||||
const property =
|
|
||||||
this.definition.properties[serializedField as keyof Shape];
|
|
||||||
if (property) {
|
|
||||||
// Found a matching model property, assigning its deserialized value.
|
|
||||||
(this.instance[serializedField as keyof Shape as keyof T] as any) = (
|
|
||||||
property as UnknownDefinition
|
|
||||||
).type.applyPatch(
|
|
||||||
this.instance[serializedField as keyof Shape as keyof T],
|
|
||||||
patch[serializedField],
|
|
||||||
updateOriginals,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (updateOriginals) {
|
|
||||||
// Update original values.
|
|
||||||
// Set original property value.
|
|
||||||
(this.original.properties[serializedField] as any) = (
|
|
||||||
property as UnknownDefinition
|
|
||||||
).type.clone(
|
|
||||||
this.instance[serializedField as keyof Shape as keyof T],
|
|
||||||
);
|
|
||||||
// Set original serialized value.
|
|
||||||
this.original.serialized[serializedField] = patch[serializedField];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.instance;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A model manager, created from a model definition.
|
* A model manager, created from a model definition.
|
||||||
*/
|
*/
|
||||||
export class ModelManager<
|
export class ModelManager<T extends object, Shape extends ModelShape<T>, Identifier extends IdentifierDefinition<T, Shape>>
|
||||||
T extends object,
|
{
|
||||||
Shape extends ModelShape<T>,
|
|
||||||
Identifier extends IdentifierDefinition<T, Shape>,
|
|
||||||
> {
|
|
||||||
/**
|
/**
|
||||||
* Defined properties.
|
* Defined properties.
|
||||||
*/
|
*/
|
||||||
|
@ -513,9 +400,8 @@ export class ModelManager<
|
||||||
* Initialize a model manager from a model definition.
|
* Initialize a model manager from a model definition.
|
||||||
* @param definition The model definition.
|
* @param definition The model definition.
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(public readonly definition: ModelDefinition<T, Shape, Identifier>)
|
||||||
public readonly definition: ModelDefinition<T, Shape, Identifier>,
|
{
|
||||||
) {
|
|
||||||
this.initProperties();
|
this.initProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -523,22 +409,23 @@ export class ModelManager<
|
||||||
* Initialize properties iterator from current definition.
|
* Initialize properties iterator from current definition.
|
||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
protected initProperties(): void {
|
protected initProperties(): void
|
||||||
|
{
|
||||||
// Build an array of model properties from the definition.
|
// Build an array of model properties from the definition.
|
||||||
this.properties = [];
|
this.properties = [];
|
||||||
for (const propertyName in this.definition.properties) {
|
for (const propertyName in this.definition.properties)
|
||||||
// For each property, build a model property object.
|
{ // For each property, build a model property object.
|
||||||
this.properties.push({
|
this.properties.push({
|
||||||
name: propertyName,
|
name: propertyName,
|
||||||
definition: this.definition.properties[propertyName],
|
definition: this.definition.properties[propertyName],
|
||||||
// Find out if the current property is part of the identifier.
|
// Find out if the current property is part of the identifier.
|
||||||
identifier: Array.isArray(this.definition.identifier)
|
identifier: (
|
||||||
? // The identifier is an array, the property must be in the array.
|
Array.isArray(this.definition.identifier)
|
||||||
this.definition.identifier.includes(
|
// The identifier is an array, the property must be in the array.
|
||||||
propertyName as keyof Shape as keyof T,
|
? this.definition.identifier.includes(propertyName as keyof Shape as keyof T)
|
||||||
)
|
// The identifier is a single string, the property must be the defined identifier.
|
||||||
: // The identifier is a single string, the property must be the defined identifier.
|
: (this.definition.identifier == propertyName as keyof Shape)
|
||||||
this.definition.identifier == (propertyName as keyof Shape),
|
),
|
||||||
} as ModelProperty<T, Shape>);
|
} as ModelProperty<T, Shape>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -547,121 +434,49 @@ export class ModelManager<
|
||||||
* Get the model state of the provided model instance.
|
* Get the model state of the provided model instance.
|
||||||
* @param instance The model instance for which to get its state. NULL or undefined to create a new one.
|
* @param instance The model instance for which to get its state. NULL or undefined to create a new one.
|
||||||
*/
|
*/
|
||||||
model(
|
model(instance: T|ModelInstance<T, Shape, Identifier>|null = null): Model<T, Shape, Identifier>
|
||||||
instance: T | ModelInstance<T, Shape, Identifier> | null = null,
|
{ // Get the instance model state if there is one, or initialize a new one.
|
||||||
): Model<T, Shape, Identifier> {
|
|
||||||
// Get the instance model state if there is one, or initialize a new one.
|
|
||||||
if (instance)
|
if (instance)
|
||||||
// There is an instance, create a model from it.
|
// There is an instance, create a model from it.
|
||||||
return (
|
return ((instance as ModelInstance<T, Shape, Identifier>)?._sharkitek ?? (new Model<T, Shape, Identifier>(this))).fromInstance(instance);
|
||||||
(instance as ModelInstance<T, Shape, Identifier>)?._sharkitek ??
|
|
||||||
new Model<T, Shape, Identifier>(this)
|
|
||||||
).fromInstance(instance);
|
|
||||||
else
|
else
|
||||||
// No instance, initialize a new one.
|
// No instance, initialize a new one.
|
||||||
return new Model<T, Shape, Identifier>(this).initInstance();
|
return (new Model<T, Shape, Identifier>(this)).initInstance();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize a new model instance with the provided object properties values.
|
|
||||||
* Fields that cannot be matched to existing properties are silently ignored.
|
|
||||||
* @param fields
|
|
||||||
*/
|
|
||||||
from(
|
|
||||||
fields: Partial<ModelPropertiesValues<T, Shape>> & {[field: string]: any},
|
|
||||||
): ModelInstance<T, Shape, Identifier> {
|
|
||||||
return this.model().assign(fields);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the serialized model object to a new model instance.
|
* Parse the serialized model object to a new model instance.
|
||||||
* @param serialized The serialized model object.
|
* @param serialized The serialized model object.
|
||||||
*/
|
*/
|
||||||
parse(
|
parse(serialized: SerializedModel<T, Shape>): ModelInstance<T, Shape, Identifier>
|
||||||
serialized: SerializedModel<T, Shape>,
|
{
|
||||||
): ModelInstance<T, Shape, Identifier> {
|
|
||||||
return this.model().deserialize(serialized).instance;
|
return this.model().deserialize(serialized).instance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A model manager extension is a mixin, building a new model manager class with extended capabilities.
|
|
||||||
* @see https://www.typescriptlang.org/docs/handbook/mixins.html
|
|
||||||
*/
|
|
||||||
export type ModelManagerExtension<
|
|
||||||
Extension extends object,
|
|
||||||
T extends object,
|
|
||||||
Shape extends ModelShape<T>,
|
|
||||||
Identifier extends IdentifierDefinition<T, Shape>,
|
|
||||||
> = (
|
|
||||||
modelManager: ModelManager<T, Shape, Identifier>,
|
|
||||||
) => ModelManager<T, Shape, Identifier> & Extension;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define a new model.
|
* Define a new model.
|
||||||
* @param definition The model definition object.
|
* @param definition The model definition object.
|
||||||
*/
|
*/
|
||||||
export function defineModel<
|
export function defineModel<T extends object, Shape extends ModelShape<T>, Identifier extends IdentifierDefinition<T, Shape>>(
|
||||||
T extends object,
|
definition: ModelDefinition<T, Shape, Identifier>
|
||||||
Shape extends ModelShape<T>,
|
)
|
||||||
Identifier extends IdentifierDefinition<T, Shape>,
|
{
|
||||||
>(definition: ModelDefinition<T, Shape, Identifier>) {
|
|
||||||
return new ModelManager<T, Shape, Identifier>(definition);
|
return new ModelManager<T, Shape, Identifier>(definition);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Define a new model, extending an existing one.
|
|
||||||
* @param extendedModel The extended model manager instance.
|
|
||||||
* @param definition The extension of the model definition object.
|
|
||||||
*/
|
|
||||||
export function extend<
|
|
||||||
ExtT extends object,
|
|
||||||
ExtShape extends ModelShape<ExtT>,
|
|
||||||
ExtIdentifier extends IdentifierDefinition<ExtT, ExtShape>,
|
|
||||||
T extends ExtT,
|
|
||||||
Shape extends ModelShape<T>,
|
|
||||||
Identifier extends IdentifierDefinition<T, Shape>,
|
|
||||||
ResIdentifier extends IdentifierDefinition<T, ResShape>,
|
|
||||||
ResShape extends ModelShape<T> = Modify<ExtShape, Shape>,
|
|
||||||
>(
|
|
||||||
extendedModel: ModelManager<ExtT, ExtShape, ExtIdentifier>,
|
|
||||||
definition: ModelDefinition<T, Shape, Identifier>,
|
|
||||||
) {
|
|
||||||
const {properties: extendedProperties, ...overridableDefinition} =
|
|
||||||
extendedModel.definition;
|
|
||||||
const {properties: propertiesExtension, ...definitionExtension} = definition;
|
|
||||||
return new ModelManager({
|
|
||||||
...overridableDefinition,
|
|
||||||
...definitionExtension,
|
|
||||||
properties: {
|
|
||||||
...extendedProperties,
|
|
||||||
...propertiesExtension,
|
|
||||||
},
|
|
||||||
}) as unknown as ModelManager<T, ResShape, ResIdentifier>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A generic model manager for a provided model type, to use in circular dependencies.
|
* A generic model manager for a provided model type, to use in circular dependencies.
|
||||||
*/
|
*/
|
||||||
export type GenericModelManager<T extends object> = ModelManager<
|
export type GenericModelManager<T extends object> = ModelManager<T, ModelShape<T>, IdentifierDefinition<T, ModelShape<T>>>;
|
||||||
T,
|
|
||||||
ModelShape<T>,
|
|
||||||
IdentifierDefinition<T, ModelShape<T>>
|
|
||||||
>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to get a model manager lazily.
|
* Function to get a model manager lazily.
|
||||||
*/
|
*/
|
||||||
export type LazyModelManager<
|
export type LazyModelManager<T extends object, Shape extends ModelShape<T>, Identifier extends IdentifierDefinition<T, Shape>>
|
||||||
T extends object,
|
= (() => ModelManager<T, Shape, Identifier>);
|
||||||
Shape extends ModelShape<T>,
|
|
||||||
Identifier extends IdentifierDefinition<T, Shape>,
|
|
||||||
> = () => ModelManager<T, Shape, Identifier>;
|
|
||||||
/**
|
/**
|
||||||
* A model manager definition that can be lazy.
|
* A model manager definition that can be lazy.
|
||||||
*/
|
*/
|
||||||
export type DeclaredModelManager<
|
export type DeclaredModelManager<T extends object, Shape extends ModelShape<T>, Identifier extends IdentifierDefinition<T, Shape>>
|
||||||
T extends object,
|
= ModelManager<T, Shape, Identifier>|LazyModelManager<T, Shape, Identifier>;
|
||||||
Shape extends ModelShape<T>,
|
|
||||||
Identifier extends IdentifierDefinition<T, Shape>,
|
|
||||||
> = ModelManager<T, Shape, Identifier> | LazyModelManager<T, Shape, Identifier>;
|
|
||||||
|
|
|
@ -8,5 +8,3 @@ export {model} from "./types/model";
|
||||||
export {numeric} from "./types/numeric";
|
export {numeric} from "./types/numeric";
|
||||||
export {object} from "./types/object";
|
export {object} from "./types/object";
|
||||||
export {string} from "./types/string";
|
export {string} from "./types/string";
|
||||||
export {map} from "./types/map";
|
|
||||||
export {stringMap} from "./types/map";
|
|
||||||
|
|
|
@ -3,7 +3,8 @@ import {Type} from "./types/type";
|
||||||
/**
|
/**
|
||||||
* Property definition class.
|
* Property definition class.
|
||||||
*/
|
*/
|
||||||
export class Definition<SerializedType, ModelType> {
|
export class Definition<SerializedType, ModelType>
|
||||||
|
{
|
||||||
readonly _sharkitek: ModelType;
|
readonly _sharkitek: ModelType;
|
||||||
readonly _serialized: SerializedType;
|
readonly _serialized: SerializedType;
|
||||||
|
|
||||||
|
@ -11,7 +12,8 @@ export class Definition<SerializedType, ModelType> {
|
||||||
* Create a property definer instance.
|
* Create a property definer instance.
|
||||||
* @param type Property type.
|
* @param type Property type.
|
||||||
*/
|
*/
|
||||||
constructor(public readonly type: Type<SerializedType, ModelType>) {}
|
constructor(public readonly type: Type<SerializedType, ModelType>)
|
||||||
|
{}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,8 +30,7 @@ export type AnyDefinition = Definition<any, any>;
|
||||||
* New definition of a property of the given type.
|
* New definition of a property of the given type.
|
||||||
* @param type Type of the property to define.
|
* @param type Type of the property to define.
|
||||||
*/
|
*/
|
||||||
export function define<SerializedType, ModelType>(
|
export function define<SerializedType, ModelType>(type: Type<SerializedType, ModelType>): Definition<SerializedType, ModelType>
|
||||||
type: Type<SerializedType, ModelType>,
|
{
|
||||||
): Definition<SerializedType, ModelType> {
|
|
||||||
return new Definition(type);
|
return new Definition(type);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,70 +5,56 @@ import {InvalidTypeValueError} from "../../errors";
|
||||||
/**
|
/**
|
||||||
* Type of an array of values.
|
* Type of an array of values.
|
||||||
*/
|
*/
|
||||||
export class ArrayType<SerializedValueType, SharkitekValueType> extends Type<
|
export class ArrayType<SerializedValueType, SharkitekValueType> extends Type<SerializedValueType[], SharkitekValueType[]>
|
||||||
SerializedValueType[],
|
{
|
||||||
SharkitekValueType[]
|
|
||||||
> {
|
|
||||||
/**
|
/**
|
||||||
* Initialize a new array type of a Sharkitek model property.
|
* Initialize a new array type of a Sharkitek model property.
|
||||||
* @param valueDefinition Definition the array values.
|
* @param valueDefinition Definition the array values.
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(protected valueDefinition: Definition<SerializedValueType, SharkitekValueType>)
|
||||||
protected valueDefinition: Definition<
|
{
|
||||||
SerializedValueType,
|
|
||||||
SharkitekValueType
|
|
||||||
>,
|
|
||||||
) {
|
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize(
|
serialize(value: SharkitekValueType[]|null|undefined): SerializedValueType[]|null|undefined
|
||||||
value: SharkitekValueType[] | null | undefined,
|
{
|
||||||
): SerializedValueType[] | null | undefined {
|
|
||||||
if (value === undefined) return undefined;
|
if (value === undefined) return undefined;
|
||||||
if (value === null) return null;
|
if (value === null) return null;
|
||||||
|
|
||||||
if (!Array.isArray(value))
|
if (!Array.isArray(value)) throw new InvalidTypeValueError(this, value, "value must be an array");
|
||||||
throw new InvalidTypeValueError(this, value, "value must be an array");
|
|
||||||
|
|
||||||
return value.map((value) =>
|
return value.map((value) => (
|
||||||
// Serializing each value of the array.
|
// Serializing each value of the array.
|
||||||
this.valueDefinition.type.serialize(value),
|
this.valueDefinition.type.serialize(value)
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
deserialize(
|
deserialize(value: SerializedValueType[]|null|undefined): SharkitekValueType[]|null|undefined
|
||||||
value: SerializedValueType[] | null | undefined,
|
{
|
||||||
): SharkitekValueType[] | null | undefined {
|
|
||||||
if (value === undefined) return undefined;
|
if (value === undefined) return undefined;
|
||||||
if (value === null) return null;
|
if (value === null) return null;
|
||||||
|
|
||||||
if (!Array.isArray(value))
|
if (!Array.isArray(value)) throw new InvalidTypeValueError(this, value, "value must be an array");
|
||||||
throw new InvalidTypeValueError(this, value, "value must be an array");
|
|
||||||
|
|
||||||
return value.map((serializedValue) =>
|
return value.map((serializedValue) => (
|
||||||
// Deserializing each value of the array.
|
// Deserializing each value of the array.
|
||||||
this.valueDefinition.type.deserialize(serializedValue),
|
this.valueDefinition.type.deserialize(serializedValue)
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
serializeDiff(
|
serializeDiff(value: SharkitekValueType[]|null|undefined): SerializedValueType[]|null|undefined
|
||||||
value: SharkitekValueType[] | null | undefined,
|
{
|
||||||
): SerializedValueType[] | null | undefined {
|
|
||||||
if (value === undefined) return undefined;
|
if (value === undefined) return undefined;
|
||||||
if (value === null) return null;
|
if (value === null) return null;
|
||||||
|
|
||||||
if (!Array.isArray(value))
|
if (!Array.isArray(value)) throw new InvalidTypeValueError(this, value, "value must be an array");
|
||||||
throw new InvalidTypeValueError(this, value, "value must be an array");
|
|
||||||
|
|
||||||
// Serializing diff of all elements.
|
// Serializing diff of all elements.
|
||||||
return value.map(
|
return value.map((value) => this.valueDefinition.type.serializeDiff(value) as SerializedValueType);
|
||||||
(value) =>
|
|
||||||
this.valueDefinition.type.serializeDiff(value) as SerializedValueType,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resetDiff(value: SharkitekValueType[] | null | undefined): void {
|
resetDiff(value: SharkitekValueType[]|null|undefined): void
|
||||||
|
{
|
||||||
// Do nothing if it is not an array.
|
// Do nothing if it is not an array.
|
||||||
if (!Array.isArray(value)) return;
|
if (!Array.isArray(value)) return;
|
||||||
|
|
||||||
|
@ -76,24 +62,16 @@ export class ArrayType<SerializedValueType, SharkitekValueType> extends Type<
|
||||||
value.forEach((value) => this.valueDefinition.type.resetDiff(value));
|
value.forEach((value) => this.valueDefinition.type.resetDiff(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
hasChanged(
|
hasChanged(originalValue: SharkitekValueType[]|null|undefined, currentValue: SharkitekValueType[]|null|undefined): boolean
|
||||||
originalValue: SharkitekValueType[] | null | undefined,
|
{
|
||||||
currentValue: SharkitekValueType[] | null | undefined,
|
|
||||||
): boolean {
|
|
||||||
// If any array length is different, arrays are different.
|
// If any array length is different, arrays are different.
|
||||||
if (originalValue?.length != currentValue?.length) return true;
|
if (originalValue?.length != currentValue?.length) return true;
|
||||||
// If length is undefined, values are probably not arrays.
|
// If length is undefined, values are probably not arrays.
|
||||||
if (originalValue?.length == undefined)
|
if (originalValue?.length == undefined) return super.hasChanged(originalValue, currentValue);
|
||||||
return super.hasChanged(originalValue, currentValue);
|
|
||||||
|
|
||||||
for (const key of originalValue.keys()) {
|
for (const key of originalValue.keys())
|
||||||
// Check for any change for each value in the array.
|
{ // Check for any change for each value in the array.
|
||||||
if (
|
if (this.valueDefinition.type.hasChanged(originalValue[key], currentValue[key]))
|
||||||
this.valueDefinition.type.hasChanged(
|
|
||||||
originalValue[key],
|
|
||||||
currentValue[key],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
// The value has changed, the array is different.
|
// The value has changed, the array is different.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -101,24 +79,16 @@ export class ArrayType<SerializedValueType, SharkitekValueType> extends Type<
|
||||||
return false; // No change detected.
|
return false; // No change detected.
|
||||||
}
|
}
|
||||||
|
|
||||||
serializedHasChanged(
|
serializedHasChanged(originalValue: SerializedValueType[] | null | undefined, currentValue: SerializedValueType[] | null | undefined): boolean
|
||||||
originalValue: SerializedValueType[] | null | undefined,
|
{
|
||||||
currentValue: SerializedValueType[] | null | undefined,
|
|
||||||
): boolean {
|
|
||||||
// If any array length is different, arrays are different.
|
// If any array length is different, arrays are different.
|
||||||
if (originalValue?.length != currentValue?.length) return true;
|
if (originalValue?.length != currentValue?.length) return true;
|
||||||
// If length is undefined, values are probably not arrays.
|
// If length is undefined, values are probably not arrays.
|
||||||
if (originalValue?.length == undefined)
|
if (originalValue?.length == undefined) return super.serializedHasChanged(originalValue, currentValue);
|
||||||
return super.serializedHasChanged(originalValue, currentValue);
|
|
||||||
|
|
||||||
for (const key of originalValue.keys()) {
|
for (const key of originalValue.keys())
|
||||||
// Check for any change for each value in the array.
|
{ // Check for any change for each value in the array.
|
||||||
if (
|
if (this.valueDefinition.type.serializedHasChanged(originalValue[key], currentValue[key]))
|
||||||
this.valueDefinition.type.serializedHasChanged(
|
|
||||||
originalValue[key],
|
|
||||||
currentValue[key],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
// The value has changed, the array is different.
|
// The value has changed, the array is different.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -126,62 +96,30 @@ export class ArrayType<SerializedValueType, SharkitekValueType> extends Type<
|
||||||
return false; // No change detected.
|
return false; // No change detected.
|
||||||
}
|
}
|
||||||
|
|
||||||
clone<T extends SharkitekValueType[]>(array: T | null | undefined): T {
|
clone<T extends SharkitekValueType[]>(array: T|null|undefined): T
|
||||||
|
{
|
||||||
// Handle NULL / undefined array.
|
// Handle NULL / undefined array.
|
||||||
if (!array) return super.clone(array);
|
if (!array) return super.clone(array);
|
||||||
|
|
||||||
if (!Array.isArray(array))
|
if (!Array.isArray(array)) throw new InvalidTypeValueError(this, array, "value must be an array");
|
||||||
throw new InvalidTypeValueError(this, array, "value must be an array");
|
|
||||||
|
|
||||||
// Initialize an empty array.
|
// Initialize an empty array.
|
||||||
const cloned = [] as T;
|
const cloned = [] as T;
|
||||||
|
|
||||||
for (const value of array) {
|
for (const value of array)
|
||||||
// Clone each value of the array.
|
{ // Clone each value of the array.
|
||||||
cloned.push(this.valueDefinition.type.clone(value));
|
cloned.push(this.valueDefinition.type.clone(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
return cloned; // Returning cloned array.
|
return cloned; // Returning cloned array.
|
||||||
}
|
}
|
||||||
|
|
||||||
applyPatch<T extends SharkitekValueType[]>(
|
|
||||||
currentValue: T | null | undefined,
|
|
||||||
patchValue: SerializedValueType[] | null | undefined,
|
|
||||||
updateOriginals: boolean,
|
|
||||||
): T | null | undefined {
|
|
||||||
if (patchValue === undefined) return undefined;
|
|
||||||
if (patchValue === null) return null;
|
|
||||||
|
|
||||||
if (!Array.isArray(patchValue))
|
|
||||||
throw new InvalidTypeValueError(
|
|
||||||
this,
|
|
||||||
patchValue,
|
|
||||||
"value must be an array",
|
|
||||||
);
|
|
||||||
|
|
||||||
currentValue = Array.isArray(currentValue) ? currentValue : ([] as T);
|
|
||||||
|
|
||||||
for (let i = 0; i < patchValue.length; i++) {
|
|
||||||
// Apply the patch to all values of the array.
|
|
||||||
const patchedElement = this.valueDefinition.type.applyPatch(
|
|
||||||
currentValue?.[i],
|
|
||||||
patchValue[i],
|
|
||||||
updateOriginals,
|
|
||||||
);
|
|
||||||
if (i < currentValue.length) currentValue[i] = patchedElement;
|
|
||||||
else currentValue.push(patchedElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
return currentValue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* New array property definition.
|
* New array property definition.
|
||||||
* @param valueDefinition Array values type definition.
|
* @param valueDefinition Array values type definition.
|
||||||
*/
|
*/
|
||||||
export function array<SerializedValueType, SharkitekValueType>(
|
export function array<SerializedValueType, SharkitekValueType>(valueDefinition: Definition<SerializedValueType, SharkitekValueType>): Definition<SerializedValueType[], SharkitekValueType[]>
|
||||||
valueDefinition: Definition<SerializedValueType, SharkitekValueType>,
|
{
|
||||||
): Definition<SerializedValueType[], SharkitekValueType[]> {
|
|
||||||
return define(new ArrayType(valueDefinition));
|
return define(new ArrayType(valueDefinition));
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,10 @@ import {define, Definition} from "../property-definition";
|
||||||
/**
|
/**
|
||||||
* Type of any boolean value.
|
* Type of any boolean value.
|
||||||
*/
|
*/
|
||||||
export class BooleanType extends Type<boolean, boolean> {
|
export class BooleanType extends Type<boolean, boolean>
|
||||||
deserialize(value: boolean | null | undefined): boolean | null | undefined {
|
{
|
||||||
|
deserialize(value: boolean|null|undefined): boolean|null|undefined
|
||||||
|
{
|
||||||
// Keep NULL and undefined values.
|
// Keep NULL and undefined values.
|
||||||
if (value === undefined) return undefined;
|
if (value === undefined) return undefined;
|
||||||
if (value === null) return null;
|
if (value === null) return null;
|
||||||
|
@ -13,7 +15,8 @@ export class BooleanType extends Type<boolean, boolean> {
|
||||||
return !!value; // ensure bool type.
|
return !!value; // ensure bool type.
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize(value: boolean | null | undefined): boolean | null | undefined {
|
serialize(value: boolean|null|undefined): boolean|null|undefined
|
||||||
|
{
|
||||||
// Keep NULL and undefined values.
|
// Keep NULL and undefined values.
|
||||||
if (value === undefined) return undefined;
|
if (value === undefined) return undefined;
|
||||||
if (value === null) return null;
|
if (value === null) return null;
|
||||||
|
@ -25,13 +28,15 @@ export class BooleanType extends Type<boolean, boolean> {
|
||||||
/**
|
/**
|
||||||
* New boolean property definition.
|
* New boolean property definition.
|
||||||
*/
|
*/
|
||||||
export function boolean(): Definition<boolean, boolean> {
|
export function boolean(): Definition<boolean, boolean>
|
||||||
|
{
|
||||||
return define(new BooleanType());
|
return define(new BooleanType());
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* New boolean property definition.
|
* New boolean property definition.
|
||||||
* Alias of boolean.
|
* Alias of boolean.
|
||||||
*/
|
*/
|
||||||
export function bool(): ReturnType<typeof boolean> {
|
export function bool(): ReturnType<typeof boolean>
|
||||||
|
{
|
||||||
return boolean();
|
return boolean();
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,30 +5,30 @@ import {InvalidTypeValueError} from "../../errors";
|
||||||
/**
|
/**
|
||||||
* Type of dates.
|
* Type of dates.
|
||||||
*/
|
*/
|
||||||
export class DateType extends Type<string, Date> {
|
export class DateType extends Type<string, Date>
|
||||||
deserialize(value: string | null | undefined): Date | null | undefined {
|
{
|
||||||
|
deserialize(value: string|null|undefined): Date|null|undefined
|
||||||
|
{
|
||||||
if (value === undefined) return undefined;
|
if (value === undefined) return undefined;
|
||||||
if (value === null) return null;
|
if (value === null) return null;
|
||||||
|
|
||||||
return new Date(value);
|
return new Date(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize(value: Date | null | undefined): string | null | undefined {
|
serialize(value: Date|null|undefined): string|null|undefined
|
||||||
|
{
|
||||||
if (value === undefined) return undefined;
|
if (value === undefined) return undefined;
|
||||||
if (value === null) return null;
|
if (value === null) return null;
|
||||||
if (!(value instanceof Date))
|
if (!(value instanceof Date)) throw new InvalidTypeValueError(this, value, "value must be a date");
|
||||||
throw new InvalidTypeValueError(this, value, "value must be a date");
|
|
||||||
if (isNaN(value?.valueOf())) return value?.toString();
|
if (isNaN(value?.valueOf())) return value?.toString();
|
||||||
|
|
||||||
return value?.toISOString();
|
return value?.toISOString();
|
||||||
}
|
}
|
||||||
|
|
||||||
hasChanged(
|
hasChanged(originalValue: Date|null|undefined, currentValue: Date|null|undefined): boolean
|
||||||
originalValue: Date | null | undefined,
|
{
|
||||||
currentValue: Date | null | undefined,
|
if (originalValue instanceof Date && currentValue instanceof Date)
|
||||||
): boolean {
|
{ // Compare dates.
|
||||||
if (originalValue instanceof Date && currentValue instanceof Date) {
|
|
||||||
// Compare dates.
|
|
||||||
const originalTime = originalValue.getTime();
|
const originalTime = originalValue.getTime();
|
||||||
const currentTime = currentValue.getTime();
|
const currentTime = currentValue.getTime();
|
||||||
|
|
||||||
|
@ -37,7 +37,8 @@ export class DateType extends Type<string, Date> {
|
||||||
|
|
||||||
// Timestamps need to be exactly the same.
|
// Timestamps need to be exactly the same.
|
||||||
return originalValue.getTime() !== currentValue.getTime();
|
return originalValue.getTime() !== currentValue.getTime();
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
// Compare undefined or null values.
|
// Compare undefined or null values.
|
||||||
return originalValue !== currentValue;
|
return originalValue !== currentValue;
|
||||||
}
|
}
|
||||||
|
@ -46,6 +47,7 @@ export class DateType extends Type<string, Date> {
|
||||||
/**
|
/**
|
||||||
* New date property definition.
|
* New date property definition.
|
||||||
*/
|
*/
|
||||||
export function date(): Definition<string, Date> {
|
export function date(): Definition<string, Date>
|
||||||
|
{
|
||||||
return define(new DateType());
|
return define(new DateType());
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,19 +5,21 @@ import {InvalidTypeValueError} from "../../errors";
|
||||||
/**
|
/**
|
||||||
* Type of decimal numbers.
|
* Type of decimal numbers.
|
||||||
*/
|
*/
|
||||||
export class DecimalType extends Type<string, number> {
|
export class DecimalType extends Type<string, number>
|
||||||
deserialize(value: string | null | undefined): number | null | undefined {
|
{
|
||||||
|
deserialize(value: string|null|undefined): number|null|undefined
|
||||||
|
{
|
||||||
if (value === undefined) return undefined;
|
if (value === undefined) return undefined;
|
||||||
if (value === null) return null;
|
if (value === null) return null;
|
||||||
|
|
||||||
return parseFloat(value);
|
return parseFloat(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize(value: number | null | undefined): string | null | undefined {
|
serialize(value: number|null|undefined): string|null|undefined
|
||||||
|
{
|
||||||
if (value === undefined) return undefined;
|
if (value === undefined) return undefined;
|
||||||
if (value === null) return null;
|
if (value === null) return null;
|
||||||
if (typeof value !== "number" && typeof value !== "string")
|
if (typeof value !== "number" && typeof value !== "string") throw new InvalidTypeValueError(this, value, "value must be a number");
|
||||||
throw new InvalidTypeValueError(this, value, "value must be a number");
|
|
||||||
|
|
||||||
return value?.toString();
|
return value?.toString();
|
||||||
}
|
}
|
||||||
|
@ -26,6 +28,7 @@ export class DecimalType extends Type<string, number> {
|
||||||
/**
|
/**
|
||||||
* New decimal property definition.
|
* New decimal property definition.
|
||||||
*/
|
*/
|
||||||
export function decimal(): Definition<string, number> {
|
export function decimal(): Definition<string, number>
|
||||||
|
{
|
||||||
return define(new DecimalType());
|
return define(new DecimalType());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,264 +0,0 @@
|
||||||
import {Type} from "./type";
|
|
||||||
import {define, Definition} from "../property-definition";
|
|
||||||
import {InvalidTypeValueError} from "../../errors";
|
|
||||||
import {string} from "./string";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type of a key-value map.
|
|
||||||
*/
|
|
||||||
export class MapType<
|
|
||||||
KeyType,
|
|
||||||
ValueType,
|
|
||||||
SerializedValueType,
|
|
||||||
SerializedMapType extends Record<string, SerializedValueType> = Record<
|
|
||||||
string,
|
|
||||||
SerializedValueType
|
|
||||||
>,
|
|
||||||
> extends Type<SerializedMapType, Map<KeyType, ValueType>> {
|
|
||||||
/**
|
|
||||||
* Initialize a new map type of a Sharkitek model property.
|
|
||||||
* @param keyDefinition Definition of the map keys.
|
|
||||||
* @param valueDefinition Definition of the map values.
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
protected keyDefinition: Definition<string, KeyType>,
|
|
||||||
protected valueDefinition: Definition<SerializedValueType, ValueType>,
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
serialize(
|
|
||||||
value: Map<KeyType, ValueType> | null | undefined,
|
|
||||||
): SerializedMapType | null | undefined {
|
|
||||||
if (value === undefined) return undefined;
|
|
||||||
if (value === null) return null;
|
|
||||||
|
|
||||||
if (!(value instanceof Map))
|
|
||||||
throw new InvalidTypeValueError(
|
|
||||||
this,
|
|
||||||
value,
|
|
||||||
"value must be an instance of map",
|
|
||||||
);
|
|
||||||
|
|
||||||
return Object.fromEntries(
|
|
||||||
// Serializing each key-value pair of the map.
|
|
||||||
value
|
|
||||||
.entries()
|
|
||||||
.map(([key, value]) => [
|
|
||||||
this.keyDefinition.type.serialize(key),
|
|
||||||
this.valueDefinition.type.serialize(value),
|
|
||||||
]),
|
|
||||||
) as SerializedMapType;
|
|
||||||
}
|
|
||||||
|
|
||||||
deserialize(
|
|
||||||
value: SerializedMapType | null | undefined,
|
|
||||||
): Map<KeyType, ValueType> | null | undefined {
|
|
||||||
if (value === undefined) return undefined;
|
|
||||||
if (value === null) return null;
|
|
||||||
|
|
||||||
if (typeof value !== "object" || Array.isArray(value))
|
|
||||||
throw new InvalidTypeValueError(this, value, "value must be an object");
|
|
||||||
|
|
||||||
const map = new Map<KeyType, ValueType>();
|
|
||||||
for (const [serializedKey, serializedValue] of Object.entries(value)) {
|
|
||||||
// Deserializing each key-value pair of the map.
|
|
||||||
map.set(
|
|
||||||
this.keyDefinition.type.deserialize(serializedKey),
|
|
||||||
this.valueDefinition.type.deserialize(serializedValue),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
serializeDiff(
|
|
||||||
value: Map<KeyType, ValueType> | null | undefined,
|
|
||||||
): SerializedMapType | null | undefined {
|
|
||||||
if (value === undefined) return undefined;
|
|
||||||
if (value === null) return null;
|
|
||||||
|
|
||||||
if (!(value instanceof Map))
|
|
||||||
throw new InvalidTypeValueError(
|
|
||||||
this,
|
|
||||||
value,
|
|
||||||
"value must be an instance of map",
|
|
||||||
);
|
|
||||||
|
|
||||||
return Object.fromEntries(
|
|
||||||
// Serializing the diff of each key-value pair of the map.
|
|
||||||
value
|
|
||||||
.entries()
|
|
||||||
.map(([key, value]) => [
|
|
||||||
this.keyDefinition.type.serializeDiff(key),
|
|
||||||
this.valueDefinition.type.serializeDiff(value),
|
|
||||||
]),
|
|
||||||
) as SerializedMapType;
|
|
||||||
}
|
|
||||||
|
|
||||||
resetDiff(value: Map<KeyType, ValueType> | null | undefined): void {
|
|
||||||
// Do nothing if it is not a map.
|
|
||||||
if (!(value instanceof Map)) return;
|
|
||||||
|
|
||||||
// Reset diff of all key-value pairs.
|
|
||||||
value.forEach((value, key) => {
|
|
||||||
this.keyDefinition.type.resetDiff(key);
|
|
||||||
this.valueDefinition.type.resetDiff(value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
hasChanged(
|
|
||||||
originalValue: Map<KeyType, ValueType> | null | undefined,
|
|
||||||
currentValue: Map<KeyType, ValueType> | null | undefined,
|
|
||||||
): boolean {
|
|
||||||
// If any map size is different, maps are different.
|
|
||||||
if (originalValue?.size != currentValue?.size) return true;
|
|
||||||
// If size is undefined, values are probably not maps.
|
|
||||||
if (originalValue?.size == undefined)
|
|
||||||
return super.hasChanged(originalValue, currentValue);
|
|
||||||
|
|
||||||
for (const [key, value] of originalValue.entries()) {
|
|
||||||
// Check for any change for each key-value in the map.
|
|
||||||
if (this.valueDefinition.type.hasChanged(value, currentValue.get(key)))
|
|
||||||
// The value has changed, the map is different.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false; // No change detected.
|
|
||||||
}
|
|
||||||
|
|
||||||
serializedHasChanged(
|
|
||||||
originalValue: SerializedMapType | null | undefined,
|
|
||||||
currentValue: SerializedMapType | null | undefined,
|
|
||||||
): boolean {
|
|
||||||
// If any value is not a defined object, use the default comparison function.
|
|
||||||
if (
|
|
||||||
!originalValue ||
|
|
||||||
!currentValue ||
|
|
||||||
typeof originalValue !== "object" ||
|
|
||||||
typeof currentValue !== "object"
|
|
||||||
)
|
|
||||||
return super.serializedHasChanged(originalValue, currentValue);
|
|
||||||
|
|
||||||
// If any object size is different, objects are different.
|
|
||||||
if (Object.keys(originalValue)?.length != Object.keys(currentValue)?.length)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(originalValue)) {
|
|
||||||
// Check for any change for each key-value pair in the object.
|
|
||||||
if (
|
|
||||||
this.valueDefinition.type.serializedHasChanged(value, currentValue[key])
|
|
||||||
)
|
|
||||||
// The value has changed, the object is different.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false; // No change detected.
|
|
||||||
}
|
|
||||||
|
|
||||||
clone<T extends Map<KeyType, ValueType>>(map: T | null | undefined): T {
|
|
||||||
// Handle NULL / undefined map.
|
|
||||||
if (!map) return super.clone(map);
|
|
||||||
|
|
||||||
if (!(map instanceof Map))
|
|
||||||
throw new InvalidTypeValueError(
|
|
||||||
this,
|
|
||||||
map,
|
|
||||||
"value must be an instance of map",
|
|
||||||
);
|
|
||||||
|
|
||||||
// Initialize an empty map.
|
|
||||||
const cloned = new Map<KeyType, ValueType>() as T;
|
|
||||||
|
|
||||||
for (const [key, value] of map.entries()) {
|
|
||||||
// Clone each value of the map.
|
|
||||||
cloned.set(
|
|
||||||
this.keyDefinition.type.clone(key),
|
|
||||||
this.valueDefinition.type.clone(value),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return cloned; // Returning cloned map.
|
|
||||||
}
|
|
||||||
|
|
||||||
applyPatch<T extends Map<KeyType, ValueType>>(
|
|
||||||
currentValue: T | null | undefined,
|
|
||||||
patchValue: SerializedMapType | null | undefined,
|
|
||||||
updateOriginals: boolean,
|
|
||||||
): T | null | undefined {
|
|
||||||
if (patchValue === undefined) return undefined;
|
|
||||||
if (patchValue === null) return null;
|
|
||||||
|
|
||||||
if (typeof patchValue !== "object")
|
|
||||||
throw new InvalidTypeValueError(
|
|
||||||
this,
|
|
||||||
patchValue,
|
|
||||||
"value must be an object",
|
|
||||||
);
|
|
||||||
|
|
||||||
currentValue =
|
|
||||||
currentValue instanceof Map
|
|
||||||
? currentValue
|
|
||||||
: (new Map<KeyType, ValueType>() as T);
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(patchValue)) {
|
|
||||||
// Apply the patch to all values of the map.
|
|
||||||
const patchedKey = this.keyDefinition.type.deserialize(key);
|
|
||||||
const patchedElement = this.valueDefinition.type.applyPatch(
|
|
||||||
currentValue.get(patchedKey),
|
|
||||||
value,
|
|
||||||
updateOriginals,
|
|
||||||
);
|
|
||||||
currentValue.set(patchedKey, patchedElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
return currentValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* New map property definition.
|
|
||||||
* @param keyDefinition Definition of the map keys.
|
|
||||||
* @param valueDefinition Definition of the map values.
|
|
||||||
*/
|
|
||||||
export function map<
|
|
||||||
KeyType,
|
|
||||||
ValueType,
|
|
||||||
SerializedValueType,
|
|
||||||
SerializedMapType extends Record<string, SerializedValueType> = Record<
|
|
||||||
string,
|
|
||||||
SerializedValueType
|
|
||||||
>,
|
|
||||||
>(
|
|
||||||
keyDefinition: Definition<string, KeyType>,
|
|
||||||
valueDefinition: Definition<SerializedValueType, ValueType>,
|
|
||||||
): Definition<SerializedMapType, Map<KeyType, ValueType>> {
|
|
||||||
return define(
|
|
||||||
new MapType<KeyType, ValueType, SerializedValueType, SerializedMapType>(
|
|
||||||
keyDefinition,
|
|
||||||
valueDefinition,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* New map property definition, with string as index.
|
|
||||||
* @param valueDefinition Definition of the map values.
|
|
||||||
*/
|
|
||||||
export function stringMap<
|
|
||||||
ValueType,
|
|
||||||
SerializedValueType,
|
|
||||||
SerializedMapType extends Record<string, SerializedValueType> = Record<
|
|
||||||
string,
|
|
||||||
SerializedValueType
|
|
||||||
>,
|
|
||||||
>(
|
|
||||||
valueDefinition: Definition<SerializedValueType, ValueType>,
|
|
||||||
): Definition<SerializedMapType, Map<string, ValueType>> {
|
|
||||||
return define(
|
|
||||||
new MapType<string, ValueType, SerializedValueType, SerializedMapType>(
|
|
||||||
string(),
|
|
||||||
valueDefinition,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -2,62 +2,50 @@ import {Type} from "./type";
|
||||||
import {define, Definition} from "../property-definition";
|
import {define, Definition} from "../property-definition";
|
||||||
import {
|
import {
|
||||||
GenericModelManager,
|
GenericModelManager,
|
||||||
IdentifierDefinition,
|
IdentifierDefinition, DeclaredModelManager,
|
||||||
DeclaredModelManager,
|
|
||||||
ModelInstance,
|
ModelInstance,
|
||||||
ModelManager,
|
ModelManager,
|
||||||
ModelShape,
|
ModelShape,
|
||||||
SerializedModel,
|
SerializedModel
|
||||||
} from "../model";
|
} from "../model";
|
||||||
import {InvalidTypeValueError} from "../../errors";
|
import {InvalidTypeValueError} from "../../errors";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type of a Sharkitek model value.
|
* Type of a Sharkitek model value.
|
||||||
*/
|
*/
|
||||||
export class ModelType<
|
export class ModelType<T extends object, Shape extends ModelShape<T>, Identifier extends IdentifierDefinition<T, Shape>> extends Type<SerializedModel<T, Shape>, ModelInstance<T, Shape, Identifier>>
|
||||||
T extends object,
|
{
|
||||||
Shape extends ModelShape<T>,
|
|
||||||
Identifier extends IdentifierDefinition<T, Shape>,
|
|
||||||
> extends Type<SerializedModel<T, Shape>, ModelInstance<T, Shape, Identifier>> {
|
|
||||||
/**
|
/**
|
||||||
* Initialize a new model type of a Sharkitek model property.
|
* Initialize a new model type of a Sharkitek model property.
|
||||||
* @param declaredModelManager Model manager.
|
* @param declaredModelManager Model manager.
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(protected declaredModelManager: DeclaredModelManager<T, Shape, Identifier>)
|
||||||
protected declaredModelManager: DeclaredModelManager<T, Shape, Identifier>,
|
{
|
||||||
) {
|
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve the defined model using the declared model, that can be defined lazily.
|
* Resolve the defined model using the declared model, that can be defined lazily.
|
||||||
*/
|
*/
|
||||||
get definedModel(): ModelManager<T, Shape, Identifier> {
|
get definedModel(): ModelManager<T, Shape, Identifier>
|
||||||
return typeof this.declaredModelManager == "object"
|
{
|
||||||
? this.declaredModelManager
|
return typeof this.declaredModelManager == "object" ? this.declaredModelManager : this.declaredModelManager();
|
||||||
: this.declaredModelManager();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize(
|
serialize(value: ModelInstance<T, Shape, Identifier>|null|undefined): SerializedModel<T, Shape>|null|undefined
|
||||||
value: ModelInstance<T, Shape, Identifier> | null | undefined,
|
{
|
||||||
): SerializedModel<T, Shape> | null | undefined {
|
|
||||||
if (value === undefined) return undefined;
|
if (value === undefined) return undefined;
|
||||||
if (value === null) return null;
|
if (value === null) return null;
|
||||||
|
|
||||||
if (!(value instanceof this.definedModel.definition.Class))
|
if (!(value instanceof this.definedModel.definition.Class))
|
||||||
throw new InvalidTypeValueError(
|
throw new InvalidTypeValueError(this, value, `value must be a compatible model (given ${value.constructor.name}, expected ${this.definedModel.definition.Class.name})`);
|
||||||
this,
|
|
||||||
value,
|
|
||||||
`value must be a compatible model (given ${value.constructor.name}, expected ${this.definedModel.definition.Class.name})`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Serializing the given model.
|
// Serializing the given model.
|
||||||
return this.definedModel.model(value).serialize();
|
return this.definedModel.model(value).serialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
deserialize(
|
deserialize(value: SerializedModel<T, Shape>|null|undefined): ModelInstance<T, Shape, Identifier>|null|undefined
|
||||||
value: SerializedModel<T, Shape> | null | undefined,
|
{
|
||||||
): ModelInstance<T, Shape, Identifier> | null | undefined {
|
|
||||||
if (value === undefined) return undefined;
|
if (value === undefined) return undefined;
|
||||||
if (value === null) return null;
|
if (value === null) return null;
|
||||||
|
|
||||||
|
@ -68,158 +56,90 @@ export class ModelType<
|
||||||
return this.definedModel.parse(value);
|
return this.definedModel.parse(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
serializeDiff(
|
serializeDiff(value: ModelInstance<T, Shape, Identifier>|null|undefined): Partial<SerializedModel<T, Shape>>|null|undefined
|
||||||
value: ModelInstance<T, Shape, Identifier> | null | undefined,
|
{
|
||||||
): Partial<SerializedModel<T, Shape>> | null | undefined {
|
|
||||||
if (value === undefined) return undefined;
|
if (value === undefined) return undefined;
|
||||||
if (value === null) return null;
|
if (value === null) return null;
|
||||||
|
|
||||||
if (!(value instanceof this.definedModel.definition.Class))
|
if (!(value instanceof this.definedModel.definition.Class))
|
||||||
throw new InvalidTypeValueError(
|
throw new InvalidTypeValueError(this, value, `value must be a compatible model (given ${value.constructor.name}, expected ${this.definedModel.definition.Class.name})`);
|
||||||
this,
|
|
||||||
value,
|
|
||||||
`value must be a compatible model (given ${value.constructor.name}, expected ${this.definedModel.definition.Class.name})`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Serializing the given model.
|
// Serializing the given model.
|
||||||
return this.definedModel.model(value).serializeDiff();
|
return this.definedModel.model(value).serializeDiff();
|
||||||
}
|
}
|
||||||
|
|
||||||
resetDiff(
|
resetDiff(value: ModelInstance<T, Shape, Identifier>|null|undefined): void
|
||||||
value: ModelInstance<T, Shape, Identifier> | null | undefined,
|
{
|
||||||
): void {
|
|
||||||
if (value === undefined) return;
|
if (value === undefined) return;
|
||||||
if (value === null) return;
|
if (value === null) return;
|
||||||
|
|
||||||
if (!(value instanceof this.definedModel.definition.Class))
|
if (!(value instanceof this.definedModel.definition.Class))
|
||||||
throw new InvalidTypeValueError(
|
throw new InvalidTypeValueError(this, value, `value must be a compatible model (given ${value.constructor.name}, expected ${this.definedModel.definition.Class.name})`);
|
||||||
this,
|
|
||||||
value,
|
|
||||||
`value must be a compatible model (given ${value.constructor.name}, expected ${this.definedModel.definition.Class.name})`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Reset diff of the given model.
|
// Reset diff of the given model.
|
||||||
this.definedModel.model(value).resetDiff();
|
this.definedModel.model(value).resetDiff();
|
||||||
}
|
}
|
||||||
|
|
||||||
hasChanged(
|
hasChanged(originalValue: ModelInstance<T, Shape, Identifier>|null|undefined, currentValue: ModelInstance<T, Shape, Identifier>|null|undefined): boolean
|
||||||
originalValue: ModelInstance<T, Shape, Identifier> | null | undefined,
|
{
|
||||||
currentValue: ModelInstance<T, Shape, Identifier> | null | undefined,
|
|
||||||
): boolean {
|
|
||||||
if (originalValue === undefined) return currentValue !== undefined;
|
if (originalValue === undefined) return currentValue !== undefined;
|
||||||
if (originalValue === null) return currentValue !== null;
|
if (originalValue === null) return currentValue !== null;
|
||||||
if (currentValue === undefined) return true; // Original value is not undefined.
|
if (currentValue === undefined) return true; // Original value is not undefined.
|
||||||
if (currentValue === null) return true; // Original value is not null.
|
if (currentValue === null) return true; // Original value is not null.
|
||||||
|
|
||||||
if (!(originalValue instanceof this.definedModel.definition.Class))
|
if (!(originalValue instanceof this.definedModel.definition.Class))
|
||||||
throw new InvalidTypeValueError(
|
throw new InvalidTypeValueError(this, originalValue, `value must be a compatible model (given ${originalValue.constructor.name}, expected ${this.definedModel.definition.Class.name})`);
|
||||||
this,
|
|
||||||
originalValue,
|
|
||||||
`value must be a compatible model (given ${originalValue.constructor.name}, expected ${this.definedModel.definition.Class.name})`,
|
|
||||||
);
|
|
||||||
if (!(currentValue instanceof this.definedModel.definition.Class))
|
if (!(currentValue instanceof this.definedModel.definition.Class))
|
||||||
throw new InvalidTypeValueError(
|
throw new InvalidTypeValueError(this, currentValue, `value must be a compatible model (given ${currentValue.constructor.name}, expected ${this.definedModel.definition.Class.name})`);
|
||||||
this,
|
|
||||||
currentValue,
|
|
||||||
`value must be a compatible model (given ${currentValue.constructor.name}, expected ${this.definedModel.definition.Class.name})`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// If the current value is dirty, it has changed.
|
// If the current value is dirty, it has changed.
|
||||||
return this.definedModel.model(currentValue).isDirty();
|
return this.definedModel.model(currentValue).isDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
serializedHasChanged(
|
serializedHasChanged(originalValue: SerializedModel<T, Shape> | null | undefined, currentValue: SerializedModel<T, Shape> | null | undefined): boolean
|
||||||
originalValue: SerializedModel<T, Shape> | null | undefined,
|
{
|
||||||
currentValue: SerializedModel<T, Shape> | null | undefined,
|
|
||||||
): boolean {
|
|
||||||
if (originalValue === undefined) return currentValue !== undefined;
|
if (originalValue === undefined) return currentValue !== undefined;
|
||||||
if (originalValue === null) return currentValue !== null;
|
if (originalValue === null) return currentValue !== null;
|
||||||
if (currentValue === undefined) return true; // Original value is not undefined.
|
if (currentValue === undefined) return true; // Original value is not undefined.
|
||||||
if (currentValue === null) return true; // Original value is not null.
|
if (currentValue === null) return true; // Original value is not null.
|
||||||
|
|
||||||
if (typeof originalValue !== "object" || Array.isArray(originalValue))
|
if (typeof originalValue !== "object" || Array.isArray(originalValue))
|
||||||
throw new InvalidTypeValueError(
|
throw new InvalidTypeValueError(this, originalValue, "value must be an object");
|
||||||
this,
|
|
||||||
originalValue,
|
|
||||||
"value must be an object",
|
|
||||||
);
|
|
||||||
if (typeof currentValue !== "object" || Array.isArray(currentValue))
|
if (typeof currentValue !== "object" || Array.isArray(currentValue))
|
||||||
throw new InvalidTypeValueError(
|
throw new InvalidTypeValueError(this, currentValue, "value must be an object");
|
||||||
this,
|
|
||||||
currentValue,
|
|
||||||
"value must be an object",
|
|
||||||
);
|
|
||||||
|
|
||||||
// If any property has changed, the value has changed.
|
// If any property has changed, the value has changed.
|
||||||
for (const property of this.definedModel.properties)
|
for (const property of this.definedModel.properties)
|
||||||
if (
|
if (property.definition.type.serializedHasChanged(originalValue?.[property.name], currentValue?.[property.name]))
|
||||||
property.definition.type.serializedHasChanged(
|
|
||||||
originalValue?.[property.name],
|
|
||||||
currentValue?.[property.name],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return false; // No change detected.
|
return false; // No change detected.
|
||||||
}
|
}
|
||||||
|
|
||||||
clone<Type extends ModelInstance<T, Shape, Identifier>>(
|
clone<Type extends ModelInstance<T, Shape, Identifier>>(value: Type|null|undefined): Type
|
||||||
value: Type | null | undefined,
|
{
|
||||||
): Type {
|
|
||||||
// Handle NULL / undefined values.
|
// Handle NULL / undefined values.
|
||||||
if (!value) return super.clone(value);
|
if (!value) return super.clone(value);
|
||||||
|
|
||||||
if (!(value instanceof this.definedModel.definition.Class))
|
if (!(value instanceof this.definedModel.definition.Class))
|
||||||
throw new InvalidTypeValueError(
|
throw new InvalidTypeValueError(this, value, `value must be a compatible model (given ${value.constructor.name}, expected ${this.definedModel.definition.Class.name})`);
|
||||||
this,
|
|
||||||
value,
|
|
||||||
`value must be a compatible model (given ${value.constructor.name}, expected ${this.definedModel.definition.Class.name})`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return this.definedModel.model(value).clone() as Type;
|
return this.definedModel.model(value).clone() as Type;
|
||||||
}
|
}
|
||||||
|
|
||||||
applyPatch<Type extends ModelInstance<T, Shape, Identifier>>(
|
|
||||||
currentValue: Type | null | undefined,
|
|
||||||
patchValue: SerializedModel<T, Shape> | null | undefined,
|
|
||||||
updateOriginals: boolean,
|
|
||||||
): Type | null | undefined {
|
|
||||||
if (patchValue === undefined) return undefined;
|
|
||||||
if (patchValue === null) return null;
|
|
||||||
|
|
||||||
if (typeof patchValue !== "object" || Array.isArray(patchValue))
|
|
||||||
throw new InvalidTypeValueError(
|
|
||||||
this,
|
|
||||||
patchValue,
|
|
||||||
"value must be an object",
|
|
||||||
);
|
|
||||||
|
|
||||||
return this.definedModel
|
|
||||||
.model(currentValue)
|
|
||||||
.applyPatch(patchValue, updateOriginals) as Type;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* New model property definition.
|
* New model property definition.
|
||||||
* @param definedModel Model manager.
|
* @param definedModel Model manager.
|
||||||
*/
|
*/
|
||||||
export function model<
|
export function model<T extends object, Shape extends ModelShape<T>, Identifier extends IdentifierDefinition<T, Shape>>(
|
||||||
T extends object,
|
definedModel: DeclaredModelManager<T, Shape, Identifier>
|
||||||
Shape extends ModelShape<T>,
|
): Definition<SerializedModel<T, Shape>, ModelInstance<T, Shape, Identifier>>
|
||||||
Identifier extends IdentifierDefinition<T, Shape>,
|
{
|
||||||
>(
|
|
||||||
definedModel: DeclaredModelManager<T, Shape, Identifier>,
|
|
||||||
): Definition<SerializedModel<T, Shape>, ModelInstance<T, Shape, Identifier>> {
|
|
||||||
return define(new ModelType(definedModel));
|
return define(new ModelType(definedModel));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export function circular<T extends object>(definedModel: () => any): () => GenericModelManager<T>
|
||||||
* Utility function to fix circular dependencies issues.
|
{
|
||||||
* @param definedModel A function returning the model to use.
|
|
||||||
*/
|
|
||||||
export function circular<T extends object>(
|
|
||||||
definedModel: () => any,
|
|
||||||
): () => GenericModelManager<T> {
|
|
||||||
return definedModel;
|
return definedModel;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,23 +5,24 @@ import {InvalidTypeValueError} from "../../errors";
|
||||||
/**
|
/**
|
||||||
* Type of any numeric value.
|
* Type of any numeric value.
|
||||||
*/
|
*/
|
||||||
export class NumericType extends Type<number, number> {
|
export class NumericType extends Type<number, number>
|
||||||
deserialize(value: number | null | undefined): number | null | undefined {
|
{
|
||||||
|
deserialize(value: number|null|undefined): number|null|undefined
|
||||||
|
{
|
||||||
if (value === undefined) return undefined;
|
if (value === undefined) return undefined;
|
||||||
if (value === null) return null;
|
if (value === null) return null;
|
||||||
|
|
||||||
if (typeof value !== "number")
|
if (typeof value !== "number") throw new InvalidTypeValueError(this, value, "value must be a number");
|
||||||
throw new InvalidTypeValueError(this, value, "value must be a number");
|
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize(value: number | null | undefined): number | null | undefined {
|
serialize(value: number|null|undefined): number|null|undefined
|
||||||
|
{
|
||||||
if (value === undefined) return undefined;
|
if (value === undefined) return undefined;
|
||||||
if (value === null) return null;
|
if (value === null) return null;
|
||||||
|
|
||||||
if (typeof value !== "number")
|
if (typeof value !== "number") throw new InvalidTypeValueError(this, value, "value must be a number");
|
||||||
throw new InvalidTypeValueError(this, value, "value must be a number");
|
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
@ -30,6 +31,7 @@ export class NumericType extends Type<number, number> {
|
||||||
/**
|
/**
|
||||||
* New numeric property definition.
|
* New numeric property definition.
|
||||||
*/
|
*/
|
||||||
export function numeric(): Definition<number, number> {
|
export function numeric(): Definition<number, number>
|
||||||
|
{
|
||||||
return define(new NumericType());
|
return define(new NumericType());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,13 @@
|
||||||
import {Type} from "./type";
|
import {Type} from "./type";
|
||||||
import {define, Definition, UnknownDefinition} from "../property-definition";
|
import {define, Definition} from "../property-definition";
|
||||||
import {
|
import {ModelProperties, ModelPropertiesValues, ModelProperty, ModelShape, SerializedModel} from "../model";
|
||||||
ModelProperties,
|
|
||||||
ModelPropertiesValues,
|
|
||||||
ModelProperty,
|
|
||||||
ModelShape,
|
|
||||||
SerializedModel,
|
|
||||||
} from "../model";
|
|
||||||
import {InvalidTypeValueError} from "../../errors";
|
import {InvalidTypeValueError} from "../../errors";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type of a custom object.
|
* Type of a custom object.
|
||||||
*/
|
*/
|
||||||
export class ObjectType<
|
export class ObjectType<Shape extends ModelShape<T>, T extends object> extends Type<SerializedModel<T, Shape>, ModelPropertiesValues<T, Shape>>
|
||||||
Shape extends ModelShape<T>,
|
{
|
||||||
T extends object,
|
|
||||||
> extends Type<SerializedModel<T, Shape>, ModelPropertiesValues<T, Shape>> {
|
|
||||||
/**
|
/**
|
||||||
* Defined properties.
|
* Defined properties.
|
||||||
*/
|
*/
|
||||||
|
@ -25,7 +17,8 @@ export class ObjectType<
|
||||||
* Initialize a new object type of a Sharkitek model property.
|
* Initialize a new object type of a Sharkitek model property.
|
||||||
* @param shape
|
* @param shape
|
||||||
*/
|
*/
|
||||||
constructor(readonly shape: Shape) {
|
constructor(readonly shape: Shape)
|
||||||
|
{
|
||||||
super();
|
super();
|
||||||
this.initProperties();
|
this.initProperties();
|
||||||
}
|
}
|
||||||
|
@ -34,11 +27,12 @@ export class ObjectType<
|
||||||
* Initialize properties iterator from the object shape.
|
* Initialize properties iterator from the object shape.
|
||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
protected initProperties(): void {
|
protected initProperties(): void
|
||||||
|
{
|
||||||
// Build an array of model properties from the object shape.
|
// Build an array of model properties from the object shape.
|
||||||
this.properties = [];
|
this.properties = [];
|
||||||
for (const propertyName in this.shape) {
|
for (const propertyName in this.shape)
|
||||||
// For each property, build a model property object.
|
{ // For each property, build a model property object.
|
||||||
this.properties.push({
|
this.properties.push({
|
||||||
name: propertyName,
|
name: propertyName,
|
||||||
definition: this.shape[propertyName],
|
definition: this.shape[propertyName],
|
||||||
|
@ -47,9 +41,8 @@ export class ObjectType<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deserialize(
|
deserialize(value: SerializedModel<T, Shape>|null|undefined): ModelPropertiesValues<T, Shape>|null|undefined
|
||||||
value: SerializedModel<T, Shape> | null | undefined,
|
{
|
||||||
): ModelPropertiesValues<T, Shape> | null | undefined {
|
|
||||||
if (value === undefined) return undefined;
|
if (value === undefined) return undefined;
|
||||||
if (value === null) return null;
|
if (value === null) return null;
|
||||||
|
|
||||||
|
@ -59,18 +52,16 @@ export class ObjectType<
|
||||||
// Initialize an empty object.
|
// Initialize an empty object.
|
||||||
const obj: Partial<ModelPropertiesValues<T, Shape>> = {};
|
const obj: Partial<ModelPropertiesValues<T, Shape>> = {};
|
||||||
|
|
||||||
for (const property of this.properties) {
|
for (const property of this.properties)
|
||||||
// For each defined property, deserialize its value according to its type.
|
{ // For each defined property, deserialize its value according to its type.
|
||||||
(obj[property.name as keyof T] as any) =
|
(obj[property.name as keyof T] as any) = property.definition.type.deserialize(value?.[property.name]);
|
||||||
property.definition.type.deserialize(value?.[property.name]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj as ModelPropertiesValues<T, Shape>; // Returning serialized object.
|
return obj as ModelPropertiesValues<T, Shape>; // Returning serialized object.
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize(
|
serialize(value: ModelPropertiesValues<T, Shape>|null|undefined): SerializedModel<T, Shape>|null|undefined
|
||||||
value: ModelPropertiesValues<T, Shape> | null | undefined,
|
{
|
||||||
): SerializedModel<T, Shape> | null | undefined {
|
|
||||||
if (value === undefined) return undefined;
|
if (value === undefined) return undefined;
|
||||||
if (value === null) return null;
|
if (value === null) return null;
|
||||||
|
|
||||||
|
@ -80,8 +71,8 @@ export class ObjectType<
|
||||||
// Creating an empty serialized object.
|
// Creating an empty serialized object.
|
||||||
const serializedObject: Partial<SerializedModel<T, Shape>> = {};
|
const serializedObject: Partial<SerializedModel<T, Shape>> = {};
|
||||||
|
|
||||||
for (const property of this.properties) {
|
for (const property of this.properties)
|
||||||
// For each property, adding it to the serialized object.
|
{ // For each property, adding it to the serialized object.
|
||||||
serializedObject[property.name] = property.definition.type.serialize(
|
serializedObject[property.name] = property.definition.type.serialize(
|
||||||
// keyof Shape is a subset of keyof T.
|
// keyof Shape is a subset of keyof T.
|
||||||
value?.[property.name as keyof T],
|
value?.[property.name as keyof T],
|
||||||
|
@ -91,9 +82,8 @@ export class ObjectType<
|
||||||
return serializedObject as SerializedModel<T, Shape>; // Returning the serialized object.
|
return serializedObject as SerializedModel<T, Shape>; // Returning the serialized object.
|
||||||
}
|
}
|
||||||
|
|
||||||
serializeDiff(
|
serializeDiff(value: ModelPropertiesValues<T, Shape>|null|undefined): Partial<SerializedModel<T, Shape>>|null|undefined
|
||||||
value: ModelPropertiesValues<T, Shape> | null | undefined,
|
{
|
||||||
): Partial<SerializedModel<T, Shape>> | null | undefined {
|
|
||||||
if (value === undefined) return undefined;
|
if (value === undefined) return undefined;
|
||||||
if (value === null) return null;
|
if (value === null) return null;
|
||||||
|
|
||||||
|
@ -103,8 +93,8 @@ export class ObjectType<
|
||||||
// Creating an empty serialized object.
|
// Creating an empty serialized object.
|
||||||
const serializedObject: Partial<SerializedModel<T, Shape>> = {};
|
const serializedObject: Partial<SerializedModel<T, Shape>> = {};
|
||||||
|
|
||||||
for (const property of this.properties) {
|
for (const property of this.properties)
|
||||||
// For each property, adding it to the serialized object.
|
{ // For each property, adding it to the serialized object.
|
||||||
serializedObject[property.name] = property.definition.type.serializeDiff(
|
serializedObject[property.name] = property.definition.type.serializeDiff(
|
||||||
// keyof Shape is a subset of keyof T.
|
// keyof Shape is a subset of keyof T.
|
||||||
value?.[property.name as keyof T],
|
value?.[property.name as keyof T],
|
||||||
|
@ -114,7 +104,8 @@ export class ObjectType<
|
||||||
return serializedObject as SerializedModel<T, Shape>; // Returning the serialized object.
|
return serializedObject as SerializedModel<T, Shape>; // Returning the serialized object.
|
||||||
}
|
}
|
||||||
|
|
||||||
resetDiff(value: ModelPropertiesValues<T, Shape> | null | undefined) {
|
resetDiff(value: ModelPropertiesValues<T, Shape>|null|undefined)
|
||||||
|
{
|
||||||
if (value === undefined) return;
|
if (value === undefined) return;
|
||||||
if (value === null) return;
|
if (value === null) return;
|
||||||
|
|
||||||
|
@ -122,84 +113,53 @@ export class ObjectType<
|
||||||
throw new InvalidTypeValueError(this, value, "value must be an object");
|
throw new InvalidTypeValueError(this, value, "value must be an object");
|
||||||
|
|
||||||
// For each property, reset its diff.
|
// For each property, reset its diff.
|
||||||
// keyof Shape is a subset of keyof T.
|
|
||||||
for (const property of this.properties)
|
for (const property of this.properties)
|
||||||
|
// keyof Shape is a subset of keyof T.
|
||||||
property.definition.type.resetDiff(value?.[property.name as keyof T]);
|
property.definition.type.resetDiff(value?.[property.name as keyof T]);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasChanged(
|
hasChanged(originalValue: ModelPropertiesValues<T, Shape>|null|undefined, currentValue: ModelPropertiesValues<T, Shape>|null|undefined): boolean
|
||||||
originalValue: ModelPropertiesValues<T, Shape> | null | undefined,
|
{
|
||||||
currentValue: ModelPropertiesValues<T, Shape> | null | undefined,
|
|
||||||
): boolean {
|
|
||||||
if (originalValue === undefined) return currentValue !== undefined;
|
if (originalValue === undefined) return currentValue !== undefined;
|
||||||
if (originalValue === null) return currentValue !== null;
|
if (originalValue === null) return currentValue !== null;
|
||||||
if (currentValue === undefined) return true; // Original value is not undefined.
|
if (currentValue === undefined) return true; // Original value is not undefined.
|
||||||
if (currentValue === null) return true; // Original value is not null.
|
if (currentValue === null) return true; // Original value is not null.
|
||||||
|
|
||||||
if (typeof originalValue !== "object" || Array.isArray(originalValue))
|
if (typeof originalValue !== "object" || Array.isArray(originalValue))
|
||||||
throw new InvalidTypeValueError(
|
throw new InvalidTypeValueError(this, originalValue, "value must be an object");
|
||||||
this,
|
|
||||||
originalValue,
|
|
||||||
"value must be an object",
|
|
||||||
);
|
|
||||||
if (typeof currentValue !== "object" || Array.isArray(currentValue))
|
if (typeof currentValue !== "object" || Array.isArray(currentValue))
|
||||||
throw new InvalidTypeValueError(
|
throw new InvalidTypeValueError(this, currentValue, "value must be an object");
|
||||||
this,
|
|
||||||
currentValue,
|
|
||||||
"value must be an object",
|
|
||||||
);
|
|
||||||
|
|
||||||
// If any property has changed, the value has changed.
|
// If any property has changed, the value has changed.
|
||||||
for (const property of this.properties)
|
for (const property of this.properties)
|
||||||
if (
|
if (property.definition.type.hasChanged(originalValue?.[property.name as keyof T], currentValue?.[property.name as keyof T]))
|
||||||
property.definition.type.hasChanged(
|
|
||||||
originalValue?.[property.name as keyof T],
|
|
||||||
currentValue?.[property.name as keyof T],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return false; // No change detected.
|
return false; // No change detected.
|
||||||
}
|
}
|
||||||
|
|
||||||
serializedHasChanged(
|
serializedHasChanged(originalValue: SerializedModel<T, Shape>|null|undefined, currentValue: SerializedModel<T, Shape>|null|undefined): boolean
|
||||||
originalValue: SerializedModel<T, Shape> | null | undefined,
|
{
|
||||||
currentValue: SerializedModel<T, Shape> | null | undefined,
|
|
||||||
): boolean {
|
|
||||||
if (originalValue === undefined) return currentValue !== undefined;
|
if (originalValue === undefined) return currentValue !== undefined;
|
||||||
if (originalValue === null) return currentValue !== null;
|
if (originalValue === null) return currentValue !== null;
|
||||||
if (currentValue === undefined) return true; // Original value is not undefined.
|
if (currentValue === undefined) return true; // Original value is not undefined.
|
||||||
if (currentValue === null) return true; // Original value is not null.
|
if (currentValue === null) return true; // Original value is not null.
|
||||||
|
|
||||||
if (typeof originalValue !== "object" || Array.isArray(originalValue))
|
if (typeof originalValue !== "object" || Array.isArray(originalValue))
|
||||||
throw new InvalidTypeValueError(
|
throw new InvalidTypeValueError(this, originalValue, "value must be an object");
|
||||||
this,
|
|
||||||
originalValue,
|
|
||||||
"value must be an object",
|
|
||||||
);
|
|
||||||
if (typeof currentValue !== "object" || Array.isArray(currentValue))
|
if (typeof currentValue !== "object" || Array.isArray(currentValue))
|
||||||
throw new InvalidTypeValueError(
|
throw new InvalidTypeValueError(this, currentValue, "value must be an object");
|
||||||
this,
|
|
||||||
currentValue,
|
|
||||||
"value must be an object",
|
|
||||||
);
|
|
||||||
|
|
||||||
// If any property has changed, the value has changed.
|
// If any property has changed, the value has changed.
|
||||||
for (const property of this.properties)
|
for (const property of this.properties)
|
||||||
if (
|
if (property.definition.type.serializedHasChanged(originalValue?.[property.name], currentValue?.[property.name]))
|
||||||
property.definition.type.serializedHasChanged(
|
|
||||||
originalValue?.[property.name],
|
|
||||||
currentValue?.[property.name],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return false; // No change detected.
|
return false; // No change detected.
|
||||||
}
|
}
|
||||||
|
|
||||||
clone<Type extends ModelPropertiesValues<T, Shape>>(
|
clone<Type extends ModelPropertiesValues<T, Shape>>(value: Type|null|undefined): Type
|
||||||
value: Type | null | undefined,
|
{
|
||||||
): Type {
|
|
||||||
// Handle NULL / undefined object.
|
// Handle NULL / undefined object.
|
||||||
if (!value) return super.clone(value);
|
if (!value) return super.clone(value);
|
||||||
|
|
||||||
|
@ -209,59 +169,20 @@ export class ObjectType<
|
||||||
// Initialize an empty object.
|
// Initialize an empty object.
|
||||||
const cloned: Partial<ModelPropertiesValues<T, Shape>> = {};
|
const cloned: Partial<ModelPropertiesValues<T, Shape>> = {};
|
||||||
|
|
||||||
for (const property of this.properties) {
|
for (const property of this.properties)
|
||||||
// For each defined property, clone it.
|
{ // For each defined property, clone it.
|
||||||
cloned[property.name as keyof T] = property.definition.type.clone(
|
cloned[property.name as keyof T] = property.definition.type.clone(value?.[property.name]);
|
||||||
value?.[property.name],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return cloned as Type; // Returning cloned object.
|
return cloned as Type; // Returning cloned object.
|
||||||
}
|
}
|
||||||
|
|
||||||
applyPatch<Type extends ModelPropertiesValues<T, Shape>>(
|
|
||||||
currentValue: Type | null | undefined,
|
|
||||||
patchValue: SerializedModel<T, Shape> | null | undefined,
|
|
||||||
updateOriginals: boolean,
|
|
||||||
): Type | null | undefined {
|
|
||||||
if (patchValue === undefined) return undefined;
|
|
||||||
if (patchValue === null) return null;
|
|
||||||
|
|
||||||
if (typeof patchValue !== "object" || Array.isArray(patchValue))
|
|
||||||
throw new InvalidTypeValueError(
|
|
||||||
this,
|
|
||||||
patchValue,
|
|
||||||
"value must be an object",
|
|
||||||
);
|
|
||||||
|
|
||||||
const patchedValue: Partial<Type> =
|
|
||||||
typeof currentValue === "object" && currentValue !== null
|
|
||||||
? currentValue
|
|
||||||
: {};
|
|
||||||
|
|
||||||
for (const key in patchValue) {
|
|
||||||
// Apply the patch to each property of the patch value.
|
|
||||||
const propertyDef = this.shape[key];
|
|
||||||
if (propertyDef)
|
|
||||||
patchedValue[key as keyof Type] = (
|
|
||||||
propertyDef as UnknownDefinition
|
|
||||||
).type.applyPatch(
|
|
||||||
currentValue?.[key as keyof Type],
|
|
||||||
patchValue[key],
|
|
||||||
updateOriginals,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return patchedValue as Type;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* New object property definition.
|
* New object property definition.
|
||||||
* @param shape Shape of the object.
|
* @param shape Shape of the object.
|
||||||
*/
|
*/
|
||||||
export function object<Shape extends ModelShape<T>, T extends object>(
|
export function object<Shape extends ModelShape<T>, T extends object>(shape: Shape): Definition<SerializedModel<T, Shape>, ModelPropertiesValues<T, Shape>>
|
||||||
shape: Shape,
|
{
|
||||||
): Definition<SerializedModel<T, Shape>, ModelPropertiesValues<T, Shape>> {
|
|
||||||
return define(new ObjectType(shape));
|
return define(new ObjectType(shape));
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,15 +4,18 @@ import {define, Definition} from "../property-definition";
|
||||||
/**
|
/**
|
||||||
* Type of any string value.
|
* Type of any string value.
|
||||||
*/
|
*/
|
||||||
export class StringType extends Type<string, string> {
|
export class StringType extends Type<string, string>
|
||||||
deserialize(value: string | null | undefined): string | null | undefined {
|
{
|
||||||
|
deserialize(value: string|null|undefined): string|null|undefined
|
||||||
|
{
|
||||||
if (value === undefined) return undefined;
|
if (value === undefined) return undefined;
|
||||||
if (value === null) return null;
|
if (value === null) return null;
|
||||||
|
|
||||||
return String(value);
|
return String(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize(value: string | null | undefined): string | null | undefined {
|
serialize(value: string|null|undefined): string|null|undefined
|
||||||
|
{
|
||||||
if (value === undefined) return undefined;
|
if (value === undefined) return undefined;
|
||||||
if (value === null) return null;
|
if (value === null) return null;
|
||||||
|
|
||||||
|
@ -23,6 +26,7 @@ export class StringType extends Type<string, string> {
|
||||||
/**
|
/**
|
||||||
* New string property definition.
|
* New string property definition.
|
||||||
*/
|
*/
|
||||||
export function string(): Definition<string, string> {
|
export function string(): Definition<string, string>
|
||||||
|
{
|
||||||
return define(new StringType());
|
return define(new StringType());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,26 @@
|
||||||
/**
|
/**
|
||||||
* Abstract class of a Sharkitek model property type.
|
* Abstract class of a Sharkitek model property type.
|
||||||
*/
|
*/
|
||||||
export abstract class Type<SerializedType, ModelType> {
|
export abstract class Type<SerializedType, ModelType>
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* Serialize the given value of a Sharkitek model property.
|
* Serialize the given value of a Sharkitek model property.
|
||||||
* @param value Value to serialize.
|
* @param value Value to serialize.
|
||||||
*/
|
*/
|
||||||
abstract serialize(
|
abstract serialize(value: ModelType|null|undefined): SerializedType|null|undefined;
|
||||||
value: ModelType | null | undefined,
|
|
||||||
): SerializedType | null | undefined;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deserialize the given value of a serialized Sharkitek model.
|
* Deserialize the given value of a serialized Sharkitek model.
|
||||||
* @param value Value to deserialize.
|
* @param value Value to deserialize.
|
||||||
*/
|
*/
|
||||||
abstract deserialize(
|
abstract deserialize(value: SerializedType|null|undefined): ModelType|null|undefined;
|
||||||
value: SerializedType | null | undefined,
|
|
||||||
): ModelType | null | undefined;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serialize the given value only if it has changed.
|
* Serialize the given value only if it has changed.
|
||||||
* @param value Value to deserialize.
|
* @param value Value to deserialize.
|
||||||
*/
|
*/
|
||||||
serializeDiff(
|
serializeDiff(value: ModelType|null|undefined): Partial<SerializedType>|null|undefined
|
||||||
value: ModelType | null | undefined,
|
{
|
||||||
): Partial<SerializedType> | null | undefined {
|
|
||||||
return this.serialize(value); // By default, nothing changes.
|
return this.serialize(value); // By default, nothing changes.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,10 +28,8 @@ export abstract class Type<SerializedType, ModelType> {
|
||||||
* Reset the difference between the original value and the current one.
|
* Reset the difference between the original value and the current one.
|
||||||
* @param value Value for which reset diff data.
|
* @param value Value for which reset diff data.
|
||||||
*/
|
*/
|
||||||
resetDiff(
|
resetDiff(value: ModelType|null|undefined): void
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
{
|
||||||
value: ModelType | null | undefined,
|
|
||||||
): void {
|
|
||||||
// By default, nothing to do.
|
// By default, nothing to do.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,10 +38,8 @@ export abstract class Type<SerializedType, ModelType> {
|
||||||
* @param originalValue Original value.
|
* @param originalValue Original value.
|
||||||
* @param currentValue Current value.
|
* @param currentValue Current value.
|
||||||
*/
|
*/
|
||||||
hasChanged(
|
hasChanged(originalValue: ModelType|null|undefined, currentValue: ModelType|null|undefined): boolean
|
||||||
originalValue: ModelType | null | undefined,
|
{
|
||||||
currentValue: ModelType | null | undefined,
|
|
||||||
): boolean {
|
|
||||||
return originalValue !== currentValue;
|
return originalValue !== currentValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,10 +48,8 @@ export abstract class Type<SerializedType, ModelType> {
|
||||||
* @param originalValue Original serialized value.
|
* @param originalValue Original serialized value.
|
||||||
* @param currentValue Current serialized value.
|
* @param currentValue Current serialized value.
|
||||||
*/
|
*/
|
||||||
serializedHasChanged(
|
serializedHasChanged(originalValue: SerializedType|null|undefined, currentValue: SerializedType|null|undefined): boolean
|
||||||
originalValue: SerializedType | null | undefined,
|
{
|
||||||
currentValue: SerializedType | null | undefined,
|
|
||||||
): boolean {
|
|
||||||
return originalValue !== currentValue;
|
return originalValue !== currentValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,22 +57,8 @@ export abstract class Type<SerializedType, ModelType> {
|
||||||
* Clone the provided value.
|
* Clone the provided value.
|
||||||
* @param value The to clone.
|
* @param value The to clone.
|
||||||
*/
|
*/
|
||||||
clone<T extends ModelType>(value: T | null | undefined): T {
|
clone<T extends ModelType>(value: T|null|undefined): T
|
||||||
|
{
|
||||||
return structuredClone(value);
|
return structuredClone(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply the patch value.
|
|
||||||
* @param currentValue The current property value. Its value can be mutated directly.
|
|
||||||
* @param patchValue The serialized patch value.
|
|
||||||
* @param updateOriginals Indicates if the original properties values must be updated or not.
|
|
||||||
*/
|
|
||||||
applyPatch<T extends ModelType>(
|
|
||||||
currentValue: T | null | undefined,
|
|
||||||
patchValue: SerializedType | null | undefined,
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
updateOriginals: boolean,
|
|
||||||
): T | null | undefined {
|
|
||||||
return this.deserialize(patchValue) as T;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,4 @@
|
||||||
/**
|
/**
|
||||||
* Type definition of a class constructor.
|
* Type definition of a class constructor.
|
||||||
*/
|
*/
|
||||||
export type ConstructorOf<T extends object> = {new (): T};
|
export type ConstructorOf<T extends object> = { new(): T; };
|
||||||
|
|
||||||
/**
|
|
||||||
* Type definition of an original object overridden by another.
|
|
||||||
*/
|
|
||||||
export type Modify<Original, Override> = Omit<Original, keyof Override> &
|
|
||||||
Override;
|
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
import {describe, expect, test} from "vitest";
|
|
||||||
|
|
||||||
import {defineModel, newModel, s} from "../src/library";
|
|
||||||
|
|
||||||
class Testest {
|
|
||||||
foo: string;
|
|
||||||
bar: number;
|
|
||||||
baz: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("model builder", () => {
|
|
||||||
const modelBuilder = newModel(Testest)
|
|
||||||
.property("foo", s.property.string())
|
|
||||||
.property("bar", s.property.decimal())
|
|
||||||
.property("baz", s.property.boolean())
|
|
||||||
.identifier("foo");
|
|
||||||
|
|
||||||
test("build model definition", () => {
|
|
||||||
expect(modelBuilder.definition).toStrictEqual({
|
|
||||||
Class: Testest,
|
|
||||||
identifier: "foo",
|
|
||||||
properties: {
|
|
||||||
foo: s.property.string(),
|
|
||||||
bar: s.property.decimal(),
|
|
||||||
baz: s.property.boolean(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("build a model", () => {
|
|
||||||
expect(modelBuilder.define()).toStrictEqual(
|
|
||||||
defineModel(modelBuilder.definition),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -4,21 +4,12 @@ import {s} from "../src/library";
|
||||||
|
|
||||||
describe("errors", () => {
|
describe("errors", () => {
|
||||||
it("tests type error", () => {
|
it("tests type error", () => {
|
||||||
expect(new TypeError(s.property.string().type).message).toBe(
|
expect((new TypeError(s.property.string().type)).message).toBe("Error in type StringType");
|
||||||
"Error in type StringType",
|
expect((new TypeError(s.property.string().type, "test")).message).toBe("Error in type StringType: test");
|
||||||
);
|
|
||||||
expect(new TypeError(s.property.string().type, "test").message).toBe(
|
|
||||||
"Error in type StringType: test",
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("tests invalid type value error", () => {
|
it("tests invalid type value error", () => {
|
||||||
expect(
|
expect((new InvalidTypeValueError(s.property.decimal().type, ["value"])).message).toBe("Error in type DecimalType: [\"value\"] is an invalid value");
|
||||||
new InvalidTypeValueError(s.property.decimal().type, ["value"]).message,
|
expect((new InvalidTypeValueError(s.property.decimal().type, ["value"], "test")).message).toBe("Error in type DecimalType: test");
|
||||||
).toBe('Error in type DecimalType: ["value"] is an invalid value');
|
|
||||||
expect(
|
|
||||||
new InvalidTypeValueError(s.property.decimal().type, ["value"], "test")
|
|
||||||
.message,
|
|
||||||
).toBe("Error in type DecimalType: test");
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,7 +4,20 @@ import {circular, defineModel, s} from "../src/library";
|
||||||
/**
|
/**
|
||||||
* Test class of an account.
|
* Test class of an account.
|
||||||
*/
|
*/
|
||||||
class Account {
|
class Account
|
||||||
|
{
|
||||||
|
static model = s.defineModel({
|
||||||
|
Class: Account,
|
||||||
|
identifier: "id",
|
||||||
|
properties: {
|
||||||
|
id: s.property.numeric(),
|
||||||
|
createdAt: s.property.date(),
|
||||||
|
name: s.property.string(),
|
||||||
|
email: s.property.string(),
|
||||||
|
active: s.property.boolean(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
id: number;
|
id: number;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -12,70 +25,61 @@ class Account {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccountModel = s.defineModel({
|
|
||||||
Class: Account,
|
|
||||||
identifier: "id",
|
|
||||||
properties: {
|
|
||||||
id: s.property.numeric(),
|
|
||||||
createdAt: s.property.date(),
|
|
||||||
name: s.property.string(),
|
|
||||||
email: s.property.string(),
|
|
||||||
active: s.property.boolean(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test class of an article.
|
* Test class of an article.
|
||||||
*/
|
*/
|
||||||
class Article {
|
class Article
|
||||||
|
{
|
||||||
|
static model = s.defineModel({
|
||||||
|
Class: Article,
|
||||||
|
identifier: "id",
|
||||||
|
properties: {
|
||||||
|
id: s.property.numeric(),
|
||||||
|
title: s.property.string(),
|
||||||
|
authors: s.property.array(s.property.model(() => Account.model)),
|
||||||
|
text: s.property.string(),
|
||||||
|
evaluation: s.property.decimal(),
|
||||||
|
tags: s.property.array(s.property.object({ name: s.property.string() })),
|
||||||
|
comments: s.property.array(s.property.model(() => ArticleComment.model)),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
id: number;
|
id: number;
|
||||||
title: string;
|
title: string;
|
||||||
authors: Account[];
|
authors: Account[];
|
||||||
text: string;
|
text: string;
|
||||||
evaluation: number;
|
evaluation: number;
|
||||||
tags: {name: string}[];
|
tags: { name: string }[];
|
||||||
comments: ArticleComment[];
|
comments: ArticleComment[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const ArticleModel = s.defineModel({
|
|
||||||
Class: Article,
|
|
||||||
identifier: "id",
|
|
||||||
properties: {
|
|
||||||
id: s.property.numeric(),
|
|
||||||
title: s.property.string(),
|
|
||||||
authors: s.property.array(s.property.model(() => AccountModel)),
|
|
||||||
text: s.property.string(),
|
|
||||||
evaluation: s.property.decimal(),
|
|
||||||
tags: s.property.array(s.property.object({name: s.property.string()})),
|
|
||||||
comments: s.property.array(s.property.model(() => ArticleCommentModel)),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test class of a comment on an article.
|
* Test class of a comment on an article.
|
||||||
*/
|
*/
|
||||||
class ArticleComment {
|
class ArticleComment
|
||||||
|
{
|
||||||
|
static model = s.defineModel({
|
||||||
|
Class: ArticleComment,
|
||||||
|
identifier: "id",
|
||||||
|
properties: {
|
||||||
|
id: s.property.numeric(),
|
||||||
|
article: s.property.model(circular<Article>(() => Article.model)),
|
||||||
|
author: s.property.model(() => Account.model),
|
||||||
|
message: s.property.string(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
id: number;
|
id: number;
|
||||||
article?: Article;
|
article?: Article;
|
||||||
author: Account;
|
author: Account;
|
||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ArticleCommentModel = s.defineModel({
|
|
||||||
Class: ArticleComment,
|
|
||||||
identifier: "id",
|
|
||||||
properties: {
|
|
||||||
id: s.property.numeric(),
|
|
||||||
article: s.property.model(circular<Article>(() => ArticleModel)),
|
|
||||||
author: s.property.model(() => AccountModel),
|
|
||||||
message: s.property.string(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a test account instance.
|
* Get a test account instance.
|
||||||
*/
|
*/
|
||||||
function getTestAccount(): Account {
|
function getTestAccount(): Account
|
||||||
|
{
|
||||||
const account = new Account();
|
const account = new Account();
|
||||||
account.id = 52;
|
account.id = 52;
|
||||||
account.createdAt = new Date();
|
account.createdAt = new Date();
|
||||||
|
@ -85,60 +89,40 @@ function getTestAccount(): Account {
|
||||||
return account;
|
return account;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTestArticle(): Article {
|
function getTestArticle(): Article
|
||||||
|
{
|
||||||
const article = new Article();
|
const article = new Article();
|
||||||
article.id = 1;
|
article.id = 1;
|
||||||
article.title = "this is a test";
|
article.title = "this is a test";
|
||||||
article.text = "this is a long test.";
|
article.text = "this is a long test.";
|
||||||
article.evaluation = 25.23;
|
article.evaluation = 25.23;
|
||||||
article.tags = [{name: "test"}, {name: "foo"}];
|
article.tags = [
|
||||||
|
{ name: "test" },
|
||||||
|
{ name: "foo" },
|
||||||
|
];
|
||||||
article.authors = [getTestAccount()];
|
article.authors = [getTestAccount()];
|
||||||
article.comments = [];
|
article.comments = [];
|
||||||
return article;
|
return article;
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("model", () => {
|
describe("model", () => {
|
||||||
it("defines a new model, extending an existing one", () => {
|
|
||||||
class ExtendedAccount extends Account {
|
|
||||||
extendedProperty: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ExtendedAccountModel = s.extend(AccountModel, {
|
|
||||||
Class: ExtendedAccount,
|
|
||||||
properties: {
|
|
||||||
extendedProperty: s.property.string(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(ExtendedAccountModel.definition).toEqual({
|
|
||||||
Class: ExtendedAccount,
|
|
||||||
identifier: "id",
|
|
||||||
properties: {
|
|
||||||
id: s.property.numeric(),
|
|
||||||
createdAt: s.property.date(),
|
|
||||||
name: s.property.string(),
|
|
||||||
email: s.property.string(),
|
|
||||||
active: s.property.boolean(),
|
|
||||||
extendedProperty: s.property.string(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("initializes a new model", () => {
|
it("initializes a new model", () => {
|
||||||
const article = getTestArticle();
|
const article = getTestArticle();
|
||||||
const newModel = ArticleModel.model(article);
|
const newModel = Article.model.model(article);
|
||||||
expect(newModel.instance).toBe(article);
|
expect(newModel.instance).toBe(article);
|
||||||
});
|
});
|
||||||
it("gets a model state from its instance", () => {
|
it("gets a model state from its instance", () => {
|
||||||
const article = getTestArticle();
|
const article = getTestArticle();
|
||||||
expect(ArticleModel.model(article).isNew()).toBeTruthy();
|
expect(Article.model.model(article).isNew()).toBeTruthy();
|
||||||
expect(ArticleModel.model(article).isDirty()).toBeFalsy();
|
expect(Article.model.model(article).isDirty()).toBeFalsy();
|
||||||
});
|
});
|
||||||
it("gets a model identifier value", () => {
|
it("gets a model identifier value", () => {
|
||||||
const article = getTestArticle();
|
const article = getTestArticle();
|
||||||
expect(ArticleModel.model(article).getIdentifier()).toBe(1);
|
expect(Article.model.model(article).getIdentifier()).toBe(1);
|
||||||
});
|
});
|
||||||
it("gets a model composite identifier value", () => {
|
it("gets a model composite identifier value", () => {
|
||||||
class CompositeModel {
|
class CompositeModel
|
||||||
|
{
|
||||||
static model = s.defineModel({
|
static model = s.defineModel({
|
||||||
Class: CompositeModel,
|
Class: CompositeModel,
|
||||||
properties: {
|
properties: {
|
||||||
|
@ -147,7 +131,7 @@ describe("model", () => {
|
||||||
label: s.property.string(),
|
label: s.property.string(),
|
||||||
},
|
},
|
||||||
identifier: ["firstId", "secondId"],
|
identifier: ["firstId", "secondId"],
|
||||||
});
|
})
|
||||||
|
|
||||||
firstId: number;
|
firstId: number;
|
||||||
secondId: number;
|
secondId: number;
|
||||||
|
@ -155,24 +139,20 @@ describe("model", () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
CompositeModel.model
|
CompositeModel.model.model(Object.assign(new CompositeModel(), {
|
||||||
.model(
|
firstId: 5,
|
||||||
Object.assign(new CompositeModel(), {
|
secondId: 6,
|
||||||
firstId: 5,
|
label: "test",
|
||||||
secondId: 6,
|
})).getIdentifier()
|
||||||
label: "test",
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.getIdentifier(),
|
|
||||||
).toStrictEqual([5, 6]);
|
).toStrictEqual([5, 6]);
|
||||||
});
|
});
|
||||||
it("checks model dirtiness when altered, then reset diff", () => {
|
it("checks model dirtiness when altered, then reset diff", () => {
|
||||||
const article = getTestArticle();
|
const article = getTestArticle();
|
||||||
expect(ArticleModel.model(article).isDirty()).toBeFalsy();
|
expect(Article.model.model(article).isDirty()).toBeFalsy();
|
||||||
article.title = "new title";
|
article.title = "new title";
|
||||||
expect(ArticleModel.model(article).isDirty()).toBeTruthy();
|
expect(Article.model.model(article).isDirty()).toBeTruthy();
|
||||||
ArticleModel.model(article).resetDiff();
|
Article.model.model(article).resetDiff()
|
||||||
expect(ArticleModel.model(article).isDirty()).toBeFalsy();
|
expect(Article.model.model(article).isDirty()).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("deserializes a model from a serialized form", () => {
|
it("deserializes a model from a serialized form", () => {
|
||||||
|
@ -180,84 +160,38 @@ describe("model", () => {
|
||||||
id: 1,
|
id: 1,
|
||||||
title: "this is a test",
|
title: "this is a test",
|
||||||
authors: [
|
authors: [
|
||||||
Object.assign(new Account(), {
|
Object.assign(new Account(), { id: 52, name: "John Doe", email: "test@test.test", createdAt: new Date("2022-08-07T08:47:01.000Z"), active: true, }),
|
||||||
id: 52,
|
Object.assign(new Account(), { id: 4, name: "Tester", email: "another@test.test", createdAt: new Date("2022-09-07T18:32:55.000Z"), active: false, }),
|
||||||
name: "John Doe",
|
|
||||||
email: "test@test.test",
|
|
||||||
createdAt: new Date("2022-08-07T08:47:01.000Z"),
|
|
||||||
active: true,
|
|
||||||
}),
|
|
||||||
Object.assign(new Account(), {
|
|
||||||
id: 4,
|
|
||||||
name: "Tester",
|
|
||||||
email: "another@test.test",
|
|
||||||
createdAt: new Date("2022-09-07T18:32:55.000Z"),
|
|
||||||
active: false,
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
text: "this is a long test.",
|
text: "this is a long test.",
|
||||||
evaluation: 8.52,
|
evaluation: 8.52,
|
||||||
tags: [{name: "test"}, {name: "foo"}],
|
tags: [ {name: "test"}, {name: "foo"} ],
|
||||||
comments: [
|
comments: [
|
||||||
Object.assign(new ArticleComment(), {
|
Object.assign(new ArticleComment(), { id: 542, author: Object.assign(new Account(), { id: 52, name: "John Doe", email: "test@test.test", createdAt: new Date("2022-08-07T08:47:01.000Z"), active: true, }), message: "comment content", }),
|
||||||
id: 542,
|
|
||||||
author: Object.assign(new Account(), {
|
|
||||||
id: 52,
|
|
||||||
name: "John Doe",
|
|
||||||
email: "test@test.test",
|
|
||||||
createdAt: new Date("2022-08-07T08:47:01.000Z"),
|
|
||||||
active: true,
|
|
||||||
}),
|
|
||||||
message: "comment content",
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const deserializedArticle = ArticleModel.parse({
|
const deserializedArticle = Article.model.parse({
|
||||||
id: 1,
|
id: 1,
|
||||||
title: "this is a test",
|
title: "this is a test",
|
||||||
authors: [
|
authors: [
|
||||||
{
|
{ id: 52, name: "John Doe", email: "test@test.test", createdAt: "2022-08-07T08:47:01.000Z", active: true, },
|
||||||
id: 52,
|
{ id: 4, name: "Tester", email: "another@test.test", createdAt: "2022-09-07T18:32:55.000Z", active: false, },
|
||||||
name: "John Doe",
|
|
||||||
email: "test@test.test",
|
|
||||||
createdAt: "2022-08-07T08:47:01.000Z",
|
|
||||||
active: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
name: "Tester",
|
|
||||||
email: "another@test.test",
|
|
||||||
createdAt: "2022-09-07T18:32:55.000Z",
|
|
||||||
active: false,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
text: "this is a long test.",
|
text: "this is a long test.",
|
||||||
evaluation: "8.52",
|
evaluation: "8.52",
|
||||||
tags: [{name: "test"}, {name: "foo"}],
|
tags: [ {name: "test"}, {name: "foo"} ],
|
||||||
comments: [
|
comments: [
|
||||||
{
|
{ id: 542, author: { id: 52, name: "John Doe", email: "test@test.test", createdAt: "2022-08-07T08:47:01.000Z", active: true, }, message: "comment content", },
|
||||||
id: 542,
|
|
||||||
author: {
|
|
||||||
id: 52,
|
|
||||||
name: "John Doe",
|
|
||||||
email: "test@test.test",
|
|
||||||
createdAt: "2022-08-07T08:47:01.000Z",
|
|
||||||
active: true,
|
|
||||||
},
|
|
||||||
message: "comment content",
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const deserializedArticleProperties =
|
const deserializedArticleProperties = Article.model.model(deserializedArticle).getInstanceProperties();
|
||||||
ArticleModel.model(deserializedArticle).getInstanceProperties();
|
|
||||||
delete deserializedArticleProperties.authors[0]._sharkitek;
|
delete deserializedArticleProperties.authors[0]._sharkitek;
|
||||||
delete deserializedArticleProperties.authors[1]._sharkitek;
|
delete deserializedArticleProperties.authors[1]._sharkitek;
|
||||||
delete deserializedArticleProperties.comments[0]._sharkitek;
|
delete deserializedArticleProperties.comments[0]._sharkitek;
|
||||||
delete (deserializedArticleProperties.comments[0].author as any)._sharkitek;
|
delete (deserializedArticleProperties.comments[0].author as any)._sharkitek;
|
||||||
const expectedArticleProperties =
|
const expectedArticleProperties = Article.model.model(expectedArticle).getInstanceProperties();
|
||||||
ArticleModel.model(expectedArticle).getInstanceProperties();
|
|
||||||
delete expectedArticleProperties.authors[0]._sharkitek;
|
delete expectedArticleProperties.authors[0]._sharkitek;
|
||||||
delete expectedArticleProperties.authors[1]._sharkitek;
|
delete expectedArticleProperties.authors[1]._sharkitek;
|
||||||
delete expectedArticleProperties.comments[0]._sharkitek;
|
delete expectedArticleProperties.comments[0]._sharkitek;
|
||||||
|
@ -267,126 +201,79 @@ describe("model", () => {
|
||||||
|
|
||||||
it("serializes an initialized model", () => {
|
it("serializes an initialized model", () => {
|
||||||
const article = getTestArticle();
|
const article = getTestArticle();
|
||||||
expect(ArticleModel.model(article).serialize()).toEqual({
|
expect(Article.model.model(article).serialize()).toEqual({
|
||||||
id: 1,
|
id: 1,
|
||||||
title: "this is a test",
|
title: "this is a test",
|
||||||
text: "this is a long test.",
|
text: "this is a long test.",
|
||||||
evaluation: "25.23",
|
evaluation: "25.23",
|
||||||
tags: [{name: "test"}, {name: "foo"}],
|
tags: [{ name: "test" }, { name: "foo" }],
|
||||||
authors: [
|
authors: [
|
||||||
{
|
{ id: 52, createdAt: article.authors[0].createdAt.toISOString(), name: "John Doe", email: "john@doe.test", active: true }
|
||||||
id: 52,
|
|
||||||
createdAt: article.authors[0].createdAt.toISOString(),
|
|
||||||
name: "John Doe",
|
|
||||||
email: "john@doe.test",
|
|
||||||
active: true,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
comments: [],
|
comments: [],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("deserializes, changes and patches", () => {
|
it("deserializes, changes and patches", () => {
|
||||||
const deserializedArticle = ArticleModel.parse({
|
const deserializedArticle = Article.model.parse({
|
||||||
id: 1,
|
id: 1,
|
||||||
title: "this is a test",
|
title: "this is a test",
|
||||||
authors: [
|
authors: [
|
||||||
{
|
{ id: 52, name: "John Doe", email: "test@test.test", createdAt: "2022-08-07T08:47:01.000Z", active: true, },
|
||||||
id: 52,
|
{ id: 4, name: "Tester", email: "another@test.test", createdAt: "2022-09-07T18:32:55.000Z", active: false, },
|
||||||
name: "John Doe",
|
|
||||||
email: "test@test.test",
|
|
||||||
createdAt: "2022-08-07T08:47:01.000Z",
|
|
||||||
active: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
name: "Tester",
|
|
||||||
email: "another@test.test",
|
|
||||||
createdAt: "2022-09-07T18:32:55.000Z",
|
|
||||||
active: false,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
text: "this is a long test.",
|
text: "this is a long test.",
|
||||||
evaluation: "8.52",
|
evaluation: "8.52",
|
||||||
tags: [{name: "test"}, {name: "foo"}],
|
tags: [ {name: "test"}, {name: "foo"} ],
|
||||||
comments: [
|
comments: [
|
||||||
{
|
{ id: 542, author: { id: 52, name: "John Doe", email: "test@test.test", createdAt: "2022-08-07T08:47:01.000Z", active: true, }, message: "comment content", },
|
||||||
id: 542,
|
|
||||||
author: {
|
|
||||||
id: 52,
|
|
||||||
name: "John Doe",
|
|
||||||
email: "test@test.test",
|
|
||||||
createdAt: "2022-08-07T08:47:01.000Z",
|
|
||||||
active: true,
|
|
||||||
},
|
|
||||||
message: "comment content",
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
deserializedArticle.text = "A new text for a new life!";
|
deserializedArticle.text = "A new text for a new life!";
|
||||||
|
|
||||||
expect(ArticleModel.model(deserializedArticle).patch()).toStrictEqual({
|
expect(Article.model.model(deserializedArticle).patch()).toStrictEqual({
|
||||||
id: 1,
|
id: 1,
|
||||||
text: "A new text for a new life!",
|
text: "A new text for a new life!",
|
||||||
});
|
});
|
||||||
|
|
||||||
deserializedArticle.evaluation = 5.24;
|
deserializedArticle.evaluation = 5.24;
|
||||||
|
|
||||||
expect(ArticleModel.model(deserializedArticle).patch()).toStrictEqual({
|
expect(Article.model.model(deserializedArticle).patch()).toStrictEqual({
|
||||||
id: 1,
|
id: 1,
|
||||||
evaluation: "5.24",
|
evaluation: "5.24",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("patches with modified submodels", () => {
|
it("patches with modified submodels", () => {
|
||||||
const deserializedArticle = ArticleModel.parse({
|
const deserializedArticle = Article.model.parse({
|
||||||
id: 1,
|
id: 1,
|
||||||
title: "this is a test",
|
title: "this is a test",
|
||||||
authors: [
|
authors: [
|
||||||
{
|
{ id: 52, name: "John Doe", email: "test@test.test", createdAt: "2022-08-07T08:47:01.000Z", active: true, },
|
||||||
id: 52,
|
{ id: 4, name: "Tester", email: "another@test.test", createdAt: "2022-09-07T18:32:55.000Z", active: false, },
|
||||||
name: "John Doe",
|
|
||||||
email: "test@test.test",
|
|
||||||
createdAt: "2022-08-07T08:47:01.000Z",
|
|
||||||
active: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
name: "Tester",
|
|
||||||
email: "another@test.test",
|
|
||||||
createdAt: "2022-09-07T18:32:55.000Z",
|
|
||||||
active: false,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
text: "this is a long test.",
|
text: "this is a long test.",
|
||||||
evaluation: "8.52",
|
evaluation: "8.52",
|
||||||
tags: [{name: "test"}, {name: "foo"}],
|
tags: [ {name: "test"}, {name: "foo"} ],
|
||||||
comments: [
|
comments: [
|
||||||
{
|
{ id: 542, author: { id: 52, name: "John Doe", email: "test@test.test", createdAt: "2022-08-07T08:47:01.000Z", active: true, }, message: "comment content", },
|
||||||
id: 542,
|
|
||||||
author: {
|
|
||||||
id: 52,
|
|
||||||
name: "John Doe",
|
|
||||||
email: "test@test.test",
|
|
||||||
createdAt: "2022-08-07T08:47:01.000Z",
|
|
||||||
active: true,
|
|
||||||
},
|
|
||||||
message: "comment content",
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
deserializedArticle.authors[1].active = true;
|
deserializedArticle.authors[1].active = true;
|
||||||
|
|
||||||
expect(ArticleModel.model(deserializedArticle).patch()).toStrictEqual({
|
expect(Article.model.model(deserializedArticle).patch()).toStrictEqual({
|
||||||
id: 1,
|
id: 1,
|
||||||
authors: [{id: 52}, {id: 4, active: true}],
|
authors: [
|
||||||
|
{ id: 52, },
|
||||||
|
{ id: 4, active: true },
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
deserializedArticle.comments[0].author.name = "Johnny";
|
deserializedArticle.comments[0].author.name = "Johnny";
|
||||||
|
|
||||||
expect(ArticleModel.model(deserializedArticle).patch()).toStrictEqual({
|
expect(Article.model.model(deserializedArticle).patch()).toStrictEqual({
|
||||||
id: 1,
|
id: 1,
|
||||||
comments: [
|
comments: [
|
||||||
{
|
{
|
||||||
|
@ -401,7 +288,8 @@ describe("model", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("deserializes and patches with fields that are not properties", () => {
|
it("deserializes and patches with fields that are not properties", () => {
|
||||||
class TestModel {
|
class TestModel
|
||||||
|
{
|
||||||
static model = defineModel({
|
static model = defineModel({
|
||||||
Class: TestModel,
|
Class: TestModel,
|
||||||
properties: {
|
properties: {
|
||||||
|
@ -409,12 +297,12 @@ describe("model", () => {
|
||||||
label: s.property.string(),
|
label: s.property.string(),
|
||||||
},
|
},
|
||||||
identifier: "id",
|
identifier: "id",
|
||||||
});
|
})
|
||||||
|
|
||||||
id: number;
|
id: number;
|
||||||
label: string;
|
label: string;
|
||||||
|
|
||||||
notAProperty: {hello: string} = {hello: "world"};
|
notAProperty: { hello: string } = { hello: "world" };
|
||||||
}
|
}
|
||||||
|
|
||||||
const deserializedModel = TestModel.model.parse({
|
const deserializedModel = TestModel.model.parse({
|
||||||
|
@ -425,163 +313,17 @@ describe("model", () => {
|
||||||
expect(deserializedModel.label).toBe("testing");
|
expect(deserializedModel.label).toBe("testing");
|
||||||
expect(deserializedModel.notAProperty?.hello).toBe("world");
|
expect(deserializedModel.notAProperty?.hello).toBe("world");
|
||||||
|
|
||||||
const clonedDeserializedModel = TestModel.model
|
const clonedDeserializedModel = TestModel.model.model(deserializedModel).clone();
|
||||||
.model(deserializedModel)
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
deserializedModel.label = "new!";
|
deserializedModel.label = "new!";
|
||||||
expect(TestModel.model.model(deserializedModel).patch()).toStrictEqual({
|
expect(TestModel.model.model(deserializedModel).patch()).toStrictEqual({ id: 5, label: "new!" });
|
||||||
id: 5,
|
|
||||||
label: "new!",
|
|
||||||
});
|
|
||||||
|
|
||||||
deserializedModel.notAProperty.hello = "monster";
|
deserializedModel.notAProperty.hello = "monster";
|
||||||
expect(TestModel.model.model(deserializedModel).patch()).toStrictEqual({
|
expect(TestModel.model.model(deserializedModel).patch()).toStrictEqual({ id: 5 });
|
||||||
id: 5,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(TestModel.model.model(deserializedModel).serialize()).toStrictEqual({
|
expect(TestModel.model.model(deserializedModel).serialize()).toStrictEqual({ id: 5, label: "new!" });
|
||||||
id: 5,
|
|
||||||
label: "new!",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
expect(TestModel.model.model(clonedDeserializedModel).serialize()).toStrictEqual({ id: 5, label: "testing" });
|
||||||
TestModel.model.model(clonedDeserializedModel).serialize(),
|
|
||||||
).toStrictEqual({id: 5, label: "testing"});
|
|
||||||
expect(clonedDeserializedModel.notAProperty.hello).toEqual("world");
|
expect(clonedDeserializedModel.notAProperty.hello).toEqual("world");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("assigns properties, ignoring fields which are not properties", () => {
|
|
||||||
const deserializedArticle = ArticleModel.parse({
|
|
||||||
id: 1,
|
|
||||||
title: "this is a test",
|
|
||||||
authors: [
|
|
||||||
{
|
|
||||||
id: 52,
|
|
||||||
name: "John Doe",
|
|
||||||
email: "test@test.test",
|
|
||||||
createdAt: "2022-08-07T08:47:01.000Z",
|
|
||||||
active: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
name: "Tester",
|
|
||||||
email: "another@test.test",
|
|
||||||
createdAt: "2022-09-07T18:32:55.000Z",
|
|
||||||
active: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
text: "this is a long test.",
|
|
||||||
evaluation: "8.52",
|
|
||||||
tags: [{name: "test"}, {name: "foo"}],
|
|
||||||
comments: [
|
|
||||||
{
|
|
||||||
id: 542,
|
|
||||||
author: {
|
|
||||||
id: 52,
|
|
||||||
name: "John Doe",
|
|
||||||
email: "test@test.test",
|
|
||||||
createdAt: "2022-08-07T08:47:01.000Z",
|
|
||||||
active: true,
|
|
||||||
},
|
|
||||||
message: "comment content",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assign title and text, html is silently ignored.
|
|
||||||
ArticleModel.model(deserializedArticle).assign({
|
|
||||||
title: "something else",
|
|
||||||
text: "fully new text! yes!",
|
|
||||||
html: "<p>fully new text! yes!</p>",
|
|
||||||
});
|
|
||||||
expect((deserializedArticle as any)?.html).toBeUndefined();
|
|
||||||
expect(ArticleModel.model(deserializedArticle).patch()).toStrictEqual({
|
|
||||||
id: 1,
|
|
||||||
title: "something else",
|
|
||||||
text: "fully new text! yes!",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("initializes a model from properties values", () => {
|
|
||||||
const testArticle = ArticleModel.from({
|
|
||||||
title: "this is a test",
|
|
||||||
authors: [
|
|
||||||
AccountModel.from({
|
|
||||||
name: "John Doe",
|
|
||||||
email: "test@test.test",
|
|
||||||
createdAt: new Date(),
|
|
||||||
active: true,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
text: "this is a long text",
|
|
||||||
evaluation: 8.52,
|
|
||||||
tags: [{name: "test"}, {name: "foo"}],
|
|
||||||
|
|
||||||
unknownField: true,
|
|
||||||
anotherOne: "test",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(testArticle.title).toBe("this is a test");
|
|
||||||
expect(testArticle.text).toBe("this is a long text");
|
|
||||||
expect(testArticle.evaluation).toBe(8.52);
|
|
||||||
expect(testArticle.authors).toHaveLength(1);
|
|
||||||
expect(testArticle.authors[0]?.name).toBe("John Doe");
|
|
||||||
expect((testArticle as any).unknownField).toBeUndefined();
|
|
||||||
expect((testArticle as any).anotherOne).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("applies patches to an existing model", () => {
|
|
||||||
const testArticle = ArticleModel.from({
|
|
||||||
id: 1,
|
|
||||||
title: "this is a test",
|
|
||||||
authors: [
|
|
||||||
AccountModel.from({
|
|
||||||
id: 55,
|
|
||||||
name: "John Doe",
|
|
||||||
email: "test@test.test",
|
|
||||||
createdAt: new Date(),
|
|
||||||
active: true,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
text: "this is a long text",
|
|
||||||
evaluation: 8.52,
|
|
||||||
tags: [{name: "test"}, {name: "foo"}],
|
|
||||||
|
|
||||||
unknownField: true,
|
|
||||||
anotherOne: "test",
|
|
||||||
});
|
|
||||||
ArticleModel.model(testArticle).resetDiff();
|
|
||||||
|
|
||||||
// Test simple patch.
|
|
||||||
ArticleModel.model(testArticle).applyPatch({
|
|
||||||
title: "new title",
|
|
||||||
});
|
|
||||||
expect(testArticle.title).toBe("new title");
|
|
||||||
expect(ArticleModel.model(testArticle).serializeDiff()).toStrictEqual({
|
|
||||||
id: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Test originals update propagation.
|
|
||||||
ArticleModel.model(testArticle).applyPatch({
|
|
||||||
authors: [{email: "john@test.test"}],
|
|
||||||
});
|
|
||||||
expect(testArticle.authors[0].email).toBe("john@test.test");
|
|
||||||
expect(ArticleModel.model(testArticle).serializeDiff()).toStrictEqual({
|
|
||||||
id: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Test without originals update.
|
|
||||||
ArticleModel.model(testArticle).applyPatch(
|
|
||||||
{
|
|
||||||
authors: [{name: "Johnny"}],
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
expect(testArticle.authors[0].name).toBe("Johnny");
|
|
||||||
expect(ArticleModel.model(testArticle).serializeDiff()).toStrictEqual({
|
|
||||||
id: 1,
|
|
||||||
authors: [{id: 55, name: "Johnny"}],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import {describe, expect, test} from "vitest";
|
import {describe, expect, test} from "vitest";
|
||||||
import {ArrayType, InvalidTypeValueError, s} from "../../../src/library";
|
import {ArrayType, InvalidTypeValueError, s} from "../../../src/library";
|
||||||
|
|
||||||
class TestModel {
|
class TestModel
|
||||||
|
{
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
price: number;
|
price: number;
|
||||||
|
@ -18,423 +19,119 @@ describe("array type", () => {
|
||||||
identifier: "id",
|
identifier: "id",
|
||||||
});
|
});
|
||||||
|
|
||||||
test("definition", () => {
|
test("array type definition", () => {
|
||||||
const arrayType = s.property.array(s.property.model(testModel));
|
const arrayType = s.property.array(s.property.model(testModel));
|
||||||
expect(arrayType.type).toBeInstanceOf(ArrayType);
|
expect(arrayType.type).toBeInstanceOf(ArrayType);
|
||||||
});
|
});
|
||||||
|
|
||||||
const testProperty = s.property.array(s.property.decimal());
|
const testProperty = s.property.array(s.property.decimal());
|
||||||
|
|
||||||
describe("serialize", () => {
|
test("array type functions", () => {
|
||||||
test("serialize", () => {
|
expect(testProperty.type.serialize([12.547, 8, -52.11])).toEqual(["12.547", "8", "-52.11"]);
|
||||||
expect(testProperty.type.serialize([12.547, 8, -52.11])).toEqual([
|
expect(testProperty.type.deserialize(["12.547", "8", "-52.11"])).toEqual([12.547, 8, -52.11]);
|
||||||
"12.547",
|
|
||||||
"8",
|
|
||||||
"-52.11",
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(testProperty.type.serialize(null)).toBe(null);
|
{ // Try to serialize the difference of an array with one changed model.
|
||||||
expect(testProperty.type.serialize(undefined)).toBe(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(() => testProperty.type.serialize({} as any)).toThrowError(
|
|
||||||
InvalidTypeValueError,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("deserialize", () => {
|
|
||||||
test("deserialize", () => {
|
|
||||||
expect(testProperty.type.deserialize(["12.547", "8", "-52.11"])).toEqual([
|
|
||||||
12.547, 8, -52.11,
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(testProperty.type.deserialize(null)).toBe(null);
|
|
||||||
expect(testProperty.type.deserialize(undefined)).toBe(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(() => testProperty.type.deserialize({} as any)).toThrowError(
|
|
||||||
InvalidTypeValueError,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("serializeDiff", () => {
|
|
||||||
test("serializeDiff", () => {
|
|
||||||
{
|
|
||||||
// Try to serialize the difference of an array with one changed model.
|
|
||||||
const propertyValue = [
|
|
||||||
testModel.model(
|
|
||||||
Object.assign(new TestModel(), {id: 1, name: "test", price: 22}),
|
|
||||||
).instance,
|
|
||||||
testModel.model(
|
|
||||||
Object.assign(new TestModel(), {
|
|
||||||
id: 2,
|
|
||||||
name: "another",
|
|
||||||
price: 12.55,
|
|
||||||
}),
|
|
||||||
).instance,
|
|
||||||
];
|
|
||||||
propertyValue[0].name = "new";
|
|
||||||
expect(
|
|
||||||
s.property
|
|
||||||
.array(s.property.model(testModel))
|
|
||||||
.type.serializeDiff(propertyValue),
|
|
||||||
).toEqual([{id: 1, name: "new"}, {id: 2}]);
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(testProperty.type.serializeDiff(null)).toBe(null);
|
|
||||||
expect(testProperty.type.serializeDiff(undefined)).toBe(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(() => testProperty.type.serializeDiff({} as any)).toThrowError(
|
|
||||||
InvalidTypeValueError,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("hasChanged", () => {
|
|
||||||
test("hasChanged", () => {
|
|
||||||
expect(
|
|
||||||
testProperty.type.hasChanged([12.547, 8, -52.11], [12.547, 8, -52.11]),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(testProperty.type.hasChanged(null, null)).toBeFalsy();
|
|
||||||
expect(testProperty.type.hasChanged(undefined, undefined)).toBeFalsy();
|
|
||||||
expect(testProperty.type.hasChanged(null, undefined)).toBeTruthy();
|
|
||||||
expect(testProperty.type.hasChanged(undefined, null)).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.hasChanged(null, [12.547, 8, -52.11]),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.hasChanged(undefined, [12.547, 8, -52.11]),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.hasChanged([12.547, 8, -52.11], null),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.hasChanged([12.547, 8, -52.11], undefined),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.hasChanged([12.547, 8, -52.11], [12.547, -52.11, 8]),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.hasChanged([12.547, -52.11, 8], [12.547, 8, -52.11]),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.hasChanged([12.547, 8, -52.11], [12.547, 8]),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.hasChanged([12.547, 8], [12.547, 8, -52.11]),
|
|
||||||
).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(testProperty.type.hasChanged({} as any, {} as any)).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.hasChanged(false as any, false as any),
|
|
||||||
).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("serializedHasChanged", () => {
|
|
||||||
test("serializedHasChanged", () => {
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(
|
|
||||||
["12.547", "8", "-52.11"],
|
|
||||||
["12.547", "8", "-52.11"],
|
|
||||||
),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(testProperty.type.serializedHasChanged(null, null)).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(undefined, undefined),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(null, undefined),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(undefined, null),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(null, ["12.547", "8", "-52.11"]),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(undefined, [
|
|
||||||
"12.547",
|
|
||||||
"8",
|
|
||||||
"-52.11",
|
|
||||||
]),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(["12.547", "8", "-52.11"], null),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(
|
|
||||||
["12.547", "8", "-52.11"],
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(
|
|
||||||
["12.547", "8", "-52.11"],
|
|
||||||
["12.547", "-52.11", "8"],
|
|
||||||
),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(
|
|
||||||
["12.547", "-52.11", "8"],
|
|
||||||
["12.547", "8", "-52.11"],
|
|
||||||
),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(
|
|
||||||
["12.547", "8", "-52.11"],
|
|
||||||
["12.547", "8"],
|
|
||||||
),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(
|
|
||||||
["12.547", "8"],
|
|
||||||
["12.547", "8", "-52.11"],
|
|
||||||
),
|
|
||||||
).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged({} as any, {} as any),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(false as any, false as any),
|
|
||||||
).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("resetDiff", () => {
|
|
||||||
test("resetDiff", () => {
|
|
||||||
{
|
|
||||||
// Try to reset the difference of an array with one changed model.
|
|
||||||
const propertyValue = [
|
|
||||||
testModel.model(
|
|
||||||
Object.assign(new TestModel(), {id: 1, name: "test", price: 22}),
|
|
||||||
).instance,
|
|
||||||
testModel.model(
|
|
||||||
Object.assign(new TestModel(), {
|
|
||||||
id: 2,
|
|
||||||
name: "another",
|
|
||||||
price: 12.55,
|
|
||||||
}),
|
|
||||||
).instance,
|
|
||||||
];
|
|
||||||
propertyValue[0].name = "new";
|
|
||||||
expect(
|
|
||||||
s.property
|
|
||||||
.array(s.property.model(testModel))
|
|
||||||
.type.serializeDiff(propertyValue),
|
|
||||||
).toEqual([{id: 1, name: "new"}, {id: 2}]);
|
|
||||||
s.property
|
|
||||||
.array(s.property.model(testModel))
|
|
||||||
.type.resetDiff(propertyValue);
|
|
||||||
expect(
|
|
||||||
s.property
|
|
||||||
.array(s.property.model(testModel))
|
|
||||||
.type.serializeDiff(propertyValue),
|
|
||||||
).toEqual([{id: 1}, {id: 2}]);
|
|
||||||
}
|
|
||||||
|
|
||||||
testProperty.type.resetDiff(undefined);
|
|
||||||
testProperty.type.resetDiff(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(() => testProperty.type.resetDiff({} as any)).not.toThrow();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("clone", () => {
|
|
||||||
test("clone", () => {
|
|
||||||
{
|
|
||||||
// Test that values are cloned in a different array.
|
|
||||||
const propertyValue = [12.547, 8, -52.11];
|
|
||||||
const clonedPropertyValue = testProperty.type.clone(propertyValue);
|
|
||||||
expect(clonedPropertyValue).not.toBe(propertyValue);
|
|
||||||
expect(clonedPropertyValue).toEqual(propertyValue);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
// Test that values are cloned recursively.
|
|
||||||
const propertyValue = [
|
|
||||||
testModel.model(
|
|
||||||
Object.assign(new TestModel(), {id: 1, name: "test", price: 22}),
|
|
||||||
).instance,
|
|
||||||
testModel.model(
|
|
||||||
Object.assign(new TestModel(), {
|
|
||||||
id: 2,
|
|
||||||
name: "another",
|
|
||||||
price: 12.55,
|
|
||||||
}),
|
|
||||||
).instance,
|
|
||||||
];
|
|
||||||
|
|
||||||
// The arrays are different.
|
|
||||||
const clonedPropertyValue = s.property
|
|
||||||
.array(s.property.model(testModel))
|
|
||||||
.type.clone(propertyValue);
|
|
||||||
expect(clonedPropertyValue).not.toBe(propertyValue);
|
|
||||||
|
|
||||||
// Array values must be different objects but have the same values.
|
|
||||||
expect(clonedPropertyValue[0]).not.toBe(propertyValue[0]);
|
|
||||||
expect(clonedPropertyValue[1]).not.toBe(propertyValue[1]);
|
|
||||||
expect(
|
|
||||||
testModel.model(clonedPropertyValue[0]).getInstanceProperties(),
|
|
||||||
).toEqual(testModel.model(propertyValue[0]).getInstanceProperties());
|
|
||||||
expect(
|
|
||||||
testModel.model(clonedPropertyValue[1]).getInstanceProperties(),
|
|
||||||
).toEqual(testModel.model(propertyValue[1]).getInstanceProperties());
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(testProperty.type.clone(undefined)).toBe(undefined);
|
|
||||||
expect(testProperty.type.clone(null)).toBe(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(() => testProperty.type.clone({} as any)).toThrowError(
|
|
||||||
InvalidTypeValueError,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("applyPatch", () => {
|
|
||||||
{
|
|
||||||
// Test simple patch.
|
|
||||||
expect(
|
|
||||||
testProperty.type.applyPatch(
|
|
||||||
[12.547, 8, -52.11],
|
|
||||||
["12.547", "444.34", "-52.11"],
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
).toEqual([12.547, 444.34, -52.11]);
|
|
||||||
expect(
|
|
||||||
testProperty.type.applyPatch(
|
|
||||||
undefined,
|
|
||||||
["12.547", "444.34", "-52.11"],
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
).toEqual([12.547, 444.34, -52.11]);
|
|
||||||
expect(
|
|
||||||
testProperty.type.applyPatch(
|
|
||||||
null,
|
|
||||||
["12.547", "444.34", "-52.11"],
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
).toEqual([12.547, 444.34, -52.11]);
|
|
||||||
expect(
|
|
||||||
testProperty.type.applyPatch([12.547, 8, -52.11], undefined, false),
|
|
||||||
).toBeUndefined();
|
|
||||||
expect(
|
|
||||||
testProperty.type.applyPatch([12.547, 8, -52.11], null, false),
|
|
||||||
).toBeNull();
|
|
||||||
}
|
|
||||||
{
|
|
||||||
// Invalid patch.
|
|
||||||
expect(() =>
|
|
||||||
testProperty.type.applyPatch([12.547, 8, -52.11], {} as any, false),
|
|
||||||
).toThrow(InvalidTypeValueError);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
// Test recursive patch.
|
|
||||||
const propertyValue = [
|
const propertyValue = [
|
||||||
testModel.model(
|
testModel.model(Object.assign(new TestModel(), { id: 1, name: "test", price: 22 })).instance,
|
||||||
Object.assign(new TestModel(), {id: 1, name: "test", price: 22}),
|
testModel.model(Object.assign(new TestModel(), { id: 2, name: "another", price: 12.55 })).instance,
|
||||||
).instance,
|
];
|
||||||
testModel.model(
|
propertyValue[0].name = "new";
|
||||||
Object.assign(new TestModel(), {
|
expect(s.property.array(s.property.model(testModel)).type.serializeDiff(propertyValue)).toEqual([
|
||||||
id: 2,
|
{ id: 1, name: "new" },
|
||||||
name: "another",
|
{ id: 2 },
|
||||||
price: 12.55,
|
]);
|
||||||
}),
|
}
|
||||||
).instance,
|
|
||||||
|
expect(testProperty.type.serialize(null)).toBe(null);
|
||||||
|
expect(testProperty.type.deserialize(null)).toBe(null);
|
||||||
|
expect(testProperty.type.serializeDiff(null)).toBe(null);
|
||||||
|
|
||||||
|
expect(testProperty.type.serialize(undefined)).toBe(undefined);
|
||||||
|
expect(testProperty.type.deserialize(undefined)).toBe(undefined);
|
||||||
|
expect(testProperty.type.serializeDiff(undefined)).toBe(undefined);
|
||||||
|
|
||||||
|
expect(testProperty.type.hasChanged([12.547, 8, -52.11], [12.547, 8, -52.11])).toBeFalsy();
|
||||||
|
expect(testProperty.type.hasChanged(null, null)).toBeFalsy();
|
||||||
|
expect(testProperty.type.hasChanged(undefined, undefined)).toBeFalsy();
|
||||||
|
expect(testProperty.type.hasChanged(null, undefined)).toBeTruthy();
|
||||||
|
expect(testProperty.type.hasChanged(undefined, null)).toBeTruthy();
|
||||||
|
expect(testProperty.type.hasChanged(null, [12.547, 8, -52.11])).toBeTruthy();
|
||||||
|
expect(testProperty.type.hasChanged(undefined, [12.547, 8, -52.11])).toBeTruthy();
|
||||||
|
expect(testProperty.type.hasChanged([12.547, 8, -52.11], null)).toBeTruthy();
|
||||||
|
expect(testProperty.type.hasChanged([12.547, 8, -52.11], undefined)).toBeTruthy();
|
||||||
|
expect(testProperty.type.hasChanged([12.547, 8, -52.11], [12.547, -52.11, 8])).toBeTruthy();
|
||||||
|
expect(testProperty.type.hasChanged([12.547, -52.11, 8], [12.547, 8, -52.11])).toBeTruthy();
|
||||||
|
expect(testProperty.type.hasChanged([12.547, 8, -52.11], [12.547, 8])).toBeTruthy();
|
||||||
|
expect(testProperty.type.hasChanged([12.547, 8], [12.547, 8, -52.11])).toBeTruthy();
|
||||||
|
|
||||||
|
expect(testProperty.type.serializedHasChanged(["12.547", "8", "-52.11"], ["12.547", "8", "-52.11"])).toBeFalsy();
|
||||||
|
expect(testProperty.type.serializedHasChanged(null, null)).toBeFalsy();
|
||||||
|
expect(testProperty.type.serializedHasChanged(undefined, undefined)).toBeFalsy();
|
||||||
|
expect(testProperty.type.serializedHasChanged(null, undefined)).toBeTruthy();
|
||||||
|
expect(testProperty.type.serializedHasChanged(undefined, null)).toBeTruthy();
|
||||||
|
expect(testProperty.type.serializedHasChanged(null, ["12.547", "8", "-52.11"])).toBeTruthy();
|
||||||
|
expect(testProperty.type.serializedHasChanged(undefined, ["12.547", "8", "-52.11"])).toBeTruthy();
|
||||||
|
expect(testProperty.type.serializedHasChanged(["12.547", "8", "-52.11"], null)).toBeTruthy();
|
||||||
|
expect(testProperty.type.serializedHasChanged(["12.547", "8", "-52.11"], undefined)).toBeTruthy();
|
||||||
|
expect(testProperty.type.serializedHasChanged(["12.547", "8", "-52.11"], ["12.547", "-52.11", "8"])).toBeTruthy();
|
||||||
|
expect(testProperty.type.serializedHasChanged(["12.547", "-52.11", "8"], ["12.547", "8", "-52.11"])).toBeTruthy();
|
||||||
|
expect(testProperty.type.serializedHasChanged(["12.547", "8", "-52.11"], ["12.547", "8"])).toBeTruthy();
|
||||||
|
expect(testProperty.type.serializedHasChanged(["12.547", "8"], ["12.547", "8", "-52.11"])).toBeTruthy();
|
||||||
|
|
||||||
|
{ // Try to reset the difference of an array with one changed model.
|
||||||
|
const propertyValue = [
|
||||||
|
testModel.model(Object.assign(new TestModel(), { id: 1, name: "test", price: 22 })).instance,
|
||||||
|
testModel.model(Object.assign(new TestModel(), { id: 2, name: "another", price: 12.55 })).instance,
|
||||||
|
];
|
||||||
|
propertyValue[0].name = "new";
|
||||||
|
expect(s.property.array(s.property.model(testModel)).type.serializeDiff(propertyValue)).toEqual([
|
||||||
|
{ id: 1, name: "new" },
|
||||||
|
{ id: 2 },
|
||||||
|
]);
|
||||||
|
s.property.array(s.property.model(testModel)).type.resetDiff(propertyValue)
|
||||||
|
expect(s.property.array(s.property.model(testModel)).type.serializeDiff(propertyValue)).toEqual([
|
||||||
|
{ id: 1 },
|
||||||
|
{ id: 2 },
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
testProperty.type.resetDiff(undefined);
|
||||||
|
testProperty.type.resetDiff(null);
|
||||||
|
|
||||||
|
{ // Test that values are cloned in a different array.
|
||||||
|
const propertyValue = [12.547, 8, -52.11];
|
||||||
|
const clonedPropertyValue = testProperty.type.clone(propertyValue);
|
||||||
|
expect(clonedPropertyValue).not.toBe(propertyValue);
|
||||||
|
expect(clonedPropertyValue).toEqual(propertyValue);
|
||||||
|
}
|
||||||
|
{ // Test that values are cloned recursively.
|
||||||
|
const propertyValue = [
|
||||||
|
testModel.model(Object.assign(new TestModel(), { id: 1, name: "test", price: 22 })).instance,
|
||||||
|
testModel.model(Object.assign(new TestModel(), { id: 2, name: "another", price: 12.55 })).instance,
|
||||||
];
|
];
|
||||||
|
|
||||||
const patched = s.property
|
// The arrays are different.
|
||||||
.array(s.property.model(testModel))
|
const clonedPropertyValue = s.property.array(s.property.model(testModel)).type.clone(propertyValue);
|
||||||
.type.applyPatch(
|
expect(clonedPropertyValue).not.toBe(propertyValue);
|
||||||
propertyValue,
|
|
||||||
[
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
name: "new",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
price: "13.65",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check applied patch.
|
// Array values must be different objects but have the same values.
|
||||||
expect(patched).toEqual([
|
expect(clonedPropertyValue[0]).not.toBe(propertyValue[0]);
|
||||||
testModel.parse({id: 1, name: "new", price: "22"}),
|
expect(clonedPropertyValue[1]).not.toBe(propertyValue[1]);
|
||||||
testModel.parse({id: 2, name: "another", price: "13.65"}),
|
expect(testModel.model(clonedPropertyValue[0]).getInstanceProperties()).toEqual(testModel.model(propertyValue[0]).getInstanceProperties());
|
||||||
]);
|
expect(testModel.model(clonedPropertyValue[1]).getInstanceProperties()).toEqual(testModel.model(propertyValue[1]).getInstanceProperties());
|
||||||
|
|
||||||
// Check that originals have been updated.
|
|
||||||
expect(testModel.model(patched[0]).serializeDiff()).toEqual({id: 1});
|
|
||||||
patched[0].name = "test";
|
|
||||||
expect(testModel.model(patched[0]).serializeDiff()).toEqual({
|
|
||||||
id: 1,
|
|
||||||
name: "test",
|
|
||||||
});
|
|
||||||
expect(testModel.model(patched[1]).serializeDiff()).toEqual({id: 2});
|
|
||||||
patched[1].price = 12.55;
|
|
||||||
expect(testModel.model(patched[1]).serializeDiff()).toEqual({
|
|
||||||
id: 2,
|
|
||||||
price: "12.55",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
{
|
expect(testProperty.type.clone(undefined)).toBe(undefined);
|
||||||
// Test recursive patch without originals update.-
|
expect(testProperty.type.clone(null)).toBe(null);
|
||||||
const propertyValue = [
|
});
|
||||||
testModel.model(
|
|
||||||
Object.assign(new TestModel(), {id: 1, name: "test", price: 22}),
|
|
||||||
).instance,
|
|
||||||
testModel.model(
|
|
||||||
Object.assign(new TestModel(), {
|
|
||||||
id: 2,
|
|
||||||
name: "another",
|
|
||||||
price: 12.55,
|
|
||||||
}),
|
|
||||||
).instance,
|
|
||||||
];
|
|
||||||
|
|
||||||
const patched = s.property
|
test("invalid parameters types", () => {
|
||||||
.array(s.property.model(testModel))
|
expect(() => testProperty.type.serialize({} as any)).toThrowError(InvalidTypeValueError);
|
||||||
.type.applyPatch(
|
expect(() => testProperty.type.deserialize({} as any)).toThrowError(InvalidTypeValueError);
|
||||||
propertyValue,
|
expect(() => testProperty.type.serializeDiff({} as any)).toThrowError(InvalidTypeValueError);
|
||||||
[
|
expect(() => testProperty.type.resetDiff({} as any)).not.toThrow();
|
||||||
{
|
expect(testProperty.type.hasChanged({} as any, {} as any)).toBeTruthy();
|
||||||
id: 1,
|
expect(testProperty.type.hasChanged(false as any, false as any)).toBeFalsy();
|
||||||
name: "new",
|
expect(testProperty.type.serializedHasChanged({} as any, {} as any)).toBeTruthy();
|
||||||
},
|
expect(testProperty.type.serializedHasChanged(false as any, false as any)).toBeFalsy();
|
||||||
{
|
expect(() => testProperty.type.clone({} as any)).toThrowError(InvalidTypeValueError);
|
||||||
id: 2,
|
|
||||||
price: "13.65",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check that originals haven't been updated.
|
|
||||||
expect(testModel.model(patched[0]).serializeDiff()).toEqual({
|
|
||||||
id: 1,
|
|
||||||
name: "new",
|
|
||||||
});
|
|
||||||
expect(testModel.model(patched[1]).serializeDiff()).toEqual({
|
|
||||||
id: 2,
|
|
||||||
price: "13.65",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {describe, expect, test} from "vitest";
|
||||||
import {BooleanType, s} from "../../../src/library";
|
import {BooleanType, s} from "../../../src/library";
|
||||||
|
|
||||||
describe("boolean type", () => {
|
describe("boolean type", () => {
|
||||||
test("definition", () => {
|
test("boolean type definition", () => {
|
||||||
{
|
{
|
||||||
const booleanType = s.property.boolean();
|
const booleanType = s.property.boolean();
|
||||||
expect(booleanType.type).toBeInstanceOf(BooleanType);
|
expect(booleanType.type).toBeInstanceOf(BooleanType);
|
||||||
|
@ -13,163 +13,56 @@ describe("boolean type", () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("serialize", () => {
|
test("boolean type functions", () => {
|
||||||
test("serialize", () => {
|
expect(s.property.boolean().type.serialize(false)).toBe(false);
|
||||||
expect(s.property.boolean().type.serialize(false)).toBe(false);
|
expect(s.property.boolean().type.deserialize(false)).toBe(false);
|
||||||
expect(s.property.boolean().type.serialize(null)).toBe(null);
|
expect(s.property.boolean().type.serializeDiff(true)).toBe(true);
|
||||||
expect(s.property.boolean().type.serialize(undefined)).toBe(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
expect(s.property.boolean().type.serialize(null)).toBe(null);
|
||||||
expect(s.property.boolean().type.serialize(1 as any)).toBeTruthy();
|
expect(s.property.boolean().type.deserialize(null)).toBe(null);
|
||||||
expect(s.property.boolean().type.serialize(0 as any)).toBeFalsy();
|
expect(s.property.boolean().type.serializeDiff(null)).toBe(null);
|
||||||
});
|
|
||||||
|
expect(s.property.boolean().type.serialize(undefined)).toBe(undefined);
|
||||||
|
expect(s.property.boolean().type.deserialize(undefined)).toBe(undefined);
|
||||||
|
expect(s.property.boolean().type.serializeDiff(undefined)).toBe(undefined);
|
||||||
|
|
||||||
|
expect(s.property.boolean().type.hasChanged(true, true)).toBeFalsy();
|
||||||
|
expect(s.property.boolean().type.hasChanged(null, null)).toBeFalsy();
|
||||||
|
expect(s.property.boolean().type.hasChanged(undefined, undefined)).toBeFalsy();
|
||||||
|
expect(s.property.boolean().type.hasChanged(null, undefined)).toBeTruthy();
|
||||||
|
expect(s.property.boolean().type.hasChanged(undefined, null)).toBeTruthy();
|
||||||
|
expect(s.property.boolean().type.hasChanged(null, false)).toBeTruthy();
|
||||||
|
expect(s.property.boolean().type.hasChanged(undefined, false)).toBeTruthy();
|
||||||
|
expect(s.property.boolean().type.hasChanged(false, null)).toBeTruthy();
|
||||||
|
expect(s.property.boolean().type.hasChanged(false, undefined)).toBeTruthy();
|
||||||
|
|
||||||
|
expect(s.property.boolean().type.serializedHasChanged(false, false)).toBeFalsy();
|
||||||
|
expect(s.property.boolean().type.serializedHasChanged(null, null)).toBeFalsy();
|
||||||
|
expect(s.property.boolean().type.serializedHasChanged(undefined, undefined)).toBeFalsy();
|
||||||
|
expect(s.property.boolean().type.serializedHasChanged(null, undefined)).toBeTruthy();
|
||||||
|
expect(s.property.boolean().type.serializedHasChanged(undefined, null)).toBeTruthy();
|
||||||
|
expect(s.property.boolean().type.serializedHasChanged(null, false)).toBeTruthy();
|
||||||
|
expect(s.property.boolean().type.serializedHasChanged(undefined, false)).toBeTruthy();
|
||||||
|
expect(s.property.boolean().type.serializedHasChanged(false, null)).toBeTruthy();
|
||||||
|
expect(s.property.boolean().type.serializedHasChanged(false, undefined)).toBeTruthy();
|
||||||
|
|
||||||
|
s.property.boolean().type.resetDiff(false);
|
||||||
|
s.property.boolean().type.resetDiff(undefined);
|
||||||
|
s.property.boolean().type.resetDiff(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("deserialize", () => {
|
test("invalid parameters types", () => {
|
||||||
test("deserialize", () => {
|
expect(s.property.boolean().type.serialize(1 as any)).toBeTruthy();
|
||||||
expect(s.property.boolean().type.deserialize(false)).toBe(false);
|
expect(s.property.boolean().type.serialize(0 as any)).toBeFalsy();
|
||||||
expect(s.property.boolean().type.deserialize(null)).toBe(null);
|
expect(s.property.boolean().type.deserialize(1 as any)).toBeTruthy();
|
||||||
expect(s.property.boolean().type.deserialize(undefined)).toBe(undefined);
|
expect(s.property.boolean().type.deserialize(0 as any)).toBeFalsy();
|
||||||
});
|
expect(s.property.boolean().type.serializeDiff(1 as any)).toBeTruthy();
|
||||||
|
expect(s.property.boolean().type.serializeDiff(0 as any)).toBeFalsy();
|
||||||
test("invalid parameters", () => {
|
expect(() => s.property.boolean().type.resetDiff({} as any)).not.toThrow();
|
||||||
expect(s.property.boolean().type.deserialize(1 as any)).toBeTruthy();
|
expect(s.property.boolean().type.hasChanged({} as any, {} as any)).toBeTruthy();
|
||||||
expect(s.property.boolean().type.deserialize(0 as any)).toBeFalsy();
|
expect(s.property.boolean().type.hasChanged(false as any, false as any)).toBeFalsy();
|
||||||
});
|
expect(s.property.boolean().type.serializedHasChanged({} as any, {} as any)).toBeTruthy();
|
||||||
});
|
expect(s.property.boolean().type.serializedHasChanged(false as any, false as any)).toBeFalsy();
|
||||||
|
expect(s.property.boolean().type.clone({} as any)).toStrictEqual({});
|
||||||
describe("serializeDiff", () => {
|
|
||||||
test("serializeDiff", () => {
|
|
||||||
expect(s.property.boolean().type.serializeDiff(true)).toBe(true);
|
|
||||||
expect(s.property.boolean().type.serializeDiff(null)).toBe(null);
|
|
||||||
expect(s.property.boolean().type.serializeDiff(undefined)).toBe(
|
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(s.property.boolean().type.serializeDiff(1 as any)).toBeTruthy();
|
|
||||||
expect(s.property.boolean().type.serializeDiff(0 as any)).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("hasChanged", () => {
|
|
||||||
test("hasChanged", () => {
|
|
||||||
expect(s.property.boolean().type.hasChanged(true, true)).toBeFalsy();
|
|
||||||
expect(s.property.boolean().type.hasChanged(null, null)).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property.boolean().type.hasChanged(undefined, undefined),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property.boolean().type.hasChanged(null, undefined),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.boolean().type.hasChanged(undefined, null),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(s.property.boolean().type.hasChanged(null, false)).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.boolean().type.hasChanged(undefined, false),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(s.property.boolean().type.hasChanged(false, null)).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.boolean().type.hasChanged(false, undefined),
|
|
||||||
).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(
|
|
||||||
s.property.boolean().type.hasChanged({} as any, {} as any),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.boolean().type.hasChanged(false as any, false as any),
|
|
||||||
).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("serializedHasChanged", () => {
|
|
||||||
test("serializedHasChanged", () => {
|
|
||||||
expect(
|
|
||||||
s.property.boolean().type.serializedHasChanged(false, false),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property.boolean().type.serializedHasChanged(null, null),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property.boolean().type.serializedHasChanged(undefined, undefined),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property.boolean().type.serializedHasChanged(null, undefined),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.boolean().type.serializedHasChanged(undefined, null),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.boolean().type.serializedHasChanged(null, false),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.boolean().type.serializedHasChanged(undefined, false),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.boolean().type.serializedHasChanged(false, null),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.boolean().type.serializedHasChanged(false, undefined),
|
|
||||||
).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(
|
|
||||||
s.property.boolean().type.serializedHasChanged({} as any, {} as any),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property
|
|
||||||
.boolean()
|
|
||||||
.type.serializedHasChanged(false as any, false as any),
|
|
||||||
).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("resetDiff", () => {
|
|
||||||
test("resetDiff", () => {
|
|
||||||
s.property.boolean().type.resetDiff(false);
|
|
||||||
s.property.boolean().type.resetDiff(undefined);
|
|
||||||
s.property.boolean().type.resetDiff(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("resetDiff", () => {
|
|
||||||
expect(() =>
|
|
||||||
s.property.boolean().type.resetDiff({} as any),
|
|
||||||
).not.toThrow();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("clone", () => {
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(s.property.boolean().type.clone({} as any)).toStrictEqual({});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("applyPatch", () => {
|
|
||||||
expect(
|
|
||||||
s.property.boolean().type.applyPatch(false, true, true),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.boolean().type.applyPatch(false, true, false),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.boolean().type.applyPatch(true, false, false),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property.boolean().type.applyPatch(false, undefined, false),
|
|
||||||
).toBeUndefined();
|
|
||||||
expect(s.property.boolean().type.applyPatch(false, null, false)).toBeNull();
|
|
||||||
expect(
|
|
||||||
s.property.boolean().type.applyPatch(undefined, null, false),
|
|
||||||
).toBeNull();
|
|
||||||
expect(s.property.boolean().type.applyPatch(null, null, false)).toBeNull();
|
|
||||||
expect(
|
|
||||||
s.property.boolean().type.applyPatch(null, false, false),
|
|
||||||
).toBeFalsy();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,229 +4,71 @@ import {DateType, InvalidTypeValueError, s} from "../../../src/library";
|
||||||
describe("date type", () => {
|
describe("date type", () => {
|
||||||
const testDate = new Date();
|
const testDate = new Date();
|
||||||
|
|
||||||
test("definition", () => {
|
test("date type definition", () => {
|
||||||
const dateType = s.property.date();
|
const dateType = s.property.date();
|
||||||
expect(dateType.type).toBeInstanceOf(DateType);
|
expect(dateType.type).toBeInstanceOf(DateType);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("serialize", () => {
|
test("date type functions", () => {
|
||||||
test("serialize", () => {
|
expect(s.property.date().type.serialize(testDate)).toBe(testDate.toISOString());
|
||||||
expect(s.property.date().type.serialize(testDate)).toBe(
|
expect(s.property.date().type.deserialize(testDate.toISOString())?.getTime()).toBe(testDate.getTime());
|
||||||
testDate.toISOString(),
|
expect(s.property.date().type.serializeDiff(new Date(testDate))).toBe(testDate.toISOString());
|
||||||
);
|
expect(s.property.date().type.deserialize("2565152-2156121-256123121 5121544175:21515612").valueOf()).toBeNaN();
|
||||||
|
expect(s.property.date().type.serialize(new Date(NaN))).toBe((new Date(NaN)).toString());
|
||||||
|
|
||||||
expect(s.property.date().type.serialize(new Date(NaN))).toBe(
|
expect(s.property.date().type.serialize(null)).toBe(null);
|
||||||
new Date(NaN).toString(),
|
expect(s.property.date().type.deserialize(null)).toBe(null);
|
||||||
);
|
expect(s.property.date().type.serializeDiff(null)).toBe(null);
|
||||||
|
|
||||||
expect(s.property.date().type.serialize(null)).toBe(null);
|
expect(s.property.date().type.serialize(undefined)).toBe(undefined);
|
||||||
expect(s.property.date().type.serialize(undefined)).toBe(undefined);
|
expect(s.property.date().type.deserialize(undefined)).toBe(undefined);
|
||||||
});
|
expect(s.property.date().type.serializeDiff(undefined)).toBe(undefined);
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
expect(s.property.date().type.hasChanged(testDate, new Date(testDate))).toBeFalsy();
|
||||||
expect(() => s.property.date().type.serialize({} as any)).toThrowError(
|
expect(s.property.date().type.hasChanged(null, null)).toBeFalsy();
|
||||||
InvalidTypeValueError,
|
expect(s.property.date().type.hasChanged(undefined, undefined)).toBeFalsy();
|
||||||
);
|
expect(s.property.date().type.hasChanged(null, undefined)).toBeTruthy();
|
||||||
});
|
expect(s.property.date().type.hasChanged(undefined, null)).toBeTruthy();
|
||||||
|
expect(s.property.date().type.hasChanged(null, testDate)).toBeTruthy();
|
||||||
|
expect(s.property.date().type.hasChanged(undefined, testDate)).toBeTruthy();
|
||||||
|
expect(s.property.date().type.hasChanged(testDate, null)).toBeTruthy();
|
||||||
|
expect(s.property.date().type.hasChanged(new Date(NaN), null)).toBeTruthy();
|
||||||
|
expect(s.property.date().type.hasChanged(new Date(NaN), undefined)).toBeTruthy();
|
||||||
|
expect(s.property.date().type.hasChanged(new Date(NaN), new Date(NaN))).toBeFalsy();
|
||||||
|
|
||||||
|
expect(s.property.date().type.serializedHasChanged(testDate.toISOString(), (new Date(testDate)).toISOString())).toBeFalsy();
|
||||||
|
expect(s.property.date().type.serializedHasChanged(null, null)).toBeFalsy();
|
||||||
|
expect(s.property.date().type.serializedHasChanged(undefined, undefined)).toBeFalsy();
|
||||||
|
expect(s.property.date().type.serializedHasChanged(null, undefined)).toBeTruthy();
|
||||||
|
expect(s.property.date().type.serializedHasChanged(undefined, null)).toBeTruthy();
|
||||||
|
expect(s.property.date().type.serializedHasChanged(null, testDate.toISOString())).toBeTruthy();
|
||||||
|
expect(s.property.date().type.serializedHasChanged(undefined, testDate.toISOString())).toBeTruthy();
|
||||||
|
expect(s.property.date().type.serializedHasChanged(testDate.toISOString(), null)).toBeTruthy();
|
||||||
|
expect(s.property.date().type.serializedHasChanged((new Date(NaN)).toString(), null)).toBeTruthy();
|
||||||
|
expect(s.property.date().type.serializedHasChanged((new Date(NaN)).toString(), undefined)).toBeTruthy();
|
||||||
|
expect(s.property.date().type.serializedHasChanged((new Date(NaN)).toString(), (new Date(NaN)).toString())).toBeFalsy();
|
||||||
|
|
||||||
|
s.property.date().type.resetDiff(testDate);
|
||||||
|
s.property.date().type.resetDiff(undefined);
|
||||||
|
s.property.date().type.resetDiff(null);
|
||||||
|
|
||||||
|
{ // Test that the date is cloned in a different object.
|
||||||
|
const propertyValue = new Date();
|
||||||
|
const clonedPropertyValue = s.property.date().type.clone(propertyValue);
|
||||||
|
expect(clonedPropertyValue).not.toBe(propertyValue);
|
||||||
|
expect(clonedPropertyValue).toEqual(propertyValue);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("deserialize", () => {
|
test("invalid parameters types", () => {
|
||||||
test("deserialize", () => {
|
expect(() => s.property.date().type.serialize({} as any)).toThrowError(InvalidTypeValueError);
|
||||||
expect(
|
expect(s.property.date().type.deserialize({} as any).getTime()).toBe(NaN);
|
||||||
s.property.date().type.deserialize(testDate.toISOString())?.getTime(),
|
expect(() => s.property.date().type.serializeDiff({} as any)).toThrowError(InvalidTypeValueError);
|
||||||
).toBe(testDate.getTime());
|
expect(() => s.property.date().type.resetDiff({} as any)).not.toThrow();
|
||||||
|
expect(s.property.date().type.hasChanged({} as any, {} as any)).toBeTruthy();
|
||||||
expect(
|
expect(s.property.date().type.hasChanged(false as any, false as any)).toBeFalsy();
|
||||||
s.property
|
expect(s.property.date().type.serializedHasChanged({} as any, {} as any)).toBeTruthy();
|
||||||
.date()
|
expect(s.property.date().type.serializedHasChanged(false as any, false as any)).toBeFalsy();
|
||||||
.type.deserialize("2565152-2156121-256123121 5121544175:21515612")
|
expect(s.property.date().type.clone({} as any)).toStrictEqual({});
|
||||||
.valueOf(),
|
|
||||||
).toBeNaN();
|
|
||||||
|
|
||||||
expect(s.property.date().type.deserialize(null)).toBe(null);
|
|
||||||
expect(s.property.date().type.deserialize(undefined)).toBe(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(
|
|
||||||
s.property
|
|
||||||
.date()
|
|
||||||
.type.deserialize({} as any)
|
|
||||||
.getTime(),
|
|
||||||
).toBe(NaN);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("serializeDiff", () => {
|
|
||||||
test("serializeDiff", () => {
|
|
||||||
expect(s.property.date().type.serializeDiff(new Date(testDate))).toBe(
|
|
||||||
testDate.toISOString(),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(s.property.date().type.serializeDiff(null)).toBe(null);
|
|
||||||
expect(s.property.date().type.serializeDiff(undefined)).toBe(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(() =>
|
|
||||||
s.property.date().type.serializeDiff({} as any),
|
|
||||||
).toThrowError(InvalidTypeValueError);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("hasChanged", () => {
|
|
||||||
test("hasChanged", () => {
|
|
||||||
expect(
|
|
||||||
s.property.date().type.hasChanged(testDate, new Date(testDate)),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(s.property.date().type.hasChanged(null, null)).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property.date().type.hasChanged(undefined, undefined),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(s.property.date().type.hasChanged(null, undefined)).toBeTruthy();
|
|
||||||
expect(s.property.date().type.hasChanged(undefined, null)).toBeTruthy();
|
|
||||||
expect(s.property.date().type.hasChanged(null, testDate)).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.date().type.hasChanged(undefined, testDate),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(s.property.date().type.hasChanged(testDate, null)).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.date().type.hasChanged(new Date(NaN), null),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.date().type.hasChanged(new Date(NaN), undefined),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.date().type.hasChanged(new Date(NaN), new Date(NaN)),
|
|
||||||
).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(
|
|
||||||
s.property.date().type.hasChanged({} as any, {} as any),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.date().type.hasChanged(false as any, false as any),
|
|
||||||
).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("serializedHasChanged", () => {
|
|
||||||
test("serializedHasChanged", () => {
|
|
||||||
expect(
|
|
||||||
s.property
|
|
||||||
.date()
|
|
||||||
.type.serializedHasChanged(
|
|
||||||
testDate.toISOString(),
|
|
||||||
new Date(testDate).toISOString(),
|
|
||||||
),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property.date().type.serializedHasChanged(null, null),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property.date().type.serializedHasChanged(undefined, undefined),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property.date().type.serializedHasChanged(null, undefined),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.date().type.serializedHasChanged(undefined, null),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property
|
|
||||||
.date()
|
|
||||||
.type.serializedHasChanged(null, testDate.toISOString()),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property
|
|
||||||
.date()
|
|
||||||
.type.serializedHasChanged(undefined, testDate.toISOString()),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property
|
|
||||||
.date()
|
|
||||||
.type.serializedHasChanged(testDate.toISOString(), null),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property
|
|
||||||
.date()
|
|
||||||
.type.serializedHasChanged(new Date(NaN).toString(), null),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property
|
|
||||||
.date()
|
|
||||||
.type.serializedHasChanged(new Date(NaN).toString(), undefined),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property
|
|
||||||
.date()
|
|
||||||
.type.serializedHasChanged(
|
|
||||||
new Date(NaN).toString(),
|
|
||||||
new Date(NaN).toString(),
|
|
||||||
),
|
|
||||||
).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(
|
|
||||||
s.property.date().type.serializedHasChanged({} as any, {} as any),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.date().type.serializedHasChanged(false as any, false as any),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(s.property.date().type.clone({} as any)).toStrictEqual({});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("resetDiff", () => {
|
|
||||||
test("resetDiff", () => {
|
|
||||||
s.property.date().type.resetDiff(testDate);
|
|
||||||
s.property.date().type.resetDiff(undefined);
|
|
||||||
s.property.date().type.resetDiff(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(() => s.property.date().type.resetDiff({} as any)).not.toThrow();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("clone", () => {
|
|
||||||
// Test that the date is cloned in a different object.
|
|
||||||
const propertyValue = new Date();
|
|
||||||
const clonedPropertyValue = s.property.date().type.clone(propertyValue);
|
|
||||||
expect(clonedPropertyValue).not.toBe(propertyValue);
|
|
||||||
expect(clonedPropertyValue).toEqual(propertyValue);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("applyPatch", () => {
|
|
||||||
expect(
|
|
||||||
s.property
|
|
||||||
.date()
|
|
||||||
.type.applyPatch(new Date("2022-02-22"), testDate.toISOString(), false)
|
|
||||||
?.getTime(),
|
|
||||||
).toBe(testDate.getTime());
|
|
||||||
expect(
|
|
||||||
s.property
|
|
||||||
.date()
|
|
||||||
.type.applyPatch(null, testDate.toISOString(), true)
|
|
||||||
?.getTime(),
|
|
||||||
).toBe(testDate.getTime());
|
|
||||||
expect(
|
|
||||||
s.property
|
|
||||||
.date()
|
|
||||||
.type.applyPatch(
|
|
||||||
undefined,
|
|
||||||
"2565152-2156121-256123121 5121544175:21515612",
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.valueOf(),
|
|
||||||
).toBeNaN();
|
|
||||||
expect(
|
|
||||||
s.property.date().type.applyPatch(new Date(), undefined, false),
|
|
||||||
).toBeUndefined();
|
|
||||||
expect(
|
|
||||||
s.property.date().type.applyPatch(new Date(), null, false),
|
|
||||||
).toBeNull();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,158 +7,54 @@ describe("decimal type", () => {
|
||||||
expect(decimalType.type).toBeInstanceOf(DecimalType);
|
expect(decimalType.type).toBeInstanceOf(DecimalType);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("serialize", () => {
|
test("decimal type functions", () => {
|
||||||
test("serialize", () => {
|
expect(s.property.decimal().type.serialize(5.257)).toBe("5.257");
|
||||||
expect(s.property.decimal().type.serialize(5.257)).toBe("5.257");
|
expect(s.property.decimal().type.deserialize("5.257")).toBe(5.257);
|
||||||
|
expect(s.property.decimal().type.serializeDiff(542)).toBe("542");
|
||||||
|
|
||||||
expect(s.property.decimal().type.serialize(null)).toBe(null);
|
expect(s.property.decimal().type.serialize(null)).toBe(null);
|
||||||
expect(s.property.decimal().type.serialize(undefined)).toBe(undefined);
|
expect(s.property.decimal().type.deserialize(null)).toBe(null);
|
||||||
});
|
expect(s.property.decimal().type.serializeDiff(null)).toBe(null);
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
expect(s.property.decimal().type.serialize(undefined)).toBe(undefined);
|
||||||
expect(() => s.property.decimal().type.serialize({} as any)).toThrowError(
|
expect(s.property.decimal().type.deserialize(undefined)).toBe(undefined);
|
||||||
InvalidTypeValueError,
|
expect(s.property.decimal().type.serializeDiff(undefined)).toBe(undefined);
|
||||||
);
|
|
||||||
});
|
expect(s.property.decimal().type.hasChanged(5.257, 5.257)).toBeFalsy();
|
||||||
|
expect(s.property.decimal().type.hasChanged(null, null)).toBeFalsy();
|
||||||
|
expect(s.property.decimal().type.hasChanged(undefined, undefined)).toBeFalsy();
|
||||||
|
expect(s.property.decimal().type.hasChanged(null, undefined)).toBeTruthy();
|
||||||
|
expect(s.property.decimal().type.hasChanged(undefined, null)).toBeTruthy();
|
||||||
|
expect(s.property.decimal().type.hasChanged(null, 5.257)).toBeTruthy();
|
||||||
|
expect(s.property.decimal().type.hasChanged(undefined, 5.257)).toBeTruthy();
|
||||||
|
expect(s.property.decimal().type.hasChanged(5.257, null)).toBeTruthy();
|
||||||
|
expect(s.property.decimal().type.hasChanged(5.257, undefined)).toBeTruthy();
|
||||||
|
|
||||||
|
expect(s.property.decimal().type.serializedHasChanged("5.257", "5.257")).toBeFalsy();
|
||||||
|
expect(s.property.decimal().type.serializedHasChanged(null, null)).toBeFalsy();
|
||||||
|
expect(s.property.decimal().type.serializedHasChanged(undefined, undefined)).toBeFalsy();
|
||||||
|
expect(s.property.decimal().type.serializedHasChanged(null, undefined)).toBeTruthy();
|
||||||
|
expect(s.property.decimal().type.serializedHasChanged(undefined, null)).toBeTruthy();
|
||||||
|
expect(s.property.decimal().type.serializedHasChanged(null, "5.257")).toBeTruthy();
|
||||||
|
expect(s.property.decimal().type.serializedHasChanged(undefined, "5.257")).toBeTruthy();
|
||||||
|
expect(s.property.decimal().type.serializedHasChanged("5.257", null)).toBeTruthy();
|
||||||
|
expect(s.property.decimal().type.serializedHasChanged("5.257", undefined)).toBeTruthy();
|
||||||
|
|
||||||
|
s.property.decimal().type.resetDiff(5.257);
|
||||||
|
s.property.decimal().type.resetDiff(undefined);
|
||||||
|
s.property.decimal().type.resetDiff(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("deserialize", () => {
|
test("invalid parameters types", () => {
|
||||||
test("deserialize", () => {
|
expect(() => s.property.decimal().type.serialize({} as any)).toThrowError(InvalidTypeValueError);
|
||||||
expect(s.property.decimal().type.deserialize("5.257")).toBe(5.257);
|
expect(s.property.decimal().type.deserialize({} as any)).toBe(NaN);
|
||||||
expect(s.property.decimal().type.deserialize(null)).toBe(null);
|
expect(s.property.decimal().type.deserialize({} as any)).toBe(NaN);
|
||||||
expect(s.property.decimal().type.deserialize(undefined)).toBe(undefined);
|
expect(() => s.property.decimal().type.serializeDiff({} as any)).toThrowError(InvalidTypeValueError);
|
||||||
});
|
expect(() => s.property.decimal().type.resetDiff({} as any)).not.toThrow();
|
||||||
|
expect(s.property.decimal().type.hasChanged({} as any, {} as any)).toBeTruthy();
|
||||||
test("invalid parameters", () => {
|
expect(s.property.decimal().type.hasChanged(false as any, false as any)).toBeFalsy();
|
||||||
expect(s.property.decimal().type.deserialize({} as any)).toBe(NaN);
|
expect(s.property.decimal().type.serializedHasChanged({} as any, {} as any)).toBeTruthy();
|
||||||
expect(s.property.decimal().type.deserialize({} as any)).toBe(NaN);
|
expect(s.property.decimal().type.serializedHasChanged(false as any, false as any)).toBeFalsy();
|
||||||
});
|
expect(s.property.decimal().type.clone({} as any)).toStrictEqual({});
|
||||||
});
|
|
||||||
|
|
||||||
describe("serializeDiff", () => {
|
|
||||||
test("serializeDiff", () => {
|
|
||||||
expect(s.property.decimal().type.serializeDiff(542)).toBe("542");
|
|
||||||
|
|
||||||
expect(s.property.decimal().type.serializeDiff(null)).toBe(null);
|
|
||||||
expect(s.property.decimal().type.serializeDiff(undefined)).toBe(
|
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(() =>
|
|
||||||
s.property.decimal().type.serializeDiff({} as any),
|
|
||||||
).toThrowError(InvalidTypeValueError);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("resetDiff", () => {
|
|
||||||
test("resetDiff", () => {
|
|
||||||
s.property.decimal().type.resetDiff(5.257);
|
|
||||||
s.property.decimal().type.resetDiff(undefined);
|
|
||||||
s.property.decimal().type.resetDiff(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(() =>
|
|
||||||
s.property.decimal().type.resetDiff({} as any),
|
|
||||||
).not.toThrow();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("hasChanged", () => {
|
|
||||||
test("hasChanged", () => {
|
|
||||||
expect(s.property.decimal().type.hasChanged(5.257, 5.257)).toBeFalsy();
|
|
||||||
expect(s.property.decimal().type.hasChanged(null, null)).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property.decimal().type.hasChanged(undefined, undefined),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property.decimal().type.hasChanged(null, undefined),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.decimal().type.hasChanged(undefined, null),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(s.property.decimal().type.hasChanged(null, 5.257)).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.decimal().type.hasChanged(undefined, 5.257),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(s.property.decimal().type.hasChanged(5.257, null)).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.decimal().type.hasChanged(5.257, undefined),
|
|
||||||
).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(
|
|
||||||
s.property.decimal().type.hasChanged({} as any, {} as any),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.decimal().type.hasChanged(false as any, false as any),
|
|
||||||
).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("serializedHasChanged", () => {
|
|
||||||
test("serializedHasChanged", () => {
|
|
||||||
expect(
|
|
||||||
s.property.decimal().type.serializedHasChanged("5.257", "5.257"),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property.decimal().type.serializedHasChanged(null, null),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property.decimal().type.serializedHasChanged(undefined, undefined),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property.decimal().type.serializedHasChanged(null, undefined),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.decimal().type.serializedHasChanged(undefined, null),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.decimal().type.serializedHasChanged(null, "5.257"),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.decimal().type.serializedHasChanged(undefined, "5.257"),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.decimal().type.serializedHasChanged("5.257", null),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.decimal().type.serializedHasChanged("5.257", undefined),
|
|
||||||
).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(
|
|
||||||
s.property.decimal().type.serializedHasChanged({} as any, {} as any),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property
|
|
||||||
.decimal()
|
|
||||||
.type.serializedHasChanged(false as any, false as any),
|
|
||||||
).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("clone", () => {
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(s.property.decimal().type.clone({} as any)).toStrictEqual({});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("applyPatch", () => {
|
|
||||||
expect(s.property.decimal().type.applyPatch(1, "5.257", false)).toBe(5.257);
|
|
||||||
expect(s.property.decimal().type.applyPatch(undefined, "5.257", true)).toBe(
|
|
||||||
5.257,
|
|
||||||
);
|
|
||||||
expect(s.property.decimal().type.applyPatch(null, "5.257", false)).toBe(
|
|
||||||
5.257,
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
s.property.decimal().type.applyPatch(5.257, undefined, false),
|
|
||||||
).toBeUndefined();
|
|
||||||
expect(s.property.decimal().type.applyPatch(5.257, null, false)).toBeNull();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,283 +0,0 @@
|
||||||
import {describe, expect, test} from "vitest";
|
|
||||||
import {InvalidTypeValueError, s} from "../../../src/library";
|
|
||||||
import {MapType} from "../../../src/model/types/map";
|
|
||||||
|
|
||||||
describe("map type", () => {
|
|
||||||
test("definition", () => {
|
|
||||||
const mapType = s.property.map(s.property.string(), s.property.numeric());
|
|
||||||
expect(mapType.type).toBeInstanceOf(MapType);
|
|
||||||
});
|
|
||||||
|
|
||||||
const testProperty = s.property.map(
|
|
||||||
s.property.string(),
|
|
||||||
s.property.decimal(),
|
|
||||||
);
|
|
||||||
const testMapValue = new Map<string, number>();
|
|
||||||
testMapValue.set("test", 1.52);
|
|
||||||
testMapValue.set("another", 55);
|
|
||||||
|
|
||||||
describe("serialize", () => {
|
|
||||||
test("serialize", () => {
|
|
||||||
expect(testProperty.type.serialize(testMapValue)).toEqual({
|
|
||||||
test: "1.52",
|
|
||||||
another: "55",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(testProperty.type.serialize(null)).toEqual(null);
|
|
||||||
expect(testProperty.type.serialize(undefined)).toEqual(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(() => testProperty.type.serialize(5 as any)).toThrowError(
|
|
||||||
InvalidTypeValueError,
|
|
||||||
);
|
|
||||||
expect(() => testProperty.type.serialize([] as any)).toThrowError(
|
|
||||||
InvalidTypeValueError,
|
|
||||||
);
|
|
||||||
expect(() => testProperty.type.serialize({} as any)).toThrowError(
|
|
||||||
InvalidTypeValueError,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("deserialize", () => {
|
|
||||||
test("deserialize", () => {
|
|
||||||
expect(
|
|
||||||
testProperty.type.deserialize({
|
|
||||||
test: "1.52",
|
|
||||||
another: "55",
|
|
||||||
}),
|
|
||||||
).toEqual(testMapValue);
|
|
||||||
|
|
||||||
expect(testProperty.type.deserialize(null)).toEqual(null);
|
|
||||||
expect(testProperty.type.deserialize(undefined)).toEqual(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(() => testProperty.type.deserialize(5 as any)).toThrowError(
|
|
||||||
InvalidTypeValueError,
|
|
||||||
);
|
|
||||||
expect(() => testProperty.type.deserialize([] as any)).toThrowError(
|
|
||||||
InvalidTypeValueError,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("serializeDiff", () => {
|
|
||||||
test("serializeDiff", () => {
|
|
||||||
expect(testProperty.type.serializeDiff(testMapValue)).toEqual({
|
|
||||||
test: "1.52",
|
|
||||||
another: "55",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(testProperty.type.serializeDiff(null)).toEqual(null);
|
|
||||||
expect(testProperty.type.serializeDiff(undefined)).toEqual(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(() => testProperty.type.serializeDiff(5 as any)).toThrowError(
|
|
||||||
InvalidTypeValueError,
|
|
||||||
);
|
|
||||||
expect(() => testProperty.type.serializeDiff([] as any)).toThrowError(
|
|
||||||
InvalidTypeValueError,
|
|
||||||
);
|
|
||||||
expect(() => testProperty.type.serializeDiff({} as any)).toThrowError(
|
|
||||||
InvalidTypeValueError,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("hasChanged", () => {
|
|
||||||
test("hasChanged", () => {
|
|
||||||
const anotherTestMapValue = new Map<string, number>();
|
|
||||||
anotherTestMapValue.set("test", 1.52);
|
|
||||||
anotherTestMapValue.set("another", 55);
|
|
||||||
expect(
|
|
||||||
testProperty.type.hasChanged(testMapValue, anotherTestMapValue),
|
|
||||||
).toBeFalsy();
|
|
||||||
anotherTestMapValue.set("test", 1.521);
|
|
||||||
expect(
|
|
||||||
testProperty.type.hasChanged(testMapValue, anotherTestMapValue),
|
|
||||||
).toBeTruthy();
|
|
||||||
anotherTestMapValue.delete("test");
|
|
||||||
expect(
|
|
||||||
testProperty.type.hasChanged(testMapValue, anotherTestMapValue),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(testProperty.type.hasChanged(null, null)).toBeFalsy();
|
|
||||||
expect(testProperty.type.hasChanged(undefined, undefined)).toBeFalsy();
|
|
||||||
expect(testProperty.type.hasChanged(null, undefined)).toBeTruthy();
|
|
||||||
expect(testProperty.type.hasChanged(undefined, null)).toBeTruthy();
|
|
||||||
expect(testProperty.type.hasChanged(null, testMapValue)).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.hasChanged(undefined, testMapValue),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(testProperty.type.hasChanged(testMapValue, null)).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.hasChanged(testMapValue, undefined),
|
|
||||||
).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("serializedHasChanged", () => {
|
|
||||||
test("serializedHasChanged", () => {
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(
|
|
||||||
{test: "1.52", another: "55"},
|
|
||||||
{test: "1.52", another: "55"},
|
|
||||||
),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(
|
|
||||||
{test: "1.52", another: "55"},
|
|
||||||
{test: "1.521", another: "55"},
|
|
||||||
),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(
|
|
||||||
{test: "1.52", another: "55"},
|
|
||||||
{another: "55"},
|
|
||||||
),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(testProperty.type.serializedHasChanged(null, null)).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(undefined, undefined),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(null, undefined),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(undefined, null),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(null, {
|
|
||||||
test: "1.52",
|
|
||||||
another: "55",
|
|
||||||
}),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(undefined, {
|
|
||||||
test: "1.52",
|
|
||||||
another: "55",
|
|
||||||
}),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(
|
|
||||||
{test: "1.52", another: "55"},
|
|
||||||
null,
|
|
||||||
),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(
|
|
||||||
{test: "1.52", another: "55"},
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("resetDiff", () => {
|
|
||||||
test("resetDiff", () => {
|
|
||||||
testProperty.type.resetDiff(testMapValue);
|
|
||||||
testProperty.type.resetDiff(undefined);
|
|
||||||
testProperty.type.resetDiff(null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("clone", () => {
|
|
||||||
test("clone", () => {
|
|
||||||
{
|
|
||||||
// Test that keys and values are cloned in a different map.
|
|
||||||
const clonedTestMapValue = testProperty.type.clone(testMapValue);
|
|
||||||
expect(clonedTestMapValue).not.toBe(testMapValue);
|
|
||||||
expect(clonedTestMapValue).toEqual(testMapValue);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
// Test that values are cloned in a different object.
|
|
||||||
const propertyValue = new Map();
|
|
||||||
propertyValue.set("test", [12, 11]);
|
|
||||||
const clonedPropertyValue = s.property
|
|
||||||
.stringMap(s.property.array(s.property.numeric()))
|
|
||||||
.type.clone(propertyValue);
|
|
||||||
expect(clonedPropertyValue).not.toBe(propertyValue);
|
|
||||||
expect(clonedPropertyValue).toEqual(propertyValue);
|
|
||||||
expect(clonedPropertyValue.get("test")).not.toBe(
|
|
||||||
propertyValue.get("test"),
|
|
||||||
);
|
|
||||||
expect(clonedPropertyValue.get("test")).toEqual(
|
|
||||||
propertyValue.get("test"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
expect(testProperty.type.clone(undefined)).toBe(undefined);
|
|
||||||
expect(testProperty.type.clone(null)).toBe(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(() => testProperty.type.clone(5 as any)).toThrowError(
|
|
||||||
InvalidTypeValueError,
|
|
||||||
);
|
|
||||||
expect(() => testProperty.type.clone([] as any)).toThrowError(
|
|
||||||
InvalidTypeValueError,
|
|
||||||
);
|
|
||||||
expect(() => testProperty.type.clone({} as any)).toThrowError(
|
|
||||||
InvalidTypeValueError,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("applyPatch", () => {
|
|
||||||
{
|
|
||||||
// Apply a patch with undefined / NULL values.
|
|
||||||
expect(
|
|
||||||
testProperty.type.applyPatch(testMapValue, undefined, false),
|
|
||||||
).toBeUndefined();
|
|
||||||
expect(testProperty.type.applyPatch(testMapValue, null, true)).toBeNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
// Invalid patch.
|
|
||||||
expect(() =>
|
|
||||||
testProperty.type.applyPatch(testMapValue, 5416 as any, false),
|
|
||||||
).toThrow(InvalidTypeValueError);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
// Apply a patch.
|
|
||||||
{
|
|
||||||
const objectInstance = testProperty.type.applyPatch(
|
|
||||||
testMapValue,
|
|
||||||
{test: "1.521"},
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
const expectedMapValue = new Map<string, number>();
|
|
||||||
expectedMapValue.set("test", 1.521);
|
|
||||||
expectedMapValue.set("another", 55);
|
|
||||||
expect(objectInstance).toStrictEqual(expectedMapValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const objectInstance = testProperty.type.applyPatch(
|
|
||||||
undefined,
|
|
||||||
{test: "1.52"},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
const expectedMapValue = new Map<string, number>();
|
|
||||||
expectedMapValue.set("test", 1.52);
|
|
||||||
expect(objectInstance).toStrictEqual(expectedMapValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const objectInstance = testProperty.type.applyPatch(
|
|
||||||
null,
|
|
||||||
{test: "1.52"},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
const expectedMapValue = new Map<string, number>();
|
|
||||||
expectedMapValue.set("test", 1.52);
|
|
||||||
expect(objectInstance).toStrictEqual(expectedMapValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,7 +1,8 @@
|
||||||
import {describe, expect, test} from "vitest";
|
import {describe, expect, test} from "vitest";
|
||||||
import {InvalidTypeValueError, ModelType, s} from "../../../src/library";
|
import {InvalidTypeValueError, ModelType, s} from "../../../src/library";
|
||||||
|
|
||||||
class TestModel {
|
class TestModel
|
||||||
|
{
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
price: number;
|
price: number;
|
||||||
|
@ -18,565 +19,114 @@ describe("model type", () => {
|
||||||
identifier: "id",
|
identifier: "id",
|
||||||
});
|
});
|
||||||
|
|
||||||
test("definition", () => {
|
test("model type definition", () => {
|
||||||
const modelType = s.property.model(testModel);
|
const modelType = s.property.model(testModel);
|
||||||
expect(modelType.type).toBeInstanceOf(ModelType);
|
expect(modelType.type).toBeInstanceOf(ModelType);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("serialize", () => {
|
test("model type functions", () => {
|
||||||
test("serialize", () => {
|
{ // Try to serialize / deserialize.
|
||||||
const testModelInstance = testModel.model(
|
const testModelInstance = testModel.model(Object.assign(new TestModel(), { id: 1, name: "test", price: 12.548777 })).instance;
|
||||||
Object.assign(new TestModel(), {
|
expect(s.property.model(testModel).type.serialize(testModelInstance)).toEqual({ id: 1, name: "test", price: "12.548777" });
|
||||||
id: 1,
|
expect(testModel.model(
|
||||||
name: "test",
|
s.property.model(testModel).type.deserialize({ id: 1, name: "test", price: "12.548777" })
|
||||||
price: 12.548777,
|
).getInstanceProperties()).toEqual(testModel.model(testModelInstance).getInstanceProperties());
|
||||||
}),
|
}
|
||||||
).instance;
|
|
||||||
|
|
||||||
expect(
|
|
||||||
s.property.model(testModel).type.serialize(testModelInstance),
|
|
||||||
).toEqual({id: 1, name: "test", price: "12.548777"});
|
|
||||||
|
|
||||||
expect(s.property.model(testModel).type.serialize(null)).toEqual(null);
|
|
||||||
expect(s.property.model(testModel).type.serialize(undefined)).toEqual(
|
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(() =>
|
|
||||||
s.property.model(testModel).type.serialize(5 as any),
|
|
||||||
).toThrowError(InvalidTypeValueError);
|
|
||||||
expect(() =>
|
|
||||||
s.property.model(testModel).type.serialize([] as any),
|
|
||||||
).toThrowError(InvalidTypeValueError);
|
|
||||||
expect(() =>
|
|
||||||
s.property.model(testModel).type.serialize(new (class {})() as any),
|
|
||||||
).toThrowError(InvalidTypeValueError);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("deserialize", () => {
|
|
||||||
test("deserialize", () => {
|
|
||||||
const testModelInstance = testModel.model(
|
|
||||||
Object.assign(new TestModel(), {
|
|
||||||
id: 1,
|
|
||||||
name: "test",
|
|
||||||
price: 12.548777,
|
|
||||||
}),
|
|
||||||
).instance;
|
|
||||||
|
|
||||||
expect(
|
|
||||||
testModel
|
|
||||||
.model(
|
|
||||||
s.property
|
|
||||||
.model(testModel)
|
|
||||||
.type.deserialize({id: 1, name: "test", price: "12.548777"}),
|
|
||||||
)
|
|
||||||
.getInstanceProperties(),
|
|
||||||
).toEqual(testModel.model(testModelInstance).getInstanceProperties());
|
|
||||||
|
|
||||||
expect(s.property.model(testModel).type.deserialize(null)).toEqual(null);
|
|
||||||
expect(s.property.model(testModel).type.deserialize(undefined)).toEqual(
|
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(() =>
|
|
||||||
s.property.model(testModel).type.deserialize(5 as any),
|
|
||||||
).toThrowError(InvalidTypeValueError);
|
|
||||||
expect(() =>
|
|
||||||
s.property.model(testModel).type.deserialize([] as any),
|
|
||||||
).toThrowError(InvalidTypeValueError);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("serializeDiff", () => {
|
|
||||||
test("serializeDiff", () => {
|
|
||||||
// Try to serialize the difference.
|
|
||||||
const testModelInstance = testModel.model(
|
|
||||||
Object.assign(new TestModel(), {
|
|
||||||
id: 1,
|
|
||||||
name: "test",
|
|
||||||
price: 12.548777,
|
|
||||||
}),
|
|
||||||
).instance;
|
|
||||||
|
|
||||||
|
{ // Try to serialize the difference.
|
||||||
|
const testModelInstance = testModel.model(Object.assign(new TestModel(), { id: 1, name: "test", price: 12.548777 })).instance;
|
||||||
testModelInstance.name = "new";
|
testModelInstance.name = "new";
|
||||||
expect(
|
expect(s.property.model(testModel).type.serializeDiff(testModelInstance)).toEqual({ id: 1, name: "new" });
|
||||||
s.property.model(testModel).type.serializeDiff(testModelInstance),
|
}
|
||||||
).toEqual({id: 1, name: "new"});
|
|
||||||
|
|
||||||
expect(s.property.model(testModel).type.serializeDiff(null)).toEqual(
|
expect(s.property.model(testModel).type.serialize(null)).toEqual(null);
|
||||||
null,
|
expect(s.property.model(testModel).type.deserialize(null)).toEqual(null);
|
||||||
);
|
expect(s.property.model(testModel).type.serializeDiff(null)).toEqual(null);
|
||||||
expect(s.property.model(testModel).type.serializeDiff(undefined)).toEqual(
|
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
expect(s.property.model(testModel).type.serialize(undefined)).toEqual(undefined);
|
||||||
expect(() =>
|
expect(s.property.model(testModel).type.deserialize(undefined)).toEqual(undefined);
|
||||||
s.property.model(testModel).type.serializeDiff(5 as any),
|
expect(s.property.model(testModel).type.serializeDiff(undefined)).toEqual(undefined);
|
||||||
).toThrowError(InvalidTypeValueError);
|
|
||||||
expect(() =>
|
|
||||||
s.property.model(testModel).type.serializeDiff([] as any),
|
|
||||||
).toThrowError(InvalidTypeValueError);
|
|
||||||
expect(() =>
|
|
||||||
s.property.model(testModel).type.serializeDiff(new (class {})() as any),
|
|
||||||
).toThrowError(InvalidTypeValueError);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("hasChanged", () => {
|
{
|
||||||
test("hasChanged", () => {
|
const testModelInstance = testModel.model(Object.assign(new TestModel(), { id: 1, name: "test", price: 12.548777 })).instance;
|
||||||
{
|
expect(s.property.model(testModel).type.hasChanged(testModelInstance, testModelInstance)).toBeFalsy();
|
||||||
const testModelInstance = testModel.model(
|
}
|
||||||
Object.assign(new TestModel(), {
|
{
|
||||||
id: 1,
|
const testModelInstance = testModel.model(Object.assign(new TestModel(), { id: 1, name: "test", price: 12.548777 })).instance;
|
||||||
name: "test",
|
testModelInstance.price = 12.548778;
|
||||||
price: 12.548777,
|
expect(s.property.model(testModel).type.hasChanged(testModelInstance, testModelInstance)).toBeTruthy();
|
||||||
}),
|
}
|
||||||
).instance;
|
expect(s.property.model(testModel).type.hasChanged(null, null)).toBeFalsy();
|
||||||
expect(
|
expect(s.property.model(testModel).type.hasChanged(undefined, undefined)).toBeFalsy();
|
||||||
s.property
|
expect(s.property.model(testModel).type.hasChanged(null, undefined)).toBeTruthy();
|
||||||
.model(testModel)
|
expect(s.property.model(testModel).type.hasChanged(undefined, null)).toBeTruthy();
|
||||||
.type.hasChanged(testModelInstance, testModelInstance),
|
expect(s.property.model(testModel).type.hasChanged(null, testModel.model(Object.assign(new TestModel(), { id: 1, name: "test", price: 12.548777 })).instance)).toBeTruthy();
|
||||||
).toBeFalsy();
|
expect(s.property.model(testModel).type.hasChanged(undefined, testModel.model(Object.assign(new TestModel(), { id: 1, name: "test", price: 12.548777 })).instance)).toBeTruthy();
|
||||||
}
|
expect(s.property.model(testModel).type.hasChanged(testModel.model(Object.assign(new TestModel(), { id: 1, name: "test", price: 12.548777 })).instance, null)).toBeTruthy();
|
||||||
{
|
expect(s.property.model(testModel).type.hasChanged(testModel.model(Object.assign(new TestModel(), { id: 1, name: "test", price: 12.548777 })).instance, undefined)).toBeTruthy();
|
||||||
const testModelInstance = testModel.model(
|
|
||||||
Object.assign(new TestModel(), {
|
|
||||||
id: 1,
|
|
||||||
name: "test",
|
|
||||||
price: 12.548777,
|
|
||||||
}),
|
|
||||||
).instance;
|
|
||||||
testModelInstance.price = 12.548778;
|
|
||||||
expect(
|
|
||||||
s.property
|
|
||||||
.model(testModel)
|
|
||||||
.type.hasChanged(testModelInstance, testModelInstance),
|
|
||||||
).toBeTruthy();
|
|
||||||
}
|
|
||||||
expect(
|
|
||||||
s.property.model(testModel).type.hasChanged(null, null),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property.model(testModel).type.hasChanged(undefined, undefined),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property.model(testModel).type.hasChanged(null, undefined),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.model(testModel).type.hasChanged(undefined, null),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.model(testModel).type.hasChanged(
|
|
||||||
null,
|
|
||||||
testModel.model(
|
|
||||||
Object.assign(new TestModel(), {
|
|
||||||
id: 1,
|
|
||||||
name: "test",
|
|
||||||
price: 12.548777,
|
|
||||||
}),
|
|
||||||
).instance,
|
|
||||||
),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.model(testModel).type.hasChanged(
|
|
||||||
undefined,
|
|
||||||
testModel.model(
|
|
||||||
Object.assign(new TestModel(), {
|
|
||||||
id: 1,
|
|
||||||
name: "test",
|
|
||||||
price: 12.548777,
|
|
||||||
}),
|
|
||||||
).instance,
|
|
||||||
),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.model(testModel).type.hasChanged(
|
|
||||||
testModel.model(
|
|
||||||
Object.assign(new TestModel(), {
|
|
||||||
id: 1,
|
|
||||||
name: "test",
|
|
||||||
price: 12.548777,
|
|
||||||
}),
|
|
||||||
).instance,
|
|
||||||
null,
|
|
||||||
),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.model(testModel).type.hasChanged(
|
|
||||||
testModel.model(
|
|
||||||
Object.assign(new TestModel(), {
|
|
||||||
id: 1,
|
|
||||||
name: "test",
|
|
||||||
price: 12.548777,
|
|
||||||
}),
|
|
||||||
).instance,
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
expect(s.property.model(testModel).type.serializedHasChanged(
|
||||||
expect(() =>
|
{ id: 1, name: "test", price: "12.548777" },
|
||||||
s.property.model(testModel).type.hasChanged(5 as any, 5 as any),
|
{ id: 1, price: "12.548777", name: "test" },
|
||||||
).toThrowError(InvalidTypeValueError);
|
)).toBeFalsy();
|
||||||
expect(() =>
|
expect(s.property.model(testModel).type.serializedHasChanged(
|
||||||
s.property
|
{ id: 1, name: "test", price: "12.548777" },
|
||||||
.model(testModel)
|
{ id: 1, name: "test", price: "12.548778" },
|
||||||
.type.hasChanged(
|
)).toBeTruthy();
|
||||||
testModel.model(new TestModel()).instance,
|
expect(s.property.model(testModel).type.serializedHasChanged(null, null)).toBeFalsy();
|
||||||
[] as any,
|
expect(s.property.model(testModel).type.serializedHasChanged(undefined, undefined)).toBeFalsy();
|
||||||
),
|
expect(s.property.model(testModel).type.serializedHasChanged(null, undefined)).toBeTruthy();
|
||||||
).toThrowError(InvalidTypeValueError);
|
expect(s.property.model(testModel).type.serializedHasChanged(undefined, null)).toBeTruthy();
|
||||||
expect(() =>
|
expect(s.property.model(testModel).type.serializedHasChanged(null, { id: 1, name: "test", price: "12.548777" })).toBeTruthy();
|
||||||
s.property
|
expect(s.property.model(testModel).type.serializedHasChanged(undefined, { id: 1, name: "test", price: "12.548777" })).toBeTruthy();
|
||||||
.model(testModel)
|
expect(s.property.model(testModel).type.serializedHasChanged({ id: 1, name: "test", price: "12.548777" }, null)).toBeTruthy();
|
||||||
.type.hasChanged(
|
expect(s.property.model(testModel).type.serializedHasChanged({ id: 1, name: "test", price: "12.548777" }, undefined)).toBeTruthy();
|
||||||
testModel.model(new TestModel()).instance,
|
|
||||||
new (class {})() as any,
|
|
||||||
),
|
|
||||||
).toThrowError(InvalidTypeValueError);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("serializedHasChanged", () => {
|
|
||||||
test("serializedHasChanged", () => {
|
|
||||||
expect(
|
|
||||||
s.property
|
|
||||||
.model(testModel)
|
|
||||||
.type.serializedHasChanged(
|
|
||||||
{id: 1, name: "test", price: "12.548777"},
|
|
||||||
{id: 1, price: "12.548777", name: "test"},
|
|
||||||
),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property
|
|
||||||
.model(testModel)
|
|
||||||
.type.serializedHasChanged(
|
|
||||||
{id: 1, name: "test", price: "12.548777"},
|
|
||||||
{id: 1, name: "test", price: "12.548778"},
|
|
||||||
),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.model(testModel).type.serializedHasChanged(null, null),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property
|
|
||||||
.model(testModel)
|
|
||||||
.type.serializedHasChanged(undefined, undefined),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property.model(testModel).type.serializedHasChanged(null, undefined),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.model(testModel).type.serializedHasChanged(undefined, null),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.model(testModel).type.serializedHasChanged(null, {
|
|
||||||
id: 1,
|
|
||||||
name: "test",
|
|
||||||
price: "12.548777",
|
|
||||||
}),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.model(testModel).type.serializedHasChanged(undefined, {
|
|
||||||
id: 1,
|
|
||||||
name: "test",
|
|
||||||
price: "12.548777",
|
|
||||||
}),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property
|
|
||||||
.model(testModel)
|
|
||||||
.type.serializedHasChanged(
|
|
||||||
{id: 1, name: "test", price: "12.548777"},
|
|
||||||
null,
|
|
||||||
),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property
|
|
||||||
.model(testModel)
|
|
||||||
.type.serializedHasChanged(
|
|
||||||
{id: 1, name: "test", price: "12.548777"},
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(() =>
|
|
||||||
s.property
|
|
||||||
.model(testModel)
|
|
||||||
.type.serializedHasChanged(5 as any, 5 as any),
|
|
||||||
).toThrowError(InvalidTypeValueError);
|
|
||||||
expect(() =>
|
|
||||||
s.property
|
|
||||||
.model(testModel)
|
|
||||||
.type.serializedHasChanged({} as any, [] as any),
|
|
||||||
).toThrowError(InvalidTypeValueError);
|
|
||||||
expect(
|
|
||||||
s.property
|
|
||||||
.model(testModel)
|
|
||||||
.type.serializedHasChanged({} as any, new (class {})() as any),
|
|
||||||
).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("resetDiff", () => {
|
|
||||||
test("resetDiff", () => {
|
|
||||||
const testModelInstance = testModel.model(
|
|
||||||
Object.assign(new TestModel(), {
|
|
||||||
id: 1,
|
|
||||||
name: "test",
|
|
||||||
price: 12.548777,
|
|
||||||
}),
|
|
||||||
).instance;
|
|
||||||
|
|
||||||
|
{ // Serializing the difference to check that the difference has been reset.
|
||||||
|
const testModelInstance = testModel.model(Object.assign(new TestModel(), { id: 1, name: "test", price: 12.548777 })).instance;
|
||||||
testModelInstance.price = 555.555;
|
testModelInstance.price = 555.555;
|
||||||
expect(testModel.model(testModelInstance).serializeDiff()).toEqual({
|
expect(testModel.model(testModelInstance).serializeDiff()).toEqual({ id: 1, price: "555.555" });
|
||||||
id: 1,
|
|
||||||
price: "555.555",
|
|
||||||
});
|
|
||||||
s.property.model(testModel).type.resetDiff(testModelInstance);
|
s.property.model(testModel).type.resetDiff(testModelInstance);
|
||||||
expect(testModel.model(testModelInstance).serializeDiff()).toEqual({
|
expect(testModel.model(testModelInstance).serializeDiff()).toEqual({ id: 1 });
|
||||||
id: 1,
|
}
|
||||||
});
|
|
||||||
|
|
||||||
s.property.model(testModel).type.resetDiff(undefined);
|
s.property.model(testModel).type.resetDiff(undefined);
|
||||||
s.property.model(testModel).type.resetDiff(null);
|
s.property.model(testModel).type.resetDiff(null);
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
{ // Test that values are cloned in a different model instance.
|
||||||
expect(() =>
|
const testModelInstance = testModel.model(Object.assign(new TestModel(), { id: 1, name: "test", price: 12.548777 })).instance;
|
||||||
s.property.model(testModel).type.resetDiff(5 as any),
|
|
||||||
).toThrowError(InvalidTypeValueError);
|
|
||||||
expect(() =>
|
|
||||||
s.property.model(testModel).type.resetDiff([] as any),
|
|
||||||
).toThrowError(InvalidTypeValueError);
|
|
||||||
expect(() =>
|
|
||||||
s.property.model(testModel).type.resetDiff(new (class {})() as any),
|
|
||||||
).toThrowError(InvalidTypeValueError);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("clone", () => {
|
|
||||||
test("clone", () => {
|
|
||||||
// Test that values are cloned in a different model instance.
|
|
||||||
const testModelInstance = testModel.model(
|
|
||||||
Object.assign(new TestModel(), {
|
|
||||||
id: 1,
|
|
||||||
name: "test",
|
|
||||||
price: 12.548777,
|
|
||||||
}),
|
|
||||||
).instance;
|
|
||||||
testModelInstance.price = 555.555;
|
testModelInstance.price = 555.555;
|
||||||
const clonedModelInstance = s.property
|
const clonedModelInstance = s.property.model(testModel).type.clone(testModelInstance);
|
||||||
.model(testModel)
|
|
||||||
.type.clone(testModelInstance);
|
|
||||||
expect(clonedModelInstance).not.toBe(testModelInstance);
|
expect(clonedModelInstance).not.toBe(testModelInstance);
|
||||||
expect(
|
expect(testModel.model(clonedModelInstance).getInstanceProperties()).toEqual(testModel.model(testModelInstance).getInstanceProperties());
|
||||||
testModel.model(clonedModelInstance).getInstanceProperties(),
|
expect(testModel.model(clonedModelInstance).serializeDiff()).toEqual(testModel.model(testModelInstance).serializeDiff());
|
||||||
).toEqual(testModel.model(testModelInstance).getInstanceProperties());
|
}
|
||||||
expect(testModel.model(clonedModelInstance).serializeDiff()).toEqual(
|
expect(s.property.model(testModel).type.clone(undefined)).toBe(undefined);
|
||||||
testModel.model(testModelInstance).serializeDiff(),
|
expect(s.property.model(testModel).type.clone(null)).toBe(null);
|
||||||
);
|
|
||||||
|
|
||||||
expect(s.property.model(testModel).type.clone(undefined)).toBe(undefined);
|
|
||||||
expect(s.property.model(testModel).type.clone(null)).toBe(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(() =>
|
|
||||||
s.property.model(testModel).type.clone(5 as any),
|
|
||||||
).toThrowError(InvalidTypeValueError);
|
|
||||||
expect(() =>
|
|
||||||
s.property.model(testModel).type.clone([] as any),
|
|
||||||
).toThrowError(InvalidTypeValueError);
|
|
||||||
expect(() =>
|
|
||||||
s.property.model(testModel).type.clone(new (class {})() as any),
|
|
||||||
).toThrowError(InvalidTypeValueError);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("applyPatch", () => {
|
test("invalid parameters types", () => {
|
||||||
{
|
expect(() => s.property.model(testModel).type.serialize(5 as any)).toThrowError(InvalidTypeValueError);
|
||||||
// Apply a patch with undefined / NULL values.
|
expect(() => s.property.model(testModel).type.deserialize(5 as any)).toThrowError(InvalidTypeValueError);
|
||||||
expect(
|
expect(() => s.property.model(testModel).type.serializeDiff(5 as any)).toThrowError(InvalidTypeValueError);
|
||||||
s.property.model(testModel).type.applyPatch(
|
expect(() => s.property.model(testModel).type.resetDiff(5 as any)).toThrowError(InvalidTypeValueError);
|
||||||
testModel.model(
|
expect(() => s.property.model(testModel).type.hasChanged(5 as any, 5 as any)).toThrowError(InvalidTypeValueError);
|
||||||
Object.assign(new TestModel(), {
|
expect(() => s.property.model(testModel).type.serializedHasChanged(5 as any, 5 as any)).toThrowError(InvalidTypeValueError);
|
||||||
id: 1,
|
expect(() => s.property.model(testModel).type.clone(5 as any)).toThrowError(InvalidTypeValueError);
|
||||||
name: "test",
|
|
||||||
price: 12.548777,
|
|
||||||
}),
|
|
||||||
).instance,
|
|
||||||
undefined,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
).toBeUndefined();
|
|
||||||
expect(
|
|
||||||
s.property.model(testModel).type.applyPatch(
|
|
||||||
testModel.model(
|
|
||||||
Object.assign(new TestModel(), {
|
|
||||||
id: 1,
|
|
||||||
name: "test",
|
|
||||||
price: 12.548777,
|
|
||||||
}),
|
|
||||||
).instance,
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
).toBeNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
expect(() => s.property.model(testModel).type.serialize([] as any)).toThrowError(InvalidTypeValueError);
|
||||||
// Invalid patch.
|
expect(() => s.property.model(testModel).type.deserialize([] as any)).toThrowError(InvalidTypeValueError);
|
||||||
expect(() =>
|
expect(() => s.property.model(testModel).type.serializeDiff([] as any)).toThrowError(InvalidTypeValueError);
|
||||||
s.property.model(testModel).type.applyPatch(
|
expect(() => s.property.model(testModel).type.resetDiff([] as any)).toThrowError(InvalidTypeValueError);
|
||||||
testModel.model(
|
expect(() => s.property.model(testModel).type.hasChanged(testModel.model(new TestModel()).instance, [] as any)).toThrowError(InvalidTypeValueError);
|
||||||
Object.assign(new TestModel(), {
|
expect(() => s.property.model(testModel).type.serializedHasChanged({} as any, [] as any)).toThrowError(InvalidTypeValueError);
|
||||||
id: 1,
|
expect(() => s.property.model(testModel).type.clone([] as any)).toThrowError(InvalidTypeValueError);
|
||||||
name: "test",
|
|
||||||
price: 12.548777,
|
|
||||||
}),
|
|
||||||
).instance,
|
|
||||||
5416 as any,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
).toThrow(InvalidTypeValueError);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
expect(() => s.property.model(testModel).type.serialize(new class{} as any)).toThrowError(InvalidTypeValueError);
|
||||||
// Apply a patch with originals update.
|
expect(() => s.property.model(testModel).type.serializeDiff(new class{} as any)).toThrowError(InvalidTypeValueError);
|
||||||
{
|
expect(() => s.property.model(testModel).type.resetDiff(new class{} as any)).toThrowError(InvalidTypeValueError);
|
||||||
const modelInstance = s.property.model(testModel).type.applyPatch(
|
expect(() => s.property.model(testModel).type.hasChanged(testModel.model(new TestModel()).instance, new class{} as any)).toThrowError(InvalidTypeValueError);
|
||||||
testModel.model(
|
expect(s.property.model(testModel).type.serializedHasChanged({} as any, new class{} as any)).toBeFalsy();
|
||||||
Object.assign(new TestModel(), {
|
expect(() => s.property.model(testModel).type.clone(new class{} as any)).toThrowError(InvalidTypeValueError);
|
||||||
id: 1,
|
|
||||||
name: "test",
|
|
||||||
price: 12.548777,
|
|
||||||
}),
|
|
||||||
).instance,
|
|
||||||
{id: 1, name: "another"},
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
testModel.model(modelInstance).getInstanceProperties(),
|
|
||||||
).toStrictEqual({
|
|
||||||
id: 1,
|
|
||||||
name: "another",
|
|
||||||
price: 12.548777,
|
|
||||||
});
|
|
||||||
expect(testModel.model(modelInstance).serializeDiff()).toStrictEqual({
|
|
||||||
id: 1,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const modelInstance = s.property
|
|
||||||
.model(testModel)
|
|
||||||
.type.applyPatch(undefined, {id: 1, name: "test"}, true);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
testModel.model(modelInstance).getInstanceProperties(),
|
|
||||||
).toStrictEqual({
|
|
||||||
id: 1,
|
|
||||||
name: "test",
|
|
||||||
price: undefined,
|
|
||||||
});
|
|
||||||
expect(testModel.model(modelInstance).serializeDiff()).toStrictEqual({
|
|
||||||
id: 1,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const modelInstance = s.property
|
|
||||||
.model(testModel)
|
|
||||||
.type.applyPatch(null, {id: 1, name: "test"}, true);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
testModel.model(modelInstance).getInstanceProperties(),
|
|
||||||
).toStrictEqual({
|
|
||||||
id: 1,
|
|
||||||
name: "test",
|
|
||||||
price: undefined,
|
|
||||||
});
|
|
||||||
expect(testModel.model(modelInstance).serializeDiff()).toStrictEqual({
|
|
||||||
id: 1,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
// Apply a patch without originals update.
|
|
||||||
{
|
|
||||||
const modelInstance = s.property.model(testModel).type.applyPatch(
|
|
||||||
testModel.model(
|
|
||||||
Object.assign(new TestModel(), {
|
|
||||||
id: 1,
|
|
||||||
name: "test",
|
|
||||||
price: 12.548777,
|
|
||||||
}),
|
|
||||||
).instance,
|
|
||||||
{id: 1, name: "another"},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
testModel.model(modelInstance).getInstanceProperties(),
|
|
||||||
).toStrictEqual({
|
|
||||||
id: 1,
|
|
||||||
name: "another",
|
|
||||||
price: 12.548777,
|
|
||||||
});
|
|
||||||
expect(testModel.model(modelInstance).serializeDiff()).toStrictEqual({
|
|
||||||
id: 1,
|
|
||||||
name: "another",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const modelInstance = s.property
|
|
||||||
.model(testModel)
|
|
||||||
.type.applyPatch(undefined, {id: 1, name: "test"}, false);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
testModel.model(modelInstance).getInstanceProperties(),
|
|
||||||
).toStrictEqual({
|
|
||||||
id: 1,
|
|
||||||
name: "test",
|
|
||||||
price: undefined,
|
|
||||||
});
|
|
||||||
expect(testModel.model(modelInstance).serializeDiff()).toStrictEqual({
|
|
||||||
id: 1,
|
|
||||||
name: "test",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const modelInstance = s.property
|
|
||||||
.model(testModel)
|
|
||||||
.type.applyPatch(null, {id: 1, name: "test"}, false);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
testModel.model(modelInstance).getInstanceProperties(),
|
|
||||||
).toStrictEqual({
|
|
||||||
id: 1,
|
|
||||||
name: "test",
|
|
||||||
price: undefined,
|
|
||||||
});
|
|
||||||
expect(testModel.model(modelInstance).serializeDiff()).toStrictEqual({
|
|
||||||
id: 1,
|
|
||||||
name: "test",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,163 +2,58 @@ import {describe, expect, test} from "vitest";
|
||||||
import {InvalidTypeValueError, NumericType, s} from "../../../src/library";
|
import {InvalidTypeValueError, NumericType, s} from "../../../src/library";
|
||||||
|
|
||||||
describe("numeric type", () => {
|
describe("numeric type", () => {
|
||||||
test("definition", () => {
|
test("numeric type definition", () => {
|
||||||
const numericType = s.property.numeric();
|
const numericType = s.property.numeric();
|
||||||
expect(numericType.type).toBeInstanceOf(NumericType);
|
expect(numericType.type).toBeInstanceOf(NumericType);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("serialize", () => {
|
test("numeric type functions", () => {
|
||||||
test("serialize", () => {
|
expect(s.property.numeric().type.serialize(5.257)).toBe(5.257);
|
||||||
expect(s.property.numeric().type.serialize(5.257)).toBe(5.257);
|
expect(s.property.numeric().type.deserialize(5.257)).toBe(5.257);
|
||||||
|
expect(s.property.numeric().type.serializeDiff(542)).toBe(542);
|
||||||
|
|
||||||
expect(s.property.numeric().type.serialize(null)).toBe(null);
|
expect(s.property.numeric().type.serialize(null)).toBe(null);
|
||||||
expect(s.property.numeric().type.serialize(undefined)).toBe(undefined);
|
expect(s.property.numeric().type.deserialize(null)).toBe(null);
|
||||||
});
|
expect(s.property.numeric().type.serializeDiff(null)).toBe(null);
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
expect(s.property.numeric().type.serialize(undefined)).toBe(undefined);
|
||||||
expect(() => s.property.numeric().type.serialize({} as any)).toThrowError(
|
expect(s.property.numeric().type.deserialize(undefined)).toBe(undefined);
|
||||||
InvalidTypeValueError,
|
expect(s.property.numeric().type.serializeDiff(undefined)).toBe(undefined);
|
||||||
);
|
|
||||||
});
|
expect(s.property.numeric().type.hasChanged(5.257, 5.257)).toBeFalsy();
|
||||||
|
expect(s.property.numeric().type.hasChanged(null, null)).toBeFalsy();
|
||||||
|
expect(s.property.numeric().type.hasChanged(undefined, undefined)).toBeFalsy();
|
||||||
|
expect(s.property.numeric().type.hasChanged(null, undefined)).toBeTruthy();
|
||||||
|
expect(s.property.numeric().type.hasChanged(undefined, null)).toBeTruthy();
|
||||||
|
expect(s.property.numeric().type.hasChanged(null, 5.257)).toBeTruthy();
|
||||||
|
expect(s.property.numeric().type.hasChanged(undefined, 5.257)).toBeTruthy();
|
||||||
|
expect(s.property.numeric().type.hasChanged(5.257, null)).toBeTruthy();
|
||||||
|
expect(s.property.numeric().type.hasChanged(5.257, undefined)).toBeTruthy();
|
||||||
|
|
||||||
|
expect(s.property.numeric().type.serializedHasChanged(5.257, 5.257)).toBeFalsy();
|
||||||
|
expect(s.property.numeric().type.serializedHasChanged(null, null)).toBeFalsy();
|
||||||
|
expect(s.property.numeric().type.serializedHasChanged(undefined, undefined)).toBeFalsy();
|
||||||
|
expect(s.property.numeric().type.serializedHasChanged(null, undefined)).toBeTruthy();
|
||||||
|
expect(s.property.numeric().type.serializedHasChanged(undefined, null)).toBeTruthy();
|
||||||
|
expect(s.property.numeric().type.serializedHasChanged(null, 5.257)).toBeTruthy();
|
||||||
|
expect(s.property.numeric().type.serializedHasChanged(undefined, 5.257)).toBeTruthy();
|
||||||
|
expect(s.property.numeric().type.serializedHasChanged(5.257, null)).toBeTruthy();
|
||||||
|
expect(s.property.numeric().type.serializedHasChanged(5.257, undefined)).toBeTruthy();
|
||||||
|
|
||||||
|
s.property.numeric().type.resetDiff(5.257);
|
||||||
|
s.property.numeric().type.resetDiff(undefined);
|
||||||
|
s.property.numeric().type.resetDiff(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("deserialize", () => {
|
test("invalid parameters types", () => {
|
||||||
test("deserialize", () => {
|
expect(() => s.property.numeric().type.serialize({} as any)).toThrowError(InvalidTypeValueError);
|
||||||
expect(s.property.numeric().type.deserialize(5.257)).toBe(5.257);
|
expect(() => s.property.numeric().type.deserialize({} as any)).toThrowError(InvalidTypeValueError)
|
||||||
|
expect(() => s.property.numeric().type.serializeDiff({} as any)).toThrowError(InvalidTypeValueError);
|
||||||
expect(s.property.numeric().type.deserialize(null)).toBe(null);
|
expect(() => s.property.numeric().type.resetDiff({} as any)).not.toThrow();
|
||||||
expect(s.property.numeric().type.deserialize(undefined)).toBe(undefined);
|
expect(s.property.numeric().type.hasChanged({} as any, {} as any)).toBeTruthy();
|
||||||
});
|
expect(s.property.numeric().type.hasChanged(false as any, false as any)).toBeFalsy();
|
||||||
|
expect(s.property.numeric().type.serializedHasChanged({} as any, {} as any)).toBeTruthy();
|
||||||
test("invalid parameters", () => {
|
expect(s.property.numeric().type.serializedHasChanged(false as any, false as any)).toBeFalsy();
|
||||||
expect(() =>
|
expect(s.property.numeric().type.clone({} as any)).toStrictEqual({});
|
||||||
s.property.numeric().type.deserialize({} as any),
|
|
||||||
).toThrowError(InvalidTypeValueError);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("serializeDiff", () => {
|
|
||||||
test("serializeDiff", () => {
|
|
||||||
expect(s.property.numeric().type.serializeDiff(542)).toBe(542);
|
|
||||||
|
|
||||||
expect(s.property.numeric().type.serializeDiff(null)).toBe(null);
|
|
||||||
expect(s.property.numeric().type.serializeDiff(undefined)).toBe(
|
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(() =>
|
|
||||||
s.property.numeric().type.serializeDiff({} as any),
|
|
||||||
).toThrowError(InvalidTypeValueError);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("hasChanged", () => {
|
|
||||||
test("hasChanged", () => {
|
|
||||||
expect(s.property.numeric().type.hasChanged(5.257, 5.257)).toBeFalsy();
|
|
||||||
expect(s.property.numeric().type.hasChanged(null, null)).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property.numeric().type.hasChanged(undefined, undefined),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property.numeric().type.hasChanged(null, undefined),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.numeric().type.hasChanged(undefined, null),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(s.property.numeric().type.hasChanged(null, 5.257)).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.numeric().type.hasChanged(undefined, 5.257),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(s.property.numeric().type.hasChanged(5.257, null)).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.numeric().type.hasChanged(5.257, undefined),
|
|
||||||
).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(
|
|
||||||
s.property.numeric().type.hasChanged({} as any, {} as any),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.numeric().type.hasChanged(false as any, false as any),
|
|
||||||
).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("serializedHasChanged", () => {
|
|
||||||
test("serializedHasChanged", () => {
|
|
||||||
expect(
|
|
||||||
s.property.numeric().type.serializedHasChanged(5.257, 5.257),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property.numeric().type.serializedHasChanged(null, null),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property.numeric().type.serializedHasChanged(undefined, undefined),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property.numeric().type.serializedHasChanged(null, undefined),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.numeric().type.serializedHasChanged(undefined, null),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.numeric().type.serializedHasChanged(null, 5.257),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.numeric().type.serializedHasChanged(undefined, 5.257),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.numeric().type.serializedHasChanged(5.257, null),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.numeric().type.serializedHasChanged(5.257, undefined),
|
|
||||||
).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(
|
|
||||||
s.property.numeric().type.serializedHasChanged({} as any, {} as any),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property
|
|
||||||
.numeric()
|
|
||||||
.type.serializedHasChanged(false as any, false as any),
|
|
||||||
).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("resetDiff", () => {
|
|
||||||
test("resetDiff", () => {
|
|
||||||
s.property.numeric().type.resetDiff(5.257);
|
|
||||||
s.property.numeric().type.resetDiff(undefined);
|
|
||||||
s.property.numeric().type.resetDiff(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(() =>
|
|
||||||
s.property.numeric().type.resetDiff({} as any),
|
|
||||||
).not.toThrow();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("clone", () => {
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(s.property.numeric().type.clone({} as any)).toStrictEqual({});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("applyPatch", () => {
|
|
||||||
expect(s.property.numeric().type.applyPatch(1, 5.257, false)).toBe(5.257);
|
|
||||||
expect(s.property.numeric().type.applyPatch(null, 5.257, true)).toBe(5.257);
|
|
||||||
expect(s.property.numeric().type.applyPatch(undefined, 5.257, false)).toBe(
|
|
||||||
5.257,
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
s.property.numeric().type.applyPatch(5.257, undefined, false),
|
|
||||||
).toBeUndefined();
|
|
||||||
expect(s.property.numeric().type.applyPatch(5.257, null, false)).toBeNull();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,14 +1,8 @@
|
||||||
import {describe, expect, test} from "vitest";
|
import {describe, expect, test} from "vitest";
|
||||||
import {
|
import {InvalidTypeValueError, NumericType, ObjectType, s, StringType} from "../../../src/library";
|
||||||
InvalidTypeValueError,
|
|
||||||
NumericType,
|
|
||||||
ObjectType,
|
|
||||||
s,
|
|
||||||
StringType,
|
|
||||||
} from "../../../src/library";
|
|
||||||
|
|
||||||
describe("object type", () => {
|
describe("object type", () => {
|
||||||
test("definition", () => {
|
test("object type definition", () => {
|
||||||
const objectType = s.property.object({
|
const objectType = s.property.object({
|
||||||
test: s.property.string(),
|
test: s.property.string(),
|
||||||
another: s.property.numeric(),
|
another: s.property.numeric(),
|
||||||
|
@ -16,12 +10,10 @@ describe("object type", () => {
|
||||||
expect(objectType.type).toBeInstanceOf(ObjectType);
|
expect(objectType.type).toBeInstanceOf(ObjectType);
|
||||||
|
|
||||||
expect((objectType.type as any).properties).toHaveLength(2);
|
expect((objectType.type as any).properties).toHaveLength(2);
|
||||||
for (const property of (objectType.type as any).properties) {
|
for (const property of (objectType.type as any).properties)
|
||||||
// Check all object properties.
|
{ // Check all object properties.
|
||||||
if (property.name == "test")
|
if (property.name == "test") expect(property.definition.type).toBeInstanceOf(StringType);
|
||||||
expect(property.definition.type).toBeInstanceOf(StringType);
|
else if (property.name == "another") expect(property.definition.type).toBeInstanceOf(NumericType);
|
||||||
else if (property.name == "another")
|
|
||||||
expect(property.definition.type).toBeInstanceOf(NumericType);
|
|
||||||
else expect.unreachable();
|
else expect.unreachable();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -31,293 +23,78 @@ describe("object type", () => {
|
||||||
another: s.property.decimal(),
|
another: s.property.decimal(),
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("serialize", () => {
|
test("object type functions", () => {
|
||||||
test("serialize", () => {
|
expect(testProperty.type.serialize({ test: "test", another: 12.548777 })).toEqual({ test: "test", another: "12.548777" });
|
||||||
expect(
|
expect(testProperty.type.deserialize({ test: "test", another: "12.548777" })).toEqual({ test: "test", another: 12.548777 });
|
||||||
testProperty.type.serialize({test: "test", another: 12.548777}),
|
expect(testProperty.type.serializeDiff({ test: "test", another: 12.548777 })).toEqual({ test: "test", another: "12.548777" });
|
||||||
).toEqual({test: "test", another: "12.548777"});
|
|
||||||
|
|
||||||
expect(testProperty.type.serialize(null)).toEqual(null);
|
expect(testProperty.type.serialize(null)).toEqual(null);
|
||||||
expect(testProperty.type.serialize(undefined)).toEqual(undefined);
|
expect(testProperty.type.deserialize(null)).toEqual(null);
|
||||||
});
|
expect(testProperty.type.serializeDiff(null)).toEqual(null);
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
expect(testProperty.type.serialize(undefined)).toEqual(undefined);
|
||||||
expect(() => testProperty.type.serialize(5 as any)).toThrowError(
|
expect(testProperty.type.deserialize(undefined)).toEqual(undefined);
|
||||||
InvalidTypeValueError,
|
expect(testProperty.type.serializeDiff(undefined)).toEqual(undefined);
|
||||||
);
|
|
||||||
expect(() => testProperty.type.serialize([] as any)).toThrowError(
|
|
||||||
InvalidTypeValueError,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("deserialize", () => {
|
expect(testProperty.type.hasChanged({ test: "test", another: 12.548777 }, { another: 12.548777, test: "test" })).toBeFalsy();
|
||||||
test("deserialize", () => {
|
expect(testProperty.type.hasChanged({ test: "test", another: 12.548777 }, { test: "test", another: 12.548778 })).toBeTruthy();
|
||||||
expect(
|
expect(testProperty.type.hasChanged(null, null)).toBeFalsy();
|
||||||
testProperty.type.deserialize({test: "test", another: "12.548777"}),
|
expect(testProperty.type.hasChanged(undefined, undefined)).toBeFalsy();
|
||||||
).toEqual({test: "test", another: 12.548777});
|
expect(testProperty.type.hasChanged(null, undefined)).toBeTruthy();
|
||||||
|
expect(testProperty.type.hasChanged(undefined, null)).toBeTruthy();
|
||||||
|
expect(testProperty.type.hasChanged(null, { test: "test", another: 12.548777 })).toBeTruthy();
|
||||||
|
expect(testProperty.type.hasChanged(undefined, { test: "test", another: 12.548777 })).toBeTruthy();
|
||||||
|
expect(testProperty.type.hasChanged({ test: "test", another: 12.548777 }, null)).toBeTruthy();
|
||||||
|
expect(testProperty.type.hasChanged({ test: "test", another: 12.548777 }, undefined)).toBeTruthy();
|
||||||
|
|
||||||
expect(testProperty.type.deserialize(null)).toEqual(null);
|
expect(testProperty.type.serializedHasChanged({ test: "test", another: "12.548777" }, { another: "12.548777", test: "test" })).toBeFalsy();
|
||||||
expect(testProperty.type.deserialize(undefined)).toEqual(undefined);
|
expect(testProperty.type.serializedHasChanged({ test: "test", another: "12.548777" }, { test: "test", another: "12.548778" })).toBeTruthy();
|
||||||
});
|
expect(testProperty.type.serializedHasChanged(null, null)).toBeFalsy();
|
||||||
|
expect(testProperty.type.serializedHasChanged(undefined, undefined)).toBeFalsy();
|
||||||
|
expect(testProperty.type.serializedHasChanged(null, undefined)).toBeTruthy();
|
||||||
|
expect(testProperty.type.serializedHasChanged(undefined, null)).toBeTruthy();
|
||||||
|
expect(testProperty.type.serializedHasChanged(null, { test: "test", another: "12.548777" })).toBeTruthy();
|
||||||
|
expect(testProperty.type.serializedHasChanged(undefined, { test: "test", another: "12.548777" })).toBeTruthy();
|
||||||
|
expect(testProperty.type.serializedHasChanged({ test: "test", another: "12.548777" }, null)).toBeTruthy();
|
||||||
|
expect(testProperty.type.serializedHasChanged({ test: "test", another: "12.548777" }, undefined)).toBeTruthy();
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
testProperty.type.resetDiff({ test: "test", another: 12.548777 });
|
||||||
expect(() => testProperty.type.deserialize(5 as any)).toThrowError(
|
testProperty.type.resetDiff(undefined);
|
||||||
InvalidTypeValueError,
|
testProperty.type.resetDiff(null);
|
||||||
);
|
|
||||||
expect(() => testProperty.type.deserialize([] as any)).toThrowError(
|
|
||||||
InvalidTypeValueError,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("serializeDiff", () => {
|
{ // Test that values are cloned in a different object.
|
||||||
test("serializeDiff", () => {
|
const propertyValue = { test: "test", another: 12.548777 };
|
||||||
expect(
|
const clonedPropertyValue = testProperty.type.clone(propertyValue);
|
||||||
testProperty.type.serializeDiff({test: "test", another: 12.548777}),
|
expect(clonedPropertyValue).not.toBe(propertyValue);
|
||||||
).toEqual({test: "test", another: "12.548777"});
|
expect(clonedPropertyValue).toEqual(propertyValue);
|
||||||
|
|
||||||
expect(testProperty.type.serializeDiff(null)).toEqual(null);
|
|
||||||
expect(testProperty.type.serializeDiff(undefined)).toEqual(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(() => testProperty.type.serializeDiff(5 as any)).toThrowError(
|
|
||||||
InvalidTypeValueError,
|
|
||||||
);
|
|
||||||
expect(() => testProperty.type.serializeDiff([] as any)).toThrowError(
|
|
||||||
InvalidTypeValueError,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("hasChanged", () => {
|
|
||||||
test("hasChanged", () => {
|
|
||||||
expect(
|
|
||||||
testProperty.type.hasChanged(
|
|
||||||
{test: "test", another: 12.548777},
|
|
||||||
{another: 12.548777, test: "test"},
|
|
||||||
),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.hasChanged(
|
|
||||||
{test: "test", another: 12.548777},
|
|
||||||
{test: "test", another: 12.548778},
|
|
||||||
),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(testProperty.type.hasChanged(null, null)).toBeFalsy();
|
|
||||||
expect(testProperty.type.hasChanged(undefined, undefined)).toBeFalsy();
|
|
||||||
expect(testProperty.type.hasChanged(null, undefined)).toBeTruthy();
|
|
||||||
expect(testProperty.type.hasChanged(undefined, null)).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.hasChanged(null, {test: "test", another: 12.548777}),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.hasChanged(undefined, {
|
|
||||||
test: "test",
|
|
||||||
another: 12.548777,
|
|
||||||
}),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.hasChanged({test: "test", another: 12.548777}, null),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.hasChanged(
|
|
||||||
{test: "test", another: 12.548777},
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(() =>
|
|
||||||
testProperty.type.hasChanged(5 as any, 5 as any),
|
|
||||||
).toThrowError(InvalidTypeValueError);
|
|
||||||
expect(() =>
|
|
||||||
testProperty.type.hasChanged({} as any, [] as any),
|
|
||||||
).toThrowError(InvalidTypeValueError);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("serializedHasChanged", () => {
|
|
||||||
test("serializedHasChanged", () => {
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(
|
|
||||||
{test: "test", another: "12.548777"},
|
|
||||||
{another: "12.548777", test: "test"},
|
|
||||||
),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(
|
|
||||||
{test: "test", another: "12.548777"},
|
|
||||||
{test: "test", another: "12.548778"},
|
|
||||||
),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(testProperty.type.serializedHasChanged(null, null)).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(undefined, undefined),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(null, undefined),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(undefined, null),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(null, {
|
|
||||||
test: "test",
|
|
||||||
another: "12.548777",
|
|
||||||
}),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(undefined, {
|
|
||||||
test: "test",
|
|
||||||
another: "12.548777",
|
|
||||||
}),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(
|
|
||||||
{test: "test", another: "12.548777"},
|
|
||||||
null,
|
|
||||||
),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
testProperty.type.serializedHasChanged(
|
|
||||||
{test: "test", another: "12.548777"},
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(() =>
|
|
||||||
testProperty.type.serializedHasChanged(5 as any, 5 as any),
|
|
||||||
).toThrowError(InvalidTypeValueError);
|
|
||||||
expect(() =>
|
|
||||||
testProperty.type.serializedHasChanged({} as any, [] as any),
|
|
||||||
).toThrowError(InvalidTypeValueError);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("resetDiff", () => {
|
|
||||||
test("resetDiff", () => {
|
|
||||||
testProperty.type.resetDiff({test: "test", another: 12.548777});
|
|
||||||
testProperty.type.resetDiff(undefined);
|
|
||||||
testProperty.type.resetDiff(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(() => testProperty.type.resetDiff(5 as any)).toThrowError(
|
|
||||||
InvalidTypeValueError,
|
|
||||||
);
|
|
||||||
expect(() => testProperty.type.resetDiff([] as any)).toThrowError(
|
|
||||||
InvalidTypeValueError,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("clone", () => {
|
|
||||||
test("clone", () => {
|
|
||||||
{
|
|
||||||
// Test that values are cloned in a different object.
|
|
||||||
const propertyValue = {test: "test", another: 12.548777};
|
|
||||||
const clonedPropertyValue = testProperty.type.clone(propertyValue);
|
|
||||||
expect(clonedPropertyValue).not.toBe(propertyValue);
|
|
||||||
expect(clonedPropertyValue).toEqual(propertyValue);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
// Test that values are cloned in a different object.
|
|
||||||
const propertyValue = {arr: [12, 11]};
|
|
||||||
const clonedPropertyValue = s.property
|
|
||||||
.object({arr: s.property.array(s.property.numeric())})
|
|
||||||
.type.clone(propertyValue);
|
|
||||||
expect(clonedPropertyValue).not.toBe(propertyValue);
|
|
||||||
expect(clonedPropertyValue).toEqual(propertyValue);
|
|
||||||
expect(clonedPropertyValue.arr).not.toBe(propertyValue.arr);
|
|
||||||
expect(clonedPropertyValue.arr).toEqual(propertyValue.arr);
|
|
||||||
}
|
|
||||||
expect(testProperty.type.clone(undefined)).toBe(undefined);
|
|
||||||
expect(testProperty.type.clone(null)).toBe(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(() => testProperty.type.clone(5 as any)).toThrowError(
|
|
||||||
InvalidTypeValueError,
|
|
||||||
);
|
|
||||||
expect(() => testProperty.type.clone([] as any)).toThrowError(
|
|
||||||
InvalidTypeValueError,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("applyPatch", () => {
|
|
||||||
{
|
|
||||||
// Apply a patch with undefined / NULL values.
|
|
||||||
expect(
|
|
||||||
testProperty.type.applyPatch(
|
|
||||||
{test: "test", another: 12.548777},
|
|
||||||
undefined,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
).toBeUndefined();
|
|
||||||
expect(
|
|
||||||
testProperty.type.applyPatch(
|
|
||||||
{test: "test", another: 12.548777},
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
).toBeNull();
|
|
||||||
}
|
}
|
||||||
|
{ // Test that values are cloned in a different object.
|
||||||
{
|
const propertyValue = { arr: [12, 11] };
|
||||||
// Invalid patch.
|
const clonedPropertyValue = s.property.object({ arr: s.property.array(s.property.numeric()) }).type.clone(propertyValue);
|
||||||
expect(() =>
|
expect(clonedPropertyValue).not.toBe(propertyValue);
|
||||||
testProperty.type.applyPatch(
|
expect(clonedPropertyValue).toEqual(propertyValue);
|
||||||
{test: "test", another: 12.548777},
|
expect(clonedPropertyValue.arr).not.toBe(propertyValue.arr);
|
||||||
5416 as any,
|
expect(clonedPropertyValue.arr).toEqual(propertyValue.arr);
|
||||||
false,
|
|
||||||
),
|
|
||||||
).toThrow(InvalidTypeValueError);
|
|
||||||
}
|
}
|
||||||
|
expect(testProperty.type.clone(undefined)).toBe(undefined);
|
||||||
|
expect(testProperty.type.clone(null)).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
{
|
test("invalid parameters types", () => {
|
||||||
// Apply a patch.
|
expect(() => testProperty.type.serialize(5 as any)).toThrowError(InvalidTypeValueError);
|
||||||
{
|
expect(() => testProperty.type.deserialize(5 as any)).toThrowError(InvalidTypeValueError);
|
||||||
const objectInstance = testProperty.type.applyPatch(
|
expect(() => testProperty.type.serializeDiff(5 as any)).toThrowError(InvalidTypeValueError);
|
||||||
{test: "test", another: 12.548777},
|
expect(() => testProperty.type.resetDiff(5 as any)).toThrowError(InvalidTypeValueError);
|
||||||
{test: "another"},
|
expect(() => testProperty.type.hasChanged(5 as any, 5 as any)).toThrowError(InvalidTypeValueError);
|
||||||
true,
|
expect(() => testProperty.type.serializedHasChanged(5 as any, 5 as any)).toThrowError(InvalidTypeValueError);
|
||||||
);
|
expect(() => testProperty.type.clone(5 as any)).toThrowError(InvalidTypeValueError);
|
||||||
|
|
||||||
expect(objectInstance).toStrictEqual({
|
expect(() => testProperty.type.serialize([] as any)).toThrowError(InvalidTypeValueError);
|
||||||
test: "another",
|
expect(() => testProperty.type.deserialize([] as any)).toThrowError(InvalidTypeValueError);
|
||||||
another: 12.548777,
|
expect(() => testProperty.type.serializeDiff([] as any)).toThrowError(InvalidTypeValueError);
|
||||||
});
|
expect(() => testProperty.type.resetDiff([] as any)).toThrowError(InvalidTypeValueError);
|
||||||
}
|
expect(() => testProperty.type.hasChanged({} as any, [] as any)).toThrowError(InvalidTypeValueError);
|
||||||
|
expect(() => testProperty.type.serializedHasChanged({} as any, [] as any)).toThrowError(InvalidTypeValueError);
|
||||||
{
|
expect(() => testProperty.type.clone([] as any)).toThrowError(InvalidTypeValueError);
|
||||||
const objectInstance = testProperty.type.applyPatch(
|
|
||||||
undefined,
|
|
||||||
{test: "test"},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(objectInstance).toStrictEqual({
|
|
||||||
test: "test",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const objectInstance = testProperty.type.applyPatch(
|
|
||||||
null,
|
|
||||||
{test: "test"},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(objectInstance).toStrictEqual({
|
|
||||||
test: "test",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,161 +2,62 @@ import {describe, expect, test} from "vitest";
|
||||||
import {s, StringType} from "../../../src/library";
|
import {s, StringType} from "../../../src/library";
|
||||||
|
|
||||||
describe("string type", () => {
|
describe("string type", () => {
|
||||||
test("definition", () => {
|
test("string type definition", () => {
|
||||||
const stringType = s.property.string();
|
const stringType = s.property.string();
|
||||||
expect(stringType.type).toBeInstanceOf(StringType);
|
expect(stringType.type).toBeInstanceOf(StringType);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("serialize", () => {
|
test("string type functions", () => {
|
||||||
test("serialize", () => {
|
expect(s.property.string().type.serialize("test")).toBe("test");
|
||||||
expect(s.property.string().type.serialize("test")).toBe("test");
|
expect(s.property.string().type.deserialize("test")).toBe("test");
|
||||||
expect(s.property.string().type.serialize(null)).toBe(null);
|
expect(s.property.string().type.serializeDiff("test")).toBe("test");
|
||||||
expect(s.property.string().type.serialize(undefined)).toBe(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
expect(s.property.string().type.serialize(null)).toBe(null);
|
||||||
const testDate = new Date();
|
expect(s.property.string().type.deserialize(null)).toBe(null);
|
||||||
expect(s.property.string().type.serialize({} as any)).toBe(
|
expect(s.property.string().type.serializeDiff(null)).toBe(null);
|
||||||
"[object Object]",
|
|
||||||
);
|
expect(s.property.string().type.serialize(undefined)).toBe(undefined);
|
||||||
expect(s.property.string().type.serialize(2120 as any)).toBe("2120");
|
expect(s.property.string().type.deserialize(undefined)).toBe(undefined);
|
||||||
expect(s.property.string().type.serialize(testDate as any)).toBe(
|
expect(s.property.string().type.serializeDiff(undefined)).toBe(undefined);
|
||||||
testDate.toString(),
|
|
||||||
);
|
expect(s.property.string().type.hasChanged("test", "test")).toBeFalsy();
|
||||||
});
|
expect(s.property.string().type.hasChanged(null, null)).toBeFalsy();
|
||||||
|
expect(s.property.string().type.hasChanged(undefined, undefined)).toBeFalsy();
|
||||||
|
expect(s.property.string().type.hasChanged(null, undefined)).toBeTruthy();
|
||||||
|
expect(s.property.string().type.hasChanged(undefined, null)).toBeTruthy();
|
||||||
|
expect(s.property.string().type.hasChanged(null, "test")).toBeTruthy();
|
||||||
|
expect(s.property.string().type.hasChanged(undefined, "test")).toBeTruthy();
|
||||||
|
expect(s.property.string().type.hasChanged("test", null)).toBeTruthy();
|
||||||
|
expect(s.property.string().type.hasChanged("test", undefined)).toBeTruthy();
|
||||||
|
|
||||||
|
expect(s.property.string().type.serializedHasChanged("test", "test")).toBeFalsy();
|
||||||
|
expect(s.property.string().type.serializedHasChanged(null, null)).toBeFalsy();
|
||||||
|
expect(s.property.string().type.serializedHasChanged(undefined, undefined)).toBeFalsy();
|
||||||
|
expect(s.property.string().type.serializedHasChanged(null, undefined)).toBeTruthy();
|
||||||
|
expect(s.property.string().type.serializedHasChanged(undefined, null)).toBeTruthy();
|
||||||
|
expect(s.property.string().type.serializedHasChanged(null, "test")).toBeTruthy();
|
||||||
|
expect(s.property.string().type.serializedHasChanged(undefined, "test")).toBeTruthy();
|
||||||
|
expect(s.property.string().type.serializedHasChanged("test", null)).toBeTruthy();
|
||||||
|
expect(s.property.string().type.serializedHasChanged("test", undefined)).toBeTruthy();
|
||||||
|
|
||||||
|
s.property.string().type.resetDiff("test");
|
||||||
|
s.property.string().type.resetDiff(undefined);
|
||||||
|
s.property.string().type.resetDiff(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("deserialize", () => {
|
test("invalid parameters types", () => {
|
||||||
test("deserialize", () => {
|
const testDate = new Date();
|
||||||
expect(s.property.string().type.deserialize("test")).toBe("test");
|
expect(s.property.string().type.serialize({} as any)).toBe("[object Object]");
|
||||||
expect(s.property.string().type.deserialize(null)).toBe(null);
|
expect(s.property.string().type.serialize(2120 as any)).toBe("2120");
|
||||||
expect(s.property.string().type.deserialize(undefined)).toBe(undefined);
|
expect(s.property.string().type.serialize(testDate as any)).toBe(testDate.toString());
|
||||||
});
|
expect(s.property.string().type.deserialize({} as any)).toBe("[object Object]");
|
||||||
|
expect(s.property.string().type.deserialize(2120 as any)).toBe("2120");
|
||||||
test("invalid parameters", () => {
|
expect(s.property.string().type.serializeDiff({} as any)).toBe("[object Object]");
|
||||||
expect(s.property.string().type.deserialize({} as any)).toBe(
|
expect(s.property.string().type.serializeDiff(2120 as any)).toBe("2120");
|
||||||
"[object Object]",
|
expect(s.property.string().type.hasChanged({} as any, {} as any)).toBeTruthy();
|
||||||
);
|
expect(s.property.string().type.hasChanged(false as any, false as any)).toBeFalsy();
|
||||||
expect(s.property.string().type.deserialize(2120 as any)).toBe("2120");
|
expect(s.property.string().type.serializedHasChanged({} as any, {} as any)).toBeTruthy();
|
||||||
});
|
expect(s.property.string().type.serializedHasChanged(false as any, false as any)).toBeFalsy();
|
||||||
});
|
|
||||||
|
|
||||||
describe("serializeDiff", () => {
|
|
||||||
test("serializeDiff", () => {
|
|
||||||
expect(s.property.string().type.serializeDiff("test")).toBe("test");
|
|
||||||
expect(s.property.string().type.serializeDiff(null)).toBe(null);
|
|
||||||
expect(s.property.string().type.serializeDiff(undefined)).toBe(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(s.property.string().type.serializeDiff({} as any)).toBe(
|
|
||||||
"[object Object]",
|
|
||||||
);
|
|
||||||
expect(s.property.string().type.serializeDiff(2120 as any)).toBe("2120");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("hasChanged", () => {
|
|
||||||
test("hasChanged", () => {
|
|
||||||
expect(s.property.string().type.hasChanged("test", "test")).toBeFalsy();
|
|
||||||
expect(s.property.string().type.hasChanged(null, null)).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property.string().type.hasChanged(undefined, undefined),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(s.property.string().type.hasChanged(null, undefined)).toBeTruthy();
|
|
||||||
expect(s.property.string().type.hasChanged(undefined, null)).toBeTruthy();
|
|
||||||
expect(s.property.string().type.hasChanged(null, "test")).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.string().type.hasChanged(undefined, "test"),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(s.property.string().type.hasChanged("test", null)).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.string().type.hasChanged("test", undefined),
|
|
||||||
).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(
|
|
||||||
s.property.string().type.hasChanged({} as any, {} as any),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.string().type.hasChanged(false as any, false as any),
|
|
||||||
).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("serializedHasChanged", () => {
|
|
||||||
test("serializedHasChanged", () => {
|
|
||||||
expect(
|
|
||||||
s.property.string().type.serializedHasChanged("test", "test"),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property.string().type.serializedHasChanged(null, null),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property.string().type.serializedHasChanged(undefined, undefined),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
s.property.string().type.serializedHasChanged(null, undefined),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.string().type.serializedHasChanged(undefined, null),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.string().type.serializedHasChanged(null, "test"),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.string().type.serializedHasChanged(undefined, "test"),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.string().type.serializedHasChanged("test", null),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property.string().type.serializedHasChanged("test", undefined),
|
|
||||||
).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(
|
|
||||||
s.property.string().type.serializedHasChanged({} as any, {} as any),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
s.property
|
|
||||||
.string()
|
|
||||||
.type.serializedHasChanged(false as any, false as any),
|
|
||||||
).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("resetDiff", () => {
|
|
||||||
test("resetDiff", () => {
|
|
||||||
s.property.string().type.resetDiff("test");
|
|
||||||
s.property.string().type.resetDiff(undefined);
|
|
||||||
s.property.string().type.resetDiff(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid parameters", () => {
|
|
||||||
expect(() => s.property.string().type.resetDiff({} as any)).not.toThrow();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("clone", () => {
|
|
||||||
expect(s.property.string().type.clone({} as any)).toStrictEqual({});
|
expect(s.property.string().type.clone({} as any)).toStrictEqual({});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("applyPatch", () => {
|
|
||||||
expect(s.property.string().type.applyPatch("another", "test", false)).toBe(
|
|
||||||
"test",
|
|
||||||
);
|
|
||||||
expect(s.property.string().type.applyPatch(undefined, "test", true)).toBe(
|
|
||||||
"test",
|
|
||||||
);
|
|
||||||
expect(s.property.string().type.applyPatch(null, "test", false)).toBe(
|
|
||||||
"test",
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
s.property.string().type.applyPatch("test", undefined, false),
|
|
||||||
).toBeUndefined();
|
|
||||||
expect(s.property.string().type.applyPatch("test", null, false)).toBeNull();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,27 +1,30 @@
|
||||||
{
|
{
|
||||||
"ts-node": {
|
"ts-node": {
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"types": ["node"]
|
"types": ["node"],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "./lib/",
|
"outDir": "./lib/",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"noImplicitThis": true,
|
"noImplicitThis": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"declarationMap": true,
|
"declarationMap": true,
|
||||||
"module": "ES6",
|
"module": "ES6",
|
||||||
"target": "ES6",
|
"target": "ES6",
|
||||||
"moduleResolution": "Bundler",
|
"moduleResolution": "Bundler",
|
||||||
"lib": ["ESNext", "DOM"]
|
"lib": [
|
||||||
}
|
"ESNext",
|
||||||
|
"DOM"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import {defineConfig, UserConfig} from "vite";
|
import {ConfigEnv, defineConfig, UserConfig} from "vite";
|
||||||
import dts from "vite-plugin-dts";
|
import dts from "vite-plugin-dts";
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
|
|
||||||
export default defineConfig((): UserConfig => {
|
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
|
||||||
return {
|
return ({
|
||||||
build: {
|
build: {
|
||||||
outDir: "lib",
|
outDir: "lib",
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
|
@ -22,6 +22,6 @@ export default defineConfig((): UserConfig => {
|
||||||
rollupTypes: true,
|
rollupTypes: true,
|
||||||
exclude: ["node_modules"],
|
exclude: ["node_modules"],
|
||||||
}),
|
}),
|
||||||
],
|
]
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Reference in a new issue