@civ-clone/core-data-object
Version:
A data exchange object for converting a subset of a class instances to plain JSON.
347 lines (265 loc) • 7.91 kB
text/typescript
import AdditionalData from '../AdditionalData';
import AdditionalDataRegistry from '../AdditionalDataRegistry';
import DataObject from '../DataObject';
import EntityRegistry from '@civ-clone/core-registry/EntityRegistry';
import { expect } from 'chai';
import reconstituteData from '../lib/reconstituteData';
import { IConstructor } from '@civ-clone/core-registry/Registry';
class A extends DataObject {
a: string = 'a';
b: string = 'b';
c: string = 'c';
constructor() {
super();
this.addKey('a', 'c');
}
}
class B extends DataObject {
a: A;
b: A[];
constructor(a: A, ...b: A[]) {
super();
this.addKey('a', 'b');
this.a = a;
this.b = b;
}
}
class C extends DataObject {
a: ARegistry;
constructor(a: ARegistry) {
super();
this.addKey('a');
this.a = a;
}
}
class D extends DataObject {
#a: A;
constructor(a: A) {
super();
this.addKey('a');
this.#a = a;
}
a() {
return this.#a;
}
}
class E extends DataObject {
#a: typeof A = A;
constructor() {
super();
this.addKey('a');
}
a() {
return this.#a;
}
}
class F extends DataObject {
a: Object = {
a: true,
b: 2,
c: 'C',
d: D,
e: {
a: new A(),
},
};
constructor() {
super();
this.addKey('a');
}
}
class G extends DataObject {
#a: A[];
#b: B;
constructor(b: B, ...a: A[]) {
super();
this.#a = a;
this.#b = b;
this.addKey('a', 'b');
}
a(): A[] {
return this.#a;
}
b(): B {
return this.#b;
}
}
class ARegistry extends EntityRegistry<A> {
constructor() {
super(A);
}
}
describe('DataObject', (): void => {
it('should return expected keys', (): void => {
const a = new A(),
plainA = reconstituteData(a.toPlainObject());
expect(plainA).to.a('object').keys('_', '__', 'id', 'a', 'c');
expect(plainA._).to.equal('A');
expect(plainA.a).to.equal('a');
expect(plainA.c).to.equal('c');
});
it('should return nested DataObjects', (): void => {
const b = new B(new A(), new A(), new A()),
plainB = reconstituteData(b.toPlainObject());
expect(plainB).to.a('object').keys('_', '__', 'id', 'a', 'b');
expect(plainB.a).to.a('object').keys('_', '__', 'id', 'a', 'c');
expect(plainB.b).to.a('array').lengthOf(2);
expect(plainB.b[0]).to.a('object').keys('_', '__', 'id', 'a', 'c');
expect(plainB.b[1]).to.a('object').keys('_', '__', 'id', 'a', 'c');
});
it('should return entries from Registries', (): void => {
const aRegistry = new ARegistry(),
c = new C(aRegistry),
plainC = reconstituteData(c.toPlainObject());
expect(plainC).to.a('object').keys('_', '__', 'id', 'a');
expect(plainC.a).to.a('array').lengthOf(0);
aRegistry.register(new A(), new A(), new A());
expect(aRegistry.entries().length).to.equal(3);
expect(aRegistry.length).to.equal(3);
const updatedPlainC = reconstituteData(c.toPlainObject());
expect(updatedPlainC.a).to.a('array').lengthOf(3);
expect(updatedPlainC.a[0]).to.a('object').keys('_', '__', 'id', 'a', 'c');
expect(updatedPlainC.a[1]).to.a('object').keys('_', '__', 'id', 'a', 'c');
expect(updatedPlainC.a[2]).to.a('object').keys('_', '__', 'id', 'a', 'c');
});
it('should return values from getters', (): void => {
const d = new D(new A()),
plainD = reconstituteData(d.toPlainObject());
expect(plainD).to.a('object').keys('_', '__', 'id', 'a');
expect(plainD.a).to.a('object').keys('_', '__', 'id', 'a', 'c');
});
it('should return function name only from raw functions', (): void => {
const e = new E(),
plainE = reconstituteData(e.toPlainObject());
expect(plainE).to.a('object').keys('_', '__', 'id', 'a');
expect(plainE.a).to.a('object').keys('_', '__');
expect(plainE.a._).to.equal('A');
});
it('should return correctly handle nested objects', (): void => {
const f = new F(),
plainF = reconstituteData(f.toPlainObject());
expect(plainF).to.a('object').keys('_', '__', 'id', 'a');
expect(plainF.a).to.a('object').keys('a', 'b', 'c', 'd', 'e');
expect(plainF.a.a).to.true;
expect(plainF.a.b).to.equal(2);
expect(plainF.a.c).to.equal('C');
expect(plainF.a.d).to.a('object').keys('_', '__');
expect(plainF.a.d._).to.equal('D');
expect(plainF.a.e).to.a('object').keys('a');
expect(plainF.a.e.a).to.a('object').keys('_', '__', 'id', 'a', 'c');
});
it('should correctly apply the filter', (): void => {
const g = new G(new B(new A()), new A(), new A()),
plain = reconstituteData(g.toPlainObject());
expect(plain.b.a._).equal('A');
const plainFiltered = reconstituteData(
g.toPlainObject((object) => {
if (object instanceof A) {
return new E();
}
return object;
})
);
expect(plainFiltered.b.a._).equal('E');
expect(plainFiltered.a[0]._).equal('E');
});
class World extends DataObject {
#tiles = new EntityRegistry(Tile);
constructor() {
super();
this.addKey('tiles');
}
tiles(): EntityRegistry {
return this.#tiles;
}
}
class Tile extends DataObject {}
class Unit extends DataObject {
#tile: Tile;
constructor(tile: Tile) {
super();
this.#tile = tile;
this.addKey('tile');
}
tile(): Tile {
return this.#tile;
}
}
class City extends DataObject {
#tile: Tile;
constructor(tile: Tile) {
super();
this.#tile = tile;
this.addKey('tile');
}
tile(): Tile {
return this.#tile;
}
}
it('should provide a hierarchy and map of objects that allows nesting and recursion', (): void => {
const additionalDataRegistry = new AdditionalDataRegistry(),
cityRegistry = new EntityRegistry(City),
unitRegistry = new EntityRegistry(Unit),
world = new World();
additionalDataRegistry.register(
new AdditionalData(Tile, 'units', (tile: Tile) =>
unitRegistry.getBy('tile', tile)
)
);
additionalDataRegistry.register(
new AdditionalData(
Tile,
'city',
(tile: Tile) => cityRegistry.getBy('tile', tile)[0]
)
);
const tile1 = new Tile(),
tile2 = new Tile(),
tile3 = new Tile(),
tile4 = new Tile(),
tile5 = new Tile();
world.tiles().register(tile1, tile2, tile3, tile4, tile5);
const unit1 = new Unit(tile1),
unit2 = new Unit(tile4),
unit3 = new Unit(tile4),
unit4 = new Unit(tile4);
unitRegistry.register(unit1, unit2, unit3, unit4);
const city1 = new City(tile2),
city2 = new City(tile4);
cityRegistry.register(city1, city2);
const plainWorld = world.toPlainObject(
(object) => object,
additionalDataRegistry
);
expect(Object.keys(plainWorld.objects).length).equal(12);
const reconstitutedWorld = reconstituteData(plainWorld);
expect(reconstitutedWorld).an('object').keys('_', '__', 'id', 'tiles');
expect(reconstitutedWorld.tiles).an('array').length(5);
expect(reconstitutedWorld.tiles[3])
.an('object')
.keys('_', '__', 'id', 'units', 'city');
expect(reconstitutedWorld.tiles[3]).equal(
reconstitutedWorld.tiles[3].units[0].tile
);
expect(reconstitutedWorld.tiles[3].units[0]).equal(
reconstitutedWorld.tiles[3].units[0].tile.units[0].tile.units[0]
);
});
it('should provide expected classes for `sourceClass()`', (): void => {
class A extends DataObject {}
class B extends A {}
class C extends B {}
const a = new A(),
b = new B(),
c = new C();
expect(a.sourceClass()).equal(A);
expect(b.sourceClass()).equal(B);
expect(c.sourceClass()).equal(C);
expect(reconstituteData(c.toPlainObject()).__).eql([
'C',
'B',
'A',
'DataObject',
]);
});
});