diff --git a/deps.ts b/deps.ts new file mode 100644 index 0000000..c3c1519 --- /dev/null +++ b/deps.ts @@ -0,0 +1,61 @@ +export type { + Callable, + Channel, + Instruction, + Operation, + Predicate, + Queue, + Reject, + Resolve, + Result, + Scope, + Signal, + Stream, + Subscription, + Task, +} from "https://deno.land/x/effection@3.0.0-beta.3/mod.ts"; +export { + action, + call, + createChannel, + createContext, + createQueue, + createScope, + createSignal, + each, + ensure, + Err, + Ok, + race, + resource, + run, + SignalQueueFactory, + sleep, + spawn, + suspend, + useAbortSignal, + useScope, +} from "https://deno.land/x/effection@3.0.0-beta.3/mod.ts"; + +import React from "https://esm.sh/react@18.2.0?pin=v135"; + +export type { JSX } from "https://esm.sh/react@18.2.0?pin=v135"; + +export { React }; +export { + Provider, + useDispatch, + useSelector, + useStore, +} from "https://esm.sh/react-redux@8.0.5?pin=v135"; +export type { + TypedUseSelectorHook, +} from "https://esm.sh/react-redux@8.0.5?pin=v135"; +export { createSelector } from "https://esm.sh/reselect@4.1.8?pin=v135"; + +export { + enablePatches, + produce, + produceWithPatches, +} from "https://esm.sh/immer@10.0.2?pin=v135"; +export type { Patch } from "https://esm.sh/immer@10.0.2?pin=v135"; diff --git a/test.ts b/test.ts index ad5eeea..9c7b951 100644 --- a/test.ts +++ b/test.ts @@ -5,6 +5,7 @@ export { describe, it, } from "jsr:@std/testing/bdd"; +export * as assertType from "jsr:@std/testing/types"; export { assert } from "jsr:@std/assert"; export * as asserts from "jsr:@std/assert"; export { expect } from "jsr:@std/expect"; diff --git a/test/api.test.ts b/test/api.test.ts index 299c85c..45e60a2 100644 --- a/test/api.test.ts +++ b/test/api.test.ts @@ -1,14 +1,6 @@ -import { describe, expect, it } from "../test.ts"; -import { - createSchema, - createStore, - select, - slice, - updateStore, - waitForLoader, -} from "../store/mod.ts"; import { AnyState, + API_ACTION_PREFIX, ApiCtx, call, createApi, @@ -21,6 +13,15 @@ import { waitFor, } from "../mod.ts"; import { useCache } from "../react.ts"; +import { + createSchema, + createStore, + select, + slice, + updateStore, + waitForLoader, +} from "../store/mod.ts"; +import { describe, expect, it } from "../test.ts"; interface User { id: string; @@ -48,6 +49,7 @@ const jsonBlob = (data: unknown) => { const tests = describe("createApi()"); it(tests, "POST", async () => { + expect.assertions(2); const query = createApi(); query.use(mdw.queryCtx); query.use(mdw.nameParser); @@ -118,6 +120,7 @@ it(tests, "POST", async () => { }); it(tests, "POST with uri", () => { + expect.assertions(1); const query = createApi(); query.use(mdw.queryCtx); query.use(mdw.nameParser); @@ -165,6 +168,7 @@ it(tests, "POST with uri", () => { }); it(tests, "middleware - with request fn", () => { + expect.assertions(2); const query = createApi(); query.use(mdw.queryCtx); query.use(mdw.nameParser); @@ -185,6 +189,7 @@ it(tests, "middleware - with request fn", () => { }); it(tests, "run() on endpoint action - should run the effect", () => { + expect.assertions(1); const api = createApi(); api.use(api.routes()); let acc = ""; @@ -212,7 +217,8 @@ it(tests, "run() on endpoint action - should run the effect", () => { store.dispatch(action2()); }); -it(tests, "run() from a normal saga", () => { +it(tests, "run() from a normal saga", async () => { + expect.assertions(6); const api = createApi(); api.use(api.routes()); let acc = ""; @@ -226,28 +232,43 @@ it(tests, "run() from a normal saga", () => { acc += "a"; }, ); + const extractedResults = { + actionType: null, + actionPayload: null, + name: null, + payload: null, + }; const action2 = () => ({ type: "ACTION" }); function* onAction() { const ctx = yield* safe(() => action1.run(action1({ id: "1" }))); if (!ctx.ok) { throw new Error("no ctx"); } - const payload = { name: "/users/:id [GET]", options: { id: "1" } }; - expect(ctx.value.action.type).toEqual(`@@starfx${action1}`); - expect(ctx.value.action.payload).toEqual(payload); - expect(ctx.value.name).toEqual("/users/:id [GET]"); - expect(ctx.value.payload).toEqual({ id: "1" }); + Object.assign(extractedResults, { + actionType: ctx.value.action.type, + actionPayload: ctx.value.action.payload, + name: ctx.value.name, + payload: ctx.value.payload, + }); acc += "b"; - expect(acc).toEqual("ab"); } - function* watchAction() { - yield* takeEvery(`${action2}`, onAction); + yield* takeEvery(action2, onAction); } const store = createStore({ initialState: { users: {} } }); store.run(() => keepAlive([api.bootup, watchAction])); store.dispatch(action2()); + + await new Promise((resolve) => setTimeout(resolve, 300)); + const payload = { name: "/users/:id [GET]", options: { id: "1" } }; + + expect(extractedResults.actionType).toEqual(`${API_ACTION_PREFIX}${action1}`); + expect(extractedResults.actionPayload!["name"]).toEqual(payload.name); + expect(extractedResults.actionPayload!["options"]).toEqual(payload.options); + expect(extractedResults.name).toEqual("/users/:id [GET]"); + expect(extractedResults.payload).toEqual({ id: "1" }); + expect(acc).toEqual("ab"); }); it(tests, "with hash key on a large post", async () => { diff --git a/test/safe.test.ts b/test/safe.test.ts index 50b62a3..e9d2668 100644 --- a/test/safe.test.ts +++ b/test/safe.test.ts @@ -4,6 +4,7 @@ import { call, run } from "../mod.ts"; const tests = describe("call()"); it(tests, "should call the generator function", async () => { + expect.assertions(1); function* me() { return "valid"; } @@ -15,6 +16,7 @@ it(tests, "should call the generator function", async () => { }); it(tests, "should return an Err()", async () => { + expect.assertions(1); const err = new Error("bang!"); function* me() { throw err; @@ -30,6 +32,7 @@ it(tests, "should return an Err()", async () => { }); it(tests, "should call a promise", async () => { + expect.assertions(1); const me = () => new Promise((resolve) => { setTimeout(() => { diff --git a/test/schema.test.ts b/test/schema.test.ts index 1209120..fedb9f6 100644 --- a/test/schema.test.ts +++ b/test/schema.test.ts @@ -1,4 +1,4 @@ -import { asserts, describe, it } from "../test.ts"; +import { describe, expect, it } from "../test.ts"; import { createSchema, createStore, select, slice } from "../store/mod.ts"; const tests = describe("createSchema()"); @@ -16,7 +16,7 @@ const emptyUser = { id: "", name: "" }; it(tests, "default schema", async () => { const [schema, initialState] = createSchema(); const store = createStore({ initialState }); - asserts.assertEquals(store.getState(), { + expect(store.getState()).toEqual({ cache: {}, loaders: {}, }); @@ -26,16 +26,17 @@ it(tests, "default schema", async () => { yield* schema.update(schema.cache.add({ "1": true })); }); - asserts.assertEquals(schema.cache.selectTable(store.getState()), { + expect(schema.cache.selectTable(store.getState())).toEqual({ "1": true, }); - asserts.assertEquals( + expect( schema.loaders.selectById(store.getState(), { id: "1" }).status, "loading", ); }); it(tests, "general types and functionality", async () => { + expect.assertions(8); const [db, initialState] = createSchema({ users: slice.table({ initialState: { "1": { id: "1", name: "wow" } }, @@ -50,7 +51,7 @@ it(tests, "general types and functionality", async () => { }); const store = createStore({ initialState }); - asserts.assertEquals(store.getState(), { + expect(store.getState()).toEqual({ users: { "1": { id: "1", name: "wow" } }, token: "", counter: 0, @@ -60,7 +61,7 @@ it(tests, "general types and functionality", async () => { loaders: {}, }); const userMap = db.users.selectTable(store.getState()); - asserts.assertEquals(userMap, { "1": { id: "1", name: "wow" } }); + expect(userMap).toEqual({ "1": { id: "1", name: "wow" } }); await store.run(function* () { yield* db.update([ @@ -69,30 +70,31 @@ it(tests, "general types and functionality", async () => { ]); const users = yield* select(db.users.selectTable); - asserts.assertEquals(users, { + expect(users).toEqual({ "1": { id: "1", name: "zzz" }, "2": { id: "2", name: "bob" }, }); yield* db.update(db.counter.increment()); const counter = yield* select(db.counter.select); - asserts.assertEquals(counter, 1); + expect(counter).toBe(1); yield* db.update(db.currentUser.update({ key: "name", value: "vvv" })); const curUser = yield* select(db.currentUser.select); - asserts.assertEquals(curUser, { id: "", name: "vvv" }); + expect(curUser).toEqual({ id: "", name: "vvv" }); yield* db.update(db.loaders.start({ id: "fetch-users" })); const fetchLoader = yield* select(db.loaders.selectById, { id: "fetch-users", }); - asserts.assertEquals(fetchLoader.id, "fetch-users"); - asserts.assertEquals(fetchLoader.status, "loading"); - asserts.assertNotEquals(fetchLoader.lastRun, 0); + expect(fetchLoader.id).toBe("fetch-users"); + expect(fetchLoader.status).toBe("loading"); + expect(fetchLoader.lastRun).not.toBe(0); }); }); it(tests, "can work with a nested object", async () => { + expect.assertions(3); const [db, initialState] = createSchema({ currentUser: slice.obj({ id: "", name: "", roles: [] }), cache: slice.table({ empty: {} }), @@ -102,17 +104,17 @@ it(tests, "can work with a nested object", async () => { await store.run(function* () { yield* db.update(db.currentUser.update({ key: "name", value: "vvv" })); const curUser = yield* select(db.currentUser.select); - asserts.assertEquals(curUser, { id: "", name: "vvv", roles: [] }); + expect(curUser).toEqual({ id: "", name: "vvv", roles: [] }); yield* db.update(db.currentUser.update({ key: "roles", value: ["admin"] })); const curUser2 = yield* select(db.currentUser.select); - asserts.assertEquals(curUser2, { id: "", name: "vvv", roles: ["admin"] }); + expect(curUser2).toEqual({ id: "", name: "vvv", roles: ["admin"] }); yield* db.update( db.currentUser.update({ key: "roles", value: ["admin", "users"] }), ); const curUser3 = yield* select(db.currentUser.select); - asserts.assertEquals(curUser3, { + expect(curUser3).toEqual({ id: "", name: "vvv", roles: ["admin", "users"], diff --git a/test/store.test.ts b/test/store.test.ts index 80b1395..81c0a67 100644 --- a/test/store.test.ts +++ b/test/store.test.ts @@ -1,11 +1,11 @@ -import { asserts, describe, it } from "../test.ts"; +import { createScope, Operation, parallel, put, Result, take } from "../mod.ts"; import { createStore, StoreContext, StoreUpdateContext, updateStore, } from "../store/mod.ts"; -import { createScope, Operation, parallel, put, Result, take } from "../mod.ts"; +import { describe, expect, it } from "../test.ts"; const tests = describe("store"); @@ -54,39 +54,37 @@ it( tests, "update store and receives update from channel `StoreUpdateContext`", async () => { + expect.assertions(1); const [scope] = createScope(); const initialState: Partial = { users: { 1: { id: "1", name: "testing" }, 2: { id: "2", name: "wow" } }, dev: false, }; createStore({ scope, initialState }); - + let store; await scope.run(function* (): Operation[]> { const result = yield* parallel([ function* () { - const store = yield* StoreContext; + store = yield* StoreContext; const chan = yield* StoreUpdateContext; const msgList = yield* chan.subscribe(); yield* msgList.next(); - asserts.assertEquals(store.getState(), { - users: { 1: { id: "1", name: "eric" }, 3: { id: "", name: "" } }, - theme: "", - token: null, - dev: true, - }); }, - function* () { yield* updateStore(updateUser({ id: "1", name: "eric" })); }, ]); - return yield* result; }); + expect(store!.getState()).toEqual({ + users: { 1: { id: "1", name: "eric" }, 3: { id: "", name: "" } }, + dev: true, + }); }, ); it(tests, "update store and receives update from `subscribe()`", async () => { + expect.assertions(1); const initialState: Partial = { users: { 1: { id: "1", name: "testing" }, 2: { id: "2", name: "wow" } }, dev: false, @@ -96,7 +94,7 @@ it(tests, "update store and receives update from `subscribe()`", async () => { const store = createStore({ initialState }); store.subscribe(() => { - asserts.assertEquals(store.getState(), { + expect(store.getState()).toEqual({ users: { 1: { id: "1", name: "eric" }, 3: { id: "", name: "" } }, dev: true, theme: "", @@ -110,6 +108,7 @@ it(tests, "update store and receives update from `subscribe()`", async () => { }); it(tests, "emit Action and update store", async () => { + expect.assertions(1); const initialState: Partial = { users: { 1: { id: "1", name: "testing" }, 2: { id: "2", name: "wow" } }, dev: false, @@ -131,7 +130,7 @@ it(tests, "emit Action and update store", async () => { yield* result; }); - asserts.assertEquals(store.getState(), { + expect(store.getState()).toEqual({ users: { 1: { id: "1", name: "eric" }, 3: { id: "", name: "" } }, theme: "", token: "", @@ -140,6 +139,7 @@ it(tests, "emit Action and update store", async () => { }); it(tests, "resets store", async () => { + expect.assertions(2); const initialState: Partial = { users: { 1: { id: "1", name: "testing" }, 2: { id: "2", name: "wow" } }, dev: false, @@ -156,7 +156,7 @@ it(tests, "resets store", async () => { }); }); - asserts.assertEquals(store.getState(), { + expect(store.getState()).toEqual({ users: { 3: { id: "3", name: "hehe" } }, theme: "darkness", token: "", @@ -165,7 +165,7 @@ it(tests, "resets store", async () => { await store.run(store.reset(["users"])); - asserts.assertEquals(store.getState(), { + expect(store.getState()).toEqual({ users: { 3: { id: "3", name: "hehe" } }, dev: false, theme: "", diff --git a/test/thunk.test.ts b/test/thunk.test.ts index d4b1344..f2b6c64 100644 --- a/test/thunk.test.ts +++ b/test/thunk.test.ts @@ -1,3 +1,4 @@ +import { API_ACTION_PREFIX } from "../action.ts"; import { call, createThunks, @@ -7,10 +8,9 @@ import { waitFor, } from "../mod.ts"; import { createStore, updateStore } from "../store/mod.ts"; -import { assertLike, asserts, describe, it } from "../test.ts"; +import { describe, expect, it } from "../test.ts"; import type { Next, ThunkCtx } from "../mod.ts"; - // deno-lint-ignore no-explicit-any interface RoboCtx, P = any> extends ThunkCtx

{ url: string; @@ -128,6 +128,7 @@ it( tests, "when create a query fetch pipeline - execute all middleware and save to redux", () => { + expect.assertions(1); const api = createThunks(); api.use(api.routes()); api.use(convertNameToUrl); @@ -143,7 +144,7 @@ it( store.dispatch(fetchUsers()); - asserts.assertEquals(store.getState(), { + expect(store.getState()).toEqual({ users: { [mockUser.id]: deserializeUser(mockUser) }, tickets: {}, }); @@ -154,6 +155,7 @@ it( tests, "when providing a generator the to api.create function - should call that generator before all other middleware", () => { + expect.assertions(1); const api = createThunks(); api.use(api.routes()); api.use(convertNameToUrl); @@ -183,7 +185,7 @@ it( store.run(api.bootup); store.dispatch(fetchTickets()); - asserts.assertEquals(store.getState(), { + expect(store.getState()).toEqual({ users: { [mockUser.id]: deserializeUser(mockUser) }, tickets: { [mockTicket.id]: deserializeTicket(mockTicket) }, }); @@ -191,6 +193,7 @@ it( ); it(tests, "error handling", () => { + expect.assertions(1); let called; const api = createThunks(); api.use(api.routes()); @@ -210,10 +213,11 @@ it(tests, "error handling", () => { const store = createStore({ initialState: {} }); store.run(api.bootup); store.dispatch(action()); - asserts.assertStrictEquals(called, true); + expect(called).toBe(true); }); it(tests, "error handling inside create", () => { + expect.assertions(1); let called = false; const api = createThunks(); api.use(api.routes()); @@ -235,10 +239,11 @@ it(tests, "error handling inside create", () => { const store = createStore({ initialState: {} }); store.run(api.bootup); store.dispatch(action()); - asserts.assertStrictEquals(called, true); + expect(called).toBe(true); }); it(tests, "error inside endpoint mdw", () => { + expect.assertions(1); let called = false; const query = createThunks(); query.use(function* (_, next) { @@ -266,14 +271,15 @@ it(tests, "error inside endpoint mdw", () => { }); store.run(query.bootup); store.dispatch(fetchUsers()); - asserts.assertEquals(called, true); + expect(called).toBe(true); }); it(tests, "create fn is an array", () => { + expect.assertions(1); const api = createThunks(); api.use(api.routes()); api.use(function* (ctx, next) { - asserts.assertEquals(ctx.request, { + expect(ctx.request).toEqual({ method: "POST", body: { test: "me", @@ -300,9 +306,13 @@ it(tests, "create fn is an array", () => { }); it(tests, "run() on endpoint action - should run the effect", () => { + expect.assertions(4); const api = createThunks(); api.use(api.routes()); + let acc = ""; + let curCtx: RoboCtx = {} as RoboCtx; + const action1 = api.create( "/users", { supervisor: takeEvery }, @@ -317,34 +327,36 @@ it(tests, "run() on endpoint action - should run the effect", () => { { supervisor: takeEvery }, function* (_, next) { yield* next(); - const curCtx = yield* call(() => action1.run(action1())); + curCtx = yield* call(() => action1.run(action1())); acc += "b"; - asserts.assert(acc === "ab"); - assertLike(curCtx, { - action: { - type: `@@starfx${action1}`, - payload: { - name: "/users", - }, - }, - name: "/users", - request: { method: "expect this" }, - }); }, ); const store = createStore({ initialState: {} }); store.run(api.bootup); store.dispatch(action2()); + expect(acc).toBe("ab"); + expect(curCtx.action).toMatchObject({ + type: `${API_ACTION_PREFIX}${action1}`, + payload: { + name: "/users", + }, + }); + expect(curCtx.name).toBe("/users"); + expect(curCtx.request).toEqual({ method: "expect this" }); }); it( tests, "run() on endpoint action with payload - should run the effect", () => { + expect.assertions(4); const api = createThunks(); api.use(api.routes()); + let acc = ""; + let curCtx: RoboCtx = {} as RoboCtx; + const action1 = api.create<{ id: string }>( "/users", { supervisor: takeEvery }, @@ -359,29 +371,28 @@ it( { supervisor: takeEvery }, function* (_, next) { yield* next(); - const curCtx = yield* action1.run({ id: "1" }); + curCtx = yield* call(() => action1.run({ id: "1" })); acc += "b"; - asserts.assert(acc === "ab"); - assertLike(curCtx, { - action: { - type: `@@starfx${action1}`, - payload: { - name: "/users", - }, - }, - name: "/users", - request: { method: "expect this" }, - }); }, ); const store = createStore({ initialState: {} }); store.run(api.bootup); store.dispatch(action2()); + expect(acc).toBe("ab"); + expect(curCtx.action).toMatchObject({ + type: `${API_ACTION_PREFIX}${action1}`, + payload: { + name: "/users", + }, + }); + expect(curCtx.name).toBe("/users"); + expect(curCtx.request).toEqual({ method: "expect this" }); }, ); it(tests, "middleware order of execution", async () => { + expect.assertions(1); let acc = ""; const api = createThunks(); api.use(api.routes()); @@ -418,10 +429,11 @@ it(tests, "middleware order of execution", async () => { store.dispatch(action()); await store.run(waitFor(() => acc === "abcdefg")); - asserts.assert(acc === "abcdefg"); + expect(acc).toBe("abcdefg"); }); it(tests, "retry with actionFn", async () => { + expect.assertions(1); let acc = ""; let called = false; @@ -447,10 +459,11 @@ it(tests, "retry with actionFn", async () => { store.dispatch(action()); await store.run(waitFor(() => acc === "agag")); - asserts.assertEquals(acc, "agag"); + expect(acc).toBe("agag"); }); it(tests, "retry with actionFn with payload", async () => { + expect.assertions(1); let acc = ""; const api = createThunks(); api.use(api.routes()); @@ -477,10 +490,11 @@ it(tests, "retry with actionFn with payload", async () => { store.dispatch(action({ page: 1 })); await store.run(waitFor(() => acc === "agag")); - asserts.assertEquals(acc, "agag"); + expect(acc).toBe("agag"); }); it(tests, "should only call thunk once", () => { + expect.assertions(1); const api = createThunks(); api.use(api.routes()); let acc = ""; @@ -505,10 +519,11 @@ it(tests, "should only call thunk once", () => { const store = createStore({ initialState: {} }); store.run(api.bootup); store.dispatch(action2()); - asserts.assertEquals(acc, "a"); + expect(acc).toBe("a"); }); it(tests, "should be able to create thunk after `register()`", () => { + expect.assertions(1); const api = createThunks(); api.use(api.routes()); const store = createStore({ initialState: {} }); @@ -519,10 +534,11 @@ it(tests, "should be able to create thunk after `register()`", () => { acc += "a"; }); store.dispatch(action()); - asserts.assertEquals(acc, "a"); + expect(acc).toBe("a"); }); it(tests, "should warn when calling thunk before registered", () => { + expect.assertions(1); const err = console.warn; let called = false; console.warn = () => { @@ -534,11 +550,12 @@ it(tests, "should warn when calling thunk before registered", () => { const action = api.create("/users"); store.dispatch(action()); - asserts.assertEquals(called, true); + expect(called).toBe(true); console.warn = err; }); it(tests, "it should call the api once even if we register it twice", () => { + expect.assertions(1); const api = createThunks(); api.use(api.routes()); const store = createStore({ initialState: {} }); @@ -550,13 +567,14 @@ it(tests, "it should call the api once even if we register it twice", () => { acc += "a"; }); store.dispatch(action()); - asserts.assertEquals(acc, "a"); + expect(acc).toBe("a"); }); it( tests, "Should call the API only once, even if registered multiple times, with multiple APIs defined.", () => { + expect.assertions(2); const api1 = createThunks(); api1.use(api1.routes()); @@ -578,11 +596,7 @@ it( }); store.dispatch(action()); - asserts.assertEquals( - acc, - "b", - "Expected 'b' after first API call, but got: " + acc, - ); + expect(acc).toBe("b"); let acc2 = ""; const action2 = api2.create("/users", function* () { @@ -590,11 +604,7 @@ it( }); store.dispatch(action2()); - asserts.assertEquals( - acc2, - "c", - "Expected 'c' after second API call, but got: " + acc2, - ); + expect(acc2).toBe("c"); }, ); @@ -602,6 +612,7 @@ it( tests, "should unregister the thunk when the registration function exits", async () => { + expect.assertions(1); const api1 = createThunks(); api1.use(api1.routes()); @@ -616,15 +627,12 @@ it( }); store.dispatch(action()); - asserts.assertEquals( - acc, - "b", - "Expected 'b' after first API call, but got: " + acc, - ); + expect(acc).toBe("b"); }, ); it(tests, "should allow multiple stores to register a thunk", () => { + expect.assertions(1); const api1 = createThunks(); api1.use(api1.routes()); const storeA = createStore({ initialState: {} }); @@ -638,9 +646,5 @@ it(tests, "should allow multiple stores to register a thunk", () => { storeA.dispatch(action()); storeB.dispatch(action()); - asserts.assertEquals( - acc, - "bb", - "Expected 'bb' after first API call, but got: " + acc, - ); + expect(acc).toBe("bb"); });