Core/tests/model.test.ts

329 lines
9.6 KiB
TypeScript

import {describe, expect, it} from "vitest";
import {circular, defineModel, s} from "../src/library";
/**
* Test class of an 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;
createdAt: Date;
name: string;
email: string;
active: boolean;
}
/**
* Test class of an 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;
title: string;
authors: Account[];
text: string;
evaluation: number;
tags: { name: string }[];
comments: ArticleComment[];
}
/**
* Test class of a comment on an article.
*/
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;
article?: Article;
author: Account;
message: string;
}
/**
* Get a test account instance.
*/
function getTestAccount(): Account
{
const account = new Account();
account.id = 52;
account.createdAt = new Date();
account.name = "John Doe";
account.email = "john@doe.test";
account.active = true;
return account;
}
function getTestArticle(): Article
{
const article = new Article();
article.id = 1;
article.title = "this is a test";
article.text = "this is a long test.";
article.evaluation = 25.23;
article.tags = [
{ name: "test" },
{ name: "foo" },
];
article.authors = [getTestAccount()];
article.comments = [];
return article;
}
describe("model", () => {
it("initializes a new model", () => {
const article = getTestArticle();
const newModel = Article.model.model(article);
expect(newModel.instance).toBe(article);
});
it("gets a model state from its instance", () => {
const article = getTestArticle();
expect(Article.model.model(article).isNew()).toBeTruthy();
expect(Article.model.model(article).isDirty()).toBeFalsy();
});
it("gets a model identifier value", () => {
const article = getTestArticle();
expect(Article.model.model(article).getIdentifier()).toBe(1);
});
it("gets a model composite identifier value", () => {
class CompositeModel
{
static model = s.defineModel({
Class: CompositeModel,
properties: {
firstId: s.property.numeric(),
secondId: s.property.numeric(),
label: s.property.string(),
},
identifier: ["firstId", "secondId"],
})
firstId: number;
secondId: number;
label: string;
}
expect(
CompositeModel.model.model(Object.assign(new CompositeModel(), {
firstId: 5,
secondId: 6,
label: "test",
})).getIdentifier()
).toStrictEqual([5, 6]);
});
it("checks model dirtiness when altered, then reset diff", () => {
const article = getTestArticle();
expect(Article.model.model(article).isDirty()).toBeFalsy();
article.title = "new title";
expect(Article.model.model(article).isDirty()).toBeTruthy();
Article.model.model(article).resetDiff()
expect(Article.model.model(article).isDirty()).toBeFalsy();
});
it("deserializes a model from a serialized form", () => {
const expectedArticle = Object.assign(new Article(), {
id: 1,
title: "this is a test",
authors: [
Object.assign(new Account(), { id: 52, 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.",
evaluation: 8.52,
tags: [ {name: "test"}, {name: "foo"} ],
comments: [
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", }),
],
});
const deserializedArticle = Article.model.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", },
],
});
const deserializedArticleProperties = Article.model.model(deserializedArticle).getInstanceProperties();
delete deserializedArticleProperties.authors[0]._sharkitek;
delete deserializedArticleProperties.authors[1]._sharkitek;
delete deserializedArticleProperties.comments[0]._sharkitek;
delete (deserializedArticleProperties.comments[0].author as any)._sharkitek;
const expectedArticleProperties = Article.model.model(expectedArticle).getInstanceProperties();
delete expectedArticleProperties.authors[0]._sharkitek;
delete expectedArticleProperties.authors[1]._sharkitek;
delete expectedArticleProperties.comments[0]._sharkitek;
delete (expectedArticleProperties.comments[0].author as any)._sharkitek;
expect(deserializedArticleProperties).toEqual(expectedArticleProperties);
});
it("serializes an initialized model", () => {
const article = getTestArticle();
expect(Article.model.model(article).serialize()).toEqual({
id: 1,
title: "this is a test",
text: "this is a long test.",
evaluation: "25.23",
tags: [{ name: "test" }, { name: "foo" }],
authors: [
{ id: 52, createdAt: article.authors[0].createdAt.toISOString(), name: "John Doe", email: "john@doe.test", active: true }
],
comments: [],
});
});
it("deserializes, changes and patches", () => {
const deserializedArticle = Article.model.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", },
],
});
deserializedArticle.text = "A new text for a new life!";
expect(Article.model.model(deserializedArticle).patch()).toStrictEqual({
id: 1,
text: "A new text for a new life!",
});
deserializedArticle.evaluation = 5.24;
expect(Article.model.model(deserializedArticle).patch()).toStrictEqual({
id: 1,
evaluation: "5.24",
});
});
it("patches with modified submodels", () => {
const deserializedArticle = Article.model.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", },
],
});
deserializedArticle.authors[1].active = true;
expect(Article.model.model(deserializedArticle).patch()).toStrictEqual({
id: 1,
authors: [
{ id: 52, },
{ id: 4, active: true },
],
});
deserializedArticle.comments[0].author.name = "Johnny";
expect(Article.model.model(deserializedArticle).patch()).toStrictEqual({
id: 1,
comments: [
{
id: 542,
author: {
id: 52,
name: "Johnny",
},
},
],
});
});
it("deserializes and patches with fields that are not properties", () => {
class TestModel
{
static model = defineModel({
Class: TestModel,
properties: {
id: s.property.numeric(),
label: s.property.string(),
},
identifier: "id",
})
id: number;
label: string;
notAProperty: { hello: string } = { hello: "world" };
}
const deserializedModel = TestModel.model.parse({
id: 5,
label: "testing",
});
expect(deserializedModel.id).toBe(5);
expect(deserializedModel.label).toBe("testing");
expect(deserializedModel.notAProperty?.hello).toBe("world");
const clonedDeserializedModel = TestModel.model.model(deserializedModel).clone();
deserializedModel.label = "new!";
expect(TestModel.model.model(deserializedModel).patch()).toStrictEqual({ id: 5, label: "new!" });
deserializedModel.notAProperty.hello = "monster";
expect(TestModel.model.model(deserializedModel).patch()).toStrictEqual({ id: 5 });
expect(TestModel.model.model(deserializedModel).serialize()).toStrictEqual({ id: 5, label: "new!" });
expect(TestModel.model.model(clonedDeserializedModel).serialize()).toStrictEqual({ id: 5, label: "testing" });
expect(clonedDeserializedModel.notAProperty.hello).toEqual("world");
});
});