Skip to content

Commit

Permalink
feat(npm-zod): add vitest, add support for catches, brands, pipelines…
Browse files Browse the repository at this point in the history
…, effects and etc (#1051)

* feat(npm-zod): add vitest, add support for catchs, brands, pipelines, effects and etc

* feat(npm-zod): proper derivation of z.BRAND

* fix(npm-zod): storing union types in AtomMut rather than primitive containers

* fix(npm-zod): add proper initialization of catch value, remove type: module from package.json
  • Loading branch information
Xelson authored Mar 1, 2025
1 parent b00054a commit a990907
Show file tree
Hide file tree
Showing 4 changed files with 372 additions and 120 deletions.
4 changes: 2 additions & 2 deletions packages/npm-zod/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
"sandbox": "vite",
"prepublishOnly": "npm run build && npm run test",
"build": "microbundle",
"test": "vitest run src/*",
"test:watch": "vitest src/*"
"test": "vitest run src/* --typecheck",
"test:watch": "vitest src/* --typecheck"
},
"dependencies": {
"@reatom/core": ">=3.8.0",
Expand Down
164 changes: 138 additions & 26 deletions packages/npm-zod/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,143 @@
import { describe, test, expect } from 'vitest'
import { createTestCtx, mockFn } from '@reatom/testing'
import { ParseAtoms, parseAtoms } from '@reatom/lens'
import { z } from 'zod'
import { reatomZod } from './'

describe('reatomZod', () => {
test('base API', async () => {
const model = reatomZod(
z.object({
n: z.number(),
s: z.string(),
readonly: z.string().readonly(),
}),
{
sync: () => {
track(parseAtoms(ctx, model))
},
initState: { n: 42, readonly: 'foo' },
import { test, expect } from "vitest";
import { createTestCtx, mockFn } from "@reatom/testing";
import { ParseAtoms, parseAtoms } from "@reatom/lens";
import { z } from "zod";
import { reatomZod } from "./";

test("base API", async () => {
const model = reatomZod(
z.object({
n: z.number(),
s: z.string(),
readonly: z.string().readonly(),
}),
{
sync: () => {
track(parseAtoms(ctx, model));
},
)
const track = mockFn<[ParseAtoms<typeof model>], any>()
const ctx = createTestCtx()
initState: { n: 42, readonly: "foo" },
}
);
const track = mockFn<[ParseAtoms<typeof model>], any>();
const ctx = createTestCtx();

expect(model.readonly).toBe("foo");
expect(ctx.get(model.n)).toBe(42);

model.s(ctx, "bar");
expect(track.lastInput()).toEqual({ n: 42, s: "bar", readonly: "foo" });
});

test("right values for effects", async () => {
const schema = z.object({
refine: z.string().nullable().refine((v) => !v || v.length > 0, 'too short'),
transform: z.string().transform(v => v.length > 3 ? 1337 : v),
})

const model = reatomZod(schema, {
sync: () => {
track(parseAtoms(ctx, model));
},
initState: {
refine: 'string',
transform: 1337,
}
})

const track = mockFn<[ParseAtoms<typeof model>], any>();
const ctx = createTestCtx();

expect(ctx.get(model.refine)).toBe('string')
expect(ctx.get(model.transform)).toBe(1337)
})

test("right values for catch", async () => {
const schema = z.object({
catch: z.string().nullable().catch('catchValue'),
})

const model = reatomZod(schema, {
sync: () => {
track(parseAtoms(ctx, model));
},
initState: {
catch: null,
}
})

const track = mockFn<[ParseAtoms<typeof model>], any>();
const ctx = createTestCtx();

expect(ctx.get(model.catch)).toBe(null)

const catchModel = reatomZod(schema, { initState: undefined });
expect(ctx.get(catchModel.catch)).toBe('catchValue')
})

expect(model.readonly).toBe('foo')
expect(ctx.get(model.n)).toBe(42)
test("right values for brand", async () => {
const brandSchema = z.string().nullable().brand('foo');
const brandedValue = brandSchema.parse('string');

model.s(ctx, 'bar')
expect(track.lastInput()).toEqual({ n: 42, s: 'bar', readonly: 'foo' })
const schema = z.object({
brand: brandSchema,
})

const model = reatomZod(schema, {
sync: () => {
track(parseAtoms(ctx, model));
},
initState: {
brand: brandedValue,
}
})

const track = mockFn<[ParseAtoms<typeof model>], any>();
const ctx = createTestCtx();

expect(ctx.get(model.brand)).toBe(brandedValue)
})

test("right values for pipeline", async () => {
const dateValue = new Date();

const schema = z.object({
pipeline: z.union([z.number(), z.string(), z.date()]).pipe(z.coerce.date()),
})

const model = reatomZod(schema, {
sync: () => {
track(parseAtoms(ctx, model));
},
initState: {
pipeline: dateValue,
}
})

const track = mockFn<[ParseAtoms<typeof model>], any>();
const ctx = createTestCtx();

expect(ctx.get(model.pipeline)).toBe(dateValue)
})

test("right values for lazy", async () => {
const lazySchema = z.lazy(() => z.object({
number: z.number(),
string: z.string(),
}))

const model = reatomZod(lazySchema, {
sync: () => {
track(parseAtoms(ctx, model));
},
initState: {
number: 42,
string: "test",
}
})

const track = mockFn<[ParseAtoms<typeof model>], any>();
const ctx = createTestCtx();

expect(ctx.get(model.number)).toBe(42)
expect(ctx.get(model.string)).toBe("test")
})
Loading

0 comments on commit a990907

Please sign in to comment.