-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* test(listAtom): scoped names * listItem as extended formAtom with name * use extended listItemForm * fix types so build passes * add effect for syncing field names * docs(List): add names * test(listAtom): the indexes are updated for current values * docs: add feature for scoped names * fix compouning names, drop the scoped atom once the fields unmount.
- Loading branch information
1 parent
d780fb2
commit 00d597b
Showing
11 changed files
with
473 additions
and
74 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,34 @@ | ||
import { FieldAtom } from "form-atoms"; | ||
import { Atom, atom } from "jotai"; | ||
import { Atom, Getter, atom } from "jotai"; | ||
|
||
export type ExtendFieldAtom<Value, State> = | ||
FieldAtom<Value> extends Atom<infer DefaultState> | ||
? Atom<DefaultState & State> | ||
: never; | ||
|
||
export const extendFieldAtom = < | ||
T extends FieldAtom<any>, | ||
T extends Atom<any>, | ||
E extends Record<string, unknown>, | ||
>( | ||
field: T, | ||
makeAtoms: (cfg: T extends Atom<infer Config> ? Config : never) => E, | ||
makeAtoms: ( | ||
cfg: T extends Atom<infer Config> ? Config : never, | ||
get: Getter, | ||
) => E, | ||
) => | ||
atom((get) => { | ||
const base = get(field); | ||
return { | ||
...base, | ||
...makeAtoms(base as T extends Atom<infer Config> ? Config : never), | ||
}; | ||
}); | ||
atom( | ||
(get) => { | ||
const base = get(field); | ||
return { | ||
...base, | ||
...makeAtoms( | ||
base as T extends Atom<infer Config> ? Config : never, | ||
get, | ||
), | ||
}; | ||
}, | ||
(get, set, update: T extends Atom<infer Config> ? Config : never) => { | ||
// @ts-expect-error fieldAtom is PrimitiveAtom | ||
set(field, { ...get(field), ...update }); | ||
}, | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
import { act, renderHook } from "@testing-library/react"; | ||
import { act, renderHook, waitFor } from "@testing-library/react"; | ||
import { | ||
FieldAtom, | ||
formAtom, | ||
useFieldActions, | ||
useFieldErrors, | ||
|
@@ -13,7 +14,7 @@ import { describe, expect, it, test, vi } from "vitest"; | |
|
||
import { listAtom } from "./listAtom"; | ||
import { numberField, textField } from "../../fields"; | ||
import { useFieldError, useListActions } from "../../hooks"; | ||
import { useFieldError, useListActions, useListField } from "../../hooks"; | ||
|
||
describe("listAtom()", () => { | ||
test("can be submitted within formAtom", async () => { | ||
|
@@ -370,4 +371,148 @@ describe("listAtom()", () => { | |
expect(state.current.dirty).toBe(false); | ||
}); | ||
}); | ||
|
||
describe("scoped name of list fields", () => { | ||
const useFieldName = (fieldAtom: FieldAtom<any>) => | ||
useAtomValue(useAtomValue(fieldAtom).name); | ||
|
||
describe("list of primitive fieldAtoms", () => { | ||
it("field name contains list name and index", async () => { | ||
const field = listAtom({ | ||
name: "recipients", | ||
value: ["[email protected]", "[email protected]"], | ||
builder: (value) => textField({ value }), | ||
}); | ||
|
||
const { result: list } = renderHook(() => useListField(field)); | ||
const { result: names } = renderHook(() => [ | ||
useFieldName(list.current.items[0]!.fields), | ||
useFieldName(list.current.items[1]!.fields), | ||
]); | ||
|
||
await waitFor(() => Promise.resolve()); | ||
|
||
expect(names.current).toEqual(["recipients[0]", "recipients[1]"]); | ||
}); | ||
|
||
it("updates the index for current value, when moved in the list", async () => { | ||
const field = listAtom({ | ||
name: "recipients", | ||
value: ["[email protected]", "[email protected]"], | ||
builder: (value) => textField({ value }), | ||
}); | ||
|
||
const { result: list } = renderHook(() => useListField(field)); | ||
const { result: values } = renderHook(() => [ | ||
useFieldValue(list.current.items[0]!.fields), | ||
useFieldName(list.current.items[0]!.fields), | ||
useFieldValue(list.current.items[1]!.fields), | ||
useFieldName(list.current.items[1]!.fields), | ||
]); | ||
const { result: listItems } = renderHook(() => | ||
useAtomValue(useAtomValue(field)._splitList), | ||
); | ||
|
||
await waitFor(() => Promise.resolve()); | ||
|
||
expect(values.current).toEqual([ | ||
"[email protected]", | ||
"recipients[0]", | ||
"[email protected]", | ||
"recipients[1]", | ||
]); | ||
|
||
const { result: listActions } = renderHook(() => useListActions(field)); | ||
|
||
// moves first item down | ||
await act(async () => listActions.current.move(listItems.current[0]!)); | ||
|
||
expect(values.current).toEqual([ | ||
"[email protected]", | ||
"recipients[0]", | ||
"[email protected]", | ||
"recipients[1]", | ||
]); | ||
}); | ||
}); | ||
|
||
describe("list of form fields", () => { | ||
it("field name contains list name, index and field name", async () => { | ||
const field = listAtom({ | ||
name: "contacts", | ||
value: [{ email: "[email protected]" }, { email: "[email protected]" }], | ||
builder: ({ email }) => ({ | ||
email: textField({ value: email, name: "email" }), | ||
}), | ||
}); | ||
|
||
const { result: list } = renderHook(() => useListField(field)); | ||
const { result: names } = renderHook(() => [ | ||
useFieldName(list.current.items[0]!.fields.email), | ||
useFieldName(list.current.items[1]!.fields.email), | ||
]); | ||
|
||
await waitFor(() => Promise.resolve()); | ||
|
||
expect(names.current).toEqual([ | ||
"contacts[0].email", | ||
"contacts[1].email", | ||
]); | ||
}); | ||
}); | ||
|
||
describe("nested listAtom", () => { | ||
// passes but throws error | ||
it.skip("has prefix of the parent listAtom", async () => { | ||
const field = listAtom({ | ||
name: "contacts", | ||
value: [ | ||
{ | ||
email: "[email protected]", | ||
addresses: [{ type: "home", city: "Kezmarok" }], | ||
}, | ||
{ | ||
email: "[email protected]", | ||
addresses: [ | ||
{ type: "home", city: "Humenne" }, | ||
{ type: "work", city: "Nove Zamky" }, | ||
], | ||
}, | ||
], | ||
builder: ({ email, addresses = [] }) => ({ | ||
email: textField({ value: email, name: "email" }), | ||
addresses: listAtom({ | ||
name: "addresses", | ||
value: addresses, | ||
builder: ({ type, city }) => ({ | ||
type: textField({ value: type, name: "type" }), | ||
city: textField({ value: city, name: "city" }), | ||
}), | ||
}), | ||
}), | ||
}); | ||
|
||
const { result: list } = renderHook(() => useListField(field)); | ||
const { result: secondContactAddresses } = renderHook(() => | ||
useListField(list.current.items[1]!.fields.addresses), | ||
); | ||
|
||
const { result: names } = renderHook(() => [ | ||
useFieldName(secondContactAddresses.current.items[0]!.fields.type), | ||
useFieldName(secondContactAddresses.current.items[0]!.fields.city), | ||
useFieldName(secondContactAddresses.current.items[1]!.fields.type), | ||
useFieldName(secondContactAddresses.current.items[1]!.fields.city), | ||
]); | ||
|
||
await waitFor(() => Promise.resolve()); | ||
|
||
expect(names.current).toEqual([ | ||
"contacts[1].addresses[0].type", | ||
"contacts[1].addresses[0].city", | ||
"contacts[1].addresses[1].type", | ||
"contacts[1].addresses[1].city", | ||
]); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.