import { it, describe, expect } from 'vitest';
const itEffect = (name: string, fn: () => Effect.Effect) =>
it(name, () => Effect.runPromise(fn()));
import { Effect, Schema } from 'effect';
import { EntityESchema } from '../index.js';
describe('ESchema', () => {
describe('Entity', () => {
describe('Make', () => {
itEffect('creates a v1 schema with name and id field', () =>
Effect.gen(function* () {
const schema = EntityESchema.make('User', 'id', {
name: Schema.String,
}).build();
expect(schema.idField).toBe('id');
const encoded = yield* schema.encode({ id: 'u1', name: 'Alice' });
expect(encoded).toEqual({ _v: 'v1', id: 'u1', name: 'Alice' });
}),
);
itEffect('supports complex field types', () =>
Effect.gen(function* () {
const schema = EntityESchema.make('Complex', 'id', {
count: Schema.Number,
tag: Schema.Literals(['a', 'b']),
nullable: Schema.NullOr(Schema.String),
}).build();
const decoded = yield* schema.decode({
_v: 'v1',
id: 'c1',
count: 42,
tag: 'a',
nullable: null,
});
expect(decoded).toEqual({
id: 'c1',
count: 42,
tag: 'a',
nullable: null,
});
}),
);
it('supports custom id field names', () => {
const schema = EntityESchema.make('User', 'userId', {
name: Schema.String,
}).build();
expect(schema.idField).toBe('userId');
});
});
describe('Fields', () => {
it('returns the latest schema fields including id', () => {
const schema = EntityESchema.make('Test', 'id', {
a: Schema.String,
}).build();
expect(Object.keys(schema.fields)).toEqual(['a', 'id']);
});
it('returns evolved schema fields after evolution', () => {
const schema = EntityESchema.make('Test', 'id', {
a: Schema.String,
})
.evolve('v2', { b: Schema.Number }, (v) => ({ ...v, b: 0 }))
.build();
expect(Object.keys(schema.fields).sort()).toEqual(['a', 'b', 'id']);
});
});
describe('Schema', () => {
it('returns an Effect Schema.Struct with ID field', () => {
const eschema = EntityESchema.make('Test', 'id', {
a: Schema.String,
}).build();
const effectSchema = eschema.schema;
expect(effectSchema.fields).toBeDefined();
expect(Object.keys(effectSchema.fields)).toEqual(['a', 'id']);
});
});
describe('Make partial', () => {
it('returns partial value with version', () => {
const schema = EntityESchema.make('Test', 'id', {
a: Schema.String,
b: Schema.Number,
}).build();
const partial = schema.makePartial({ a: 'hello' });
expect(partial).toEqual({ a: 'hello', _v: 'v1' });
});
it('allows empty partial', () => {
const schema = EntityESchema.make('Test', 'id', {
a: Schema.String,
}).build();
const partial = schema.makePartial({});
expect(partial).toEqual({ _v: 'v1' });
});
});
describe('ForbidIdField enforcement', () => {
it('id field is auto-added and cannot be in user schema', () => {
const schema = EntityESchema.make('Test', 'testId', {
name: Schema.String,
}).build();
expect(schema.idField).toBe('testId');
expect(Object.keys(schema.fields)).toContain('testId');
});
});
describe('ID handling', () => {
itEffect('decoded id is a plain string', () =>
Effect.gen(function* () {
const userSchema = EntityESchema.make('User', 'id', {
name: Schema.String,
}).build();
const decoded = yield* userSchema.decode({ id: 'u1', name: 'Alice' });
expect(decoded.id).toBe('u1');
}),
);
itEffect('encoded id is a plain string', () =>
Effect.gen(function* () {
const userSchema = EntityESchema.make('User', 'id', {
name: Schema.String,
}).build();
const encoded = yield* userSchema.encode({ id: 'u1', name: 'Alice' });
expect(encoded.id).toBe('u1');
const reEncoded = yield* userSchema.encode({
id: encoded.id,
name: 'Bob',
});
expect(reEncoded.id).toBe('u1');
}),
);
});
});
});