/** * Shapes (typed query projection) + Forms (schema / validate / submit) tests. * * Shapes prove over-fetch elimination: the projected record carries only the * declared fields + nested relations, nothing else. Forms prove the three * roles register as dispatchable `@client` functions carrying the IR's * `form`/`form-name`/`form-role` meta, and that validate/submit enforce the * declared field rules. */ import { describe, test, expect, beforeEach } from 'bun:test' import { clearRegistry, getFunction, handleMutationCall, Shape, project, registerForm, formSchema, validateForm, type QueryProjection, } from '../src' describe('Shapes — typed query projection', () => { test('keeps only declared scalar fields', () => { const projection: QueryProjection = { fields: ['id', 'name'] } const out = project([{ id: 1, name: 'A', secret: 'x', internal: 42 }], projection) expect(out).toEqual([{ id: 1, name: 'A' }]) expect(out[0]).not.toHaveProperty('secret') expect(out[0]).not.toHaveProperty('internal') }) test('prunes nested relations recursively', () => { const projection: QueryProjection = { fields: ['id'], relations: { orders: { fields: ['total'] } }, } const out = project( [{ id: 1, name: 'drop', orders: [{ id: 9, total: 100, hidden: true }] }], projection, ) expect(out).toEqual([{ id: 1, orders: [{ total: 100 }] }]) expect(out[0].orders[0]).not.toHaveProperty('hidden') }) test('handles single-object relation + null', () => { const projection: QueryProjection = { fields: ['id'], relations: { profile: { fields: ['bio'] } }, } const out = project( [ { id: 1, profile: { bio: 'hi', age: 30 } }, { id: 2, profile: null }, ], projection, ) expect(out[0]).toEqual({ id: 1, profile: { bio: 'hi' } }) expect(out[1]).toEqual({ id: 2, profile: null }) }) test('Shape.query binds a projection to a source', () => { const UserShape = new Shape('user', { fields: ['id', 'email'] }) const out = UserShape.query([{ id: 1, email: 'a@b.c', password: 'nope' }]) expect(out).toEqual([{ id: 1, email: 'a@b.c' }]) }) }) describe('Forms — schema / validate / submit', () => { beforeEach(() => clearRegistry()) const contactForm = { fields: [ { name: 'email', type: 'email', required: true, label: 'Email' }, { name: 'age', type: 'number', required: false, validate: (v: unknown) => (Number(v) < 0 ? 'must be non-negative' : null), }, ], } test('formSchema produces field definitions', () => { const schema = formSchema(contactForm) expect(schema.fields).toHaveLength(2) expect(schema.fields[0]).toEqual({ name: 'email', type: 'email', required: true, label: 'Email', helpText: '', choices: null, initial: null, }) // Default label derived from name when omitted. expect(schema.fields[1].label).toBe('Age') }) test('validateForm: required + custom validator', () => { expect(validateForm(contactForm, { email: 'a@b.c' }).valid).toBe(true) expect(validateForm(contactForm, {}).errors.email).toEqual(['This field is required.']) expect(validateForm(contactForm, { email: 'a@b.c', age: -1 }).errors.age).toEqual([ 'must be non-negative', ]) }) test('registerForm registers schema + validate + submit with form meta', () => { const reg = registerForm(contactForm, 'contact', { submit: async (data) => ({ saved: data.email }), }) expect(reg).toEqual({ schema: 'contact-schema', validate: 'contact-validate', submit: 'contact-submit' }) for (const [wire, role] of [ ['contact-schema', 'schema'], ['contact-validate', 'validate'], ['contact-submit', 'submit'], ] as const) { const entry = getFunction(wire) expect(entry).toBeDefined() expect(entry!.form).toBe(true) expect(entry!.formName).toBe('contact') expect(entry!.formRole).toBe(role) } }) test('schema function dispatches to the field defs', async () => { registerForm(contactForm, 'contact') const r = await handleMutationCall('contact-schema', {}) expect(r.status).toBe(200) expect(r.body.result.fields).toHaveLength(2) }) test('validate function dispatches and rejects bad data', async () => { registerForm(contactForm, 'contact') const ok = await handleMutationCall('contact-validate', { data: { email: 'a@b.c' } }) expect(ok.body.result.valid).toBe(true) const bad = await handleMutationCall('contact-validate', { data: {} }) expect(bad.body.result.valid).toBe(false) expect(bad.body.result.errors.email).toBeDefined() }) test('submit validates then runs the handler', async () => { let handled: any = null registerForm(contactForm, 'contact', { submit: async (data) => { handled = data return { id: 7 } }, }) const ok = await handleMutationCall('contact-submit', { data: { email: 'a@b.c' } }) expect(ok.body.result).toEqual({ ok: true, result: { id: 7 } }) expect(handled).toEqual({ email: 'a@b.c' }) const bad = await handleMutationCall('contact-submit', { data: {} }) expect(bad.body.result.ok).toBe(false) expect(bad.body.result.errors.email).toBeDefined() }) test('submit not registered without a handler', () => { const reg = registerForm(contactForm, 'noSubmit') expect(reg.submit).toBeUndefined() expect(getFunction('noSubmit-submit')).toBeUndefined() }) })