domain-objects
Version:
A simple, convenient way to represent domain objects, leverage domain knowledge, and add runtime validation in your code base.
246 lines • 11.1 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const joi_1 = __importDefault(require("joi"));
const uuid_1 = require("uuid");
const yup = __importStar(require("yup"));
const zod_1 = require("zod");
const DomainObject_1 = require("./DomainObject");
const HelpfulJoiValidationError_1 = require("./validate/HelpfulJoiValidationError");
const HelpfulYupValidationError_1 = require("./validate/HelpfulYupValidationError");
const HelpfulZodValidationError_1 = require("./validate/HelpfulZodValidationError");
describe('DomainObject', () => {
describe('domain modeling use cases', () => {
it('should be able to represent a literal', () => {
class ChatMessage extends DomainObject_1.DomainObject {
}
const message = new ChatMessage({
userUuid: (0, uuid_1.v4)(),
conversationUuid: (0, uuid_1.v4)(),
message: 'Hello, World!',
});
expect(message).toBeInstanceOf(ChatMessage); // sanity check
});
it('should be able to represent an entity', () => {
class RocketShip extends DomainObject_1.DomainObject {
}
const ship = new RocketShip({
serialNumber: (0, uuid_1.v4)(),
fuelQuantity: 9001,
passengers: 21,
});
expect(ship).toBeInstanceOf(RocketShip); // sanity check
});
});
describe('instantiation', () => {
class ChatMessage extends DomainObject_1.DomainObject {
}
it('should assign all properties in the constructor to the instance', () => {
const message = new ChatMessage({
userUuid: '__USER_UUID__',
conversationUuid: '__CONVO_UUID__',
message: 'Hello, World!',
});
expect(message.userUuid).toEqual('__USER_UUID__');
expect(message.conversationUuid).toEqual('__CONVO_UUID__');
expect(message.message).toEqual('Hello, World!');
});
it('should be able to spread into itself', () => {
const message = new ChatMessage({
userUuid: '__USER_UUID__',
conversationUuid: '__CONVO_UUID__',
message: 'Hello, World!',
});
const updatedMessage = new ChatMessage(Object.assign(Object.assign({}, message), { message: `Hello, World!\n Edit: You're great!` }));
expect(updatedMessage.userUuid).toEqual('__USER_UUID__');
expect(updatedMessage.conversationUuid).toEqual('__CONVO_UUID__');
expect(updatedMessage.message).toEqual(`Hello, World!\n Edit: You're great!`);
});
});
describe('validation', () => {
describe('Joi schema', () => {
const schema = joi_1.default.object().keys({
serialNumber: joi_1.default.string().uuid().required(),
fuelQuantity: joi_1.default.number().required(),
passengers: joi_1.default.number().max(42).required(),
});
class RocketShip extends DomainObject_1.DomainObject {
}
RocketShip.schema = schema;
it('should not throw error if when valid', () => {
const ship = new RocketShip({
serialNumber: (0, uuid_1.v4)(),
fuelQuantity: 9001,
passengers: 21,
});
expect(ship).toBeInstanceOf(RocketShip); // sanity check
});
it('should throw a helpful error when does not pass joi schema', () => {
try {
// eslint-disable-next-line no-new
new RocketShip({
serialNumber: '__SOME_UUID__',
fuelQuantity: 9001,
passengers: 50,
});
throw new Error('should not reach here');
}
catch (error) {
if (!(error instanceof Error))
throw error;
expect(error).toBeInstanceOf(HelpfulJoiValidationError_1.HelpfulJoiValidationError);
expect(error.message).toMatchSnapshot();
}
});
});
describe('Yup schema', () => {
const schema = yup.object({
serialNumber: yup.string().required(),
fuelQuantity: yup.number().required(),
passengers: yup.number().max(42).required(),
});
class RocketShip extends DomainObject_1.DomainObject {
}
RocketShip.schema = schema;
it('should not throw error if when valid', () => {
const ship = new RocketShip({
serialNumber: (0, uuid_1.v4)(),
fuelQuantity: 9001,
passengers: 21,
});
expect(ship).toBeInstanceOf(RocketShip); // sanity check
});
it('should throw a helpful error when does not pass schema', () => {
try {
// eslint-disable-next-line no-new
new RocketShip({
serialNumber: '__SOME_UUID__',
fuelQuantity: 9001,
passengers: 50,
});
throw new Error('should not reach here');
}
catch (error) {
if (!(error instanceof Error))
throw error;
expect(error).toBeInstanceOf(HelpfulYupValidationError_1.HelpfulYupValidationError);
expect(error.message).toMatchSnapshot();
}
});
});
describe('Zod schema', () => {
const schema = zod_1.z.object({
serialNumber: zod_1.z.string(),
fuelQuantity: zod_1.z.number(),
passengers: zod_1.z.number().max(42),
});
class RocketShip extends DomainObject_1.DomainObject {
}
RocketShip.schema = schema;
it('should not throw error if when valid', () => {
const ship = new RocketShip({
serialNumber: (0, uuid_1.v4)(),
fuelQuantity: 9001,
passengers: 21,
});
expect(ship).toBeInstanceOf(RocketShip); // sanity check
});
it('should throw a helpful error when does not pass schema', () => {
try {
// eslint-disable-next-line no-new
new RocketShip({
serialNumber: '__SOME_UUID__',
fuelQuantity: 9001,
passengers: 50,
});
throw new Error('should not reach here');
}
catch (error) {
if (!(error instanceof Error))
throw error;
expect(error).toBeInstanceOf(HelpfulZodValidationError_1.HelpfulZodValidationError);
expect(error.message).toMatchSnapshot();
}
});
});
describe('hydration', () => {
it('should hydrate nested domain objects', () => {
class PlantPot extends DomainObject_1.DomainObject {
}
class Plant extends DomainObject_1.DomainObject {
}
Plant.nested = { pot: PlantPot };
// now show that we hydrate the pot
const plant = new Plant({
pot: { diameterInInches: 7 },
lastWatered: 'monday',
});
expect(plant.pot).toBeInstanceOf(PlantPot);
});
it('should hydrate nested array of domain objects', () => {
class PlantOwner extends DomainObject_1.DomainObject {
}
class Plant extends DomainObject_1.DomainObject {
}
Plant.nested = { owners: PlantOwner };
// now show that we hydrate the pot
const plant = new Plant({
owners: [{ name: 'bob' }],
lastWatered: 'monday',
});
plant.owners.forEach((owner) => expect(owner).toBeInstanceOf(PlantOwner));
});
it('should not hydrate nullable nested domain objects when null', () => {
class PlantOwner extends DomainObject_1.DomainObject {
}
class Plant extends DomainObject_1.DomainObject {
}
Plant.nested = { owners: PlantOwner };
// now show that we hydrate the pot
const plant = new Plant({ owners: null, lastWatered: 'monday' });
expect(plant.owners).toEqual(null); // should still be null - since should not have instantiated
});
it('should hydrate nested domain objects correctly when given choice of different options', () => {
class PlantPot extends DomainObject_1.DomainObject {
}
class PlantBed extends DomainObject_1.DomainObject {
}
class Plant extends DomainObject_1.DomainObject {
}
Plant.nested = { plantedIn: [PlantPot, PlantBed] };
// now show that we hydrate the pot correctly
const plant = new Plant({
plantedIn: { _dobj: 'PlantPot', diameterInInches: 7 },
lastWatered: 'monday',
});
expect(plant.plantedIn).toBeInstanceOf(PlantPot);
});
});
});
});
//# sourceMappingURL=DomainObject.test.js.map