Skip to content

Commit 94d3397

Browse files
committed
chore: test
1 parent 4a49d2e commit 94d3397

2 files changed

Lines changed: 378 additions & 0 deletions

File tree

packages/react-form/tests/createFormHook.test-d.tsx

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -893,4 +893,131 @@ describe('createFormHook', () => {
893893
props: null,
894894
render: () => <></>,
895895
})
896+
897+
describe('extendForm', () => {
898+
function ExtendedField() {
899+
return null
900+
}
901+
function ExtendedFormComp() {
902+
return null
903+
}
904+
905+
const baseHook = createFormHook({
906+
fieldComponents: { Test },
907+
formComponents: { Test },
908+
fieldContext,
909+
formContext,
910+
})
911+
912+
const {
913+
withForm: withExtendedForm,
914+
withFieldGroup: withExtendedFieldGroup,
915+
extendForm,
916+
} = baseHook.extendForm({
917+
fieldComponents: { ExtendedField },
918+
formComponents: { ExtendedFormComp },
919+
})
920+
921+
it('should expose extendForm on the returned hook', () => {
922+
expectTypeOf(extendForm).toBeFunction()
923+
})
924+
925+
it('should include parent field components in the extended AppField render prop', () => {
926+
withExtendedForm({
927+
defaultValues: { name: '' },
928+
render: ({ form }) => {
929+
expectTypeOf(form.AppField).toBeFunction()
930+
931+
return (
932+
<form.AppField name="name">
933+
{(field) => {
934+
expectTypeOf(field.Test).toBeFunction()
935+
expectTypeOf(field.ExtendedField).toBeFunction()
936+
return null
937+
}}
938+
</form.AppField>
939+
)
940+
},
941+
})
942+
})
943+
944+
it('should include parent form components on the extended form', () => {
945+
withExtendedForm({
946+
defaultValues: { name: '' },
947+
render: ({ form }) => {
948+
expectTypeOf(form.Test).toBeFunction()
949+
expectTypeOf(form.ExtendedFormComp).toBeFunction()
950+
return null
951+
},
952+
})
953+
})
954+
955+
it('should include parent and extended components in withFieldGroup', () => {
956+
withExtendedFieldGroup({
957+
defaultValues: { name: '' },
958+
render: ({ group }) => {
959+
expectTypeOf(group.Test).toBeFunction()
960+
expectTypeOf(group.ExtendedFormComp).toBeFunction()
961+
962+
return (
963+
<group.AppField name="name">
964+
{(field) => {
965+
expectTypeOf(field.Test).toBeFunction()
966+
expectTypeOf(field.ExtendedField).toBeFunction()
967+
return null
968+
}}
969+
</group.AppField>
970+
)
971+
},
972+
})
973+
})
974+
975+
it('should error when a duplicate field component name is used in extendForm', () => {
976+
baseHook.extendForm({
977+
fieldComponents: {
978+
// @ts-expect-error 'Test' already exists in the base form
979+
Test: ExtendedField,
980+
},
981+
})
982+
})
983+
984+
it('should error when a duplicate form component name is used in extendForm', () => {
985+
baseHook.extendForm({
986+
formComponents: {
987+
// @ts-expect-error 'Test' already exists in the base form
988+
Test: ExtendedFormComp,
989+
},
990+
})
991+
})
992+
993+
it('should allow further extension of an already extended hook', () => {
994+
function ThirdField() {
995+
return null
996+
}
997+
998+
const { useAppForm: useDoublyExtended } = baseHook
999+
.extendForm({ fieldComponents: { ExtendedField } })
1000+
.extendForm({ fieldComponents: { ThirdField } })
1001+
1002+
withExtendedForm({
1003+
defaultValues: { name: '' },
1004+
render: ({ form }) => {
1005+
return (
1006+
<form.AppField name="name">
1007+
{(field) => {
1008+
expectTypeOf(field.Test).toBeFunction()
1009+
expectTypeOf(field.ExtendedField).toBeFunction()
1010+
return null
1011+
}}
1012+
</form.AppField>
1013+
)
1014+
},
1015+
})
1016+
1017+
const doublyExtendedForm = useDoublyExtended({
1018+
defaultValues: { name: '' },
1019+
})
1020+
expectTypeOf(doublyExtendedForm.AppField).toBeFunction()
1021+
})
1022+
})
8961023
})

packages/react-form/tests/createFormHook.test.tsx

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,257 @@ describe('createFormHook', () => {
700700
expect(button).toBeInTheDocument()
701701
})
702702

703+
describe('extendForm', () => {
704+
it('should include both parent and extended field components', () => {
705+
function ExtendedTextField({ label }: { label: string }) {
706+
const field = useFieldContext<string>()
707+
return (
708+
<label>
709+
<div>{label}</div>
710+
<input
711+
data-testid="extended-input"
712+
value={field.state.value}
713+
onChange={(e) => field.handleChange(e.target.value)}
714+
/>
715+
</label>
716+
)
717+
}
718+
719+
const { useAppForm: useExtendedForm } = createFormHook({
720+
fieldComponents: { TextField },
721+
formComponents: { SubscribeButton },
722+
fieldContext,
723+
formContext,
724+
}).extendForm({
725+
fieldComponents: { ExtendedTextField },
726+
})
727+
728+
function Comp() {
729+
const form = useExtendedForm({
730+
defaultValues: { firstName: 'John', lastName: 'Doe' },
731+
})
732+
733+
return (
734+
<>
735+
<form.AppField
736+
name="firstName"
737+
children={(field) => <field.TextField label="First Name" />}
738+
/>
739+
<form.AppField
740+
name="lastName"
741+
children={(field) => (
742+
<field.ExtendedTextField label="Last Name" />
743+
)}
744+
/>
745+
</>
746+
)
747+
}
748+
749+
const { getByLabelText, getByTestId } = render(<Comp />)
750+
expect(getByLabelText('First Name')).toHaveValue('John')
751+
expect(getByTestId('extended-input')).toHaveValue('Doe')
752+
})
753+
754+
it('should include both parent and extended form components', () => {
755+
function ExtendedSubmit({ label }: { label: string }) {
756+
const form = useFormContext()
757+
return (
758+
<form.Subscribe selector={(state) => state.isSubmitting}>
759+
{(isSubmitting) => (
760+
<button
761+
data-testid="extended-submit"
762+
disabled={isSubmitting}
763+
type="button"
764+
>
765+
{label}
766+
</button>
767+
)}
768+
</form.Subscribe>
769+
)
770+
}
771+
772+
const { useAppForm: useExtendedForm } = createFormHook({
773+
fieldComponents: { TextField },
774+
formComponents: { SubscribeButton },
775+
fieldContext,
776+
formContext,
777+
}).extendForm({
778+
formComponents: { ExtendedSubmit },
779+
})
780+
781+
function Comp() {
782+
const form = useExtendedForm({
783+
defaultValues: { firstName: 'John' },
784+
})
785+
786+
return (
787+
<form.AppForm>
788+
<form.SubscribeButton label="Submit" />
789+
<form.ExtendedSubmit label="Extended Submit" />
790+
</form.AppForm>
791+
)
792+
}
793+
794+
const { getByText, getByTestId } = render(<Comp />)
795+
expect(getByText('Submit')).toBeInTheDocument()
796+
expect(getByTestId('extended-submit')).toHaveTextContent(
797+
'Extended Submit',
798+
)
799+
})
800+
801+
it('should support chaining multiple extendForm calls', () => {
802+
function FieldA({ label }: { label: string }) {
803+
const field = useFieldContext<string>()
804+
return (
805+
<label>
806+
<div>{label}</div>
807+
<input
808+
value={field.state.value}
809+
onChange={(e) => field.handleChange(e.target.value)}
810+
/>
811+
</label>
812+
)
813+
}
814+
815+
function FieldB({ label }: { label: string }) {
816+
const field = useFieldContext<string>()
817+
return (
818+
<label>
819+
<div>{label}</div>
820+
<input
821+
value={field.state.value}
822+
onChange={(e) => field.handleChange(e.target.value)}
823+
/>
824+
</label>
825+
)
826+
}
827+
828+
const base = createFormHook({
829+
fieldComponents: { TextField },
830+
formComponents: {},
831+
fieldContext,
832+
formContext,
833+
})
834+
835+
const { useAppForm: useChainedForm } = base
836+
.extendForm({ fieldComponents: { FieldA } })
837+
.extendForm({ fieldComponents: { FieldB } })
838+
839+
function Comp() {
840+
const form = useChainedForm({
841+
defaultValues: { a: 'valueA', b: 'valueB', c: 'valueC' },
842+
})
843+
844+
return (
845+
<>
846+
<form.AppField
847+
name="a"
848+
children={(field) => <field.TextField label="A" />}
849+
/>
850+
<form.AppField
851+
name="b"
852+
children={(field) => <field.FieldA label="B" />}
853+
/>
854+
<form.AppField
855+
name="c"
856+
children={(field) => <field.FieldB label="C" />}
857+
/>
858+
</>
859+
)
860+
}
861+
862+
const { getByLabelText } = render(<Comp />)
863+
expect(getByLabelText('A')).toHaveValue('valueA')
864+
expect(getByLabelText('B')).toHaveValue('valueB')
865+
expect(getByLabelText('C')).toHaveValue('valueC')
866+
})
867+
868+
it('should work with withForm after extendForm', () => {
869+
function ExtendedTextField({ label }: { label: string }) {
870+
const field = useFieldContext<string>()
871+
return (
872+
<label>
873+
<div>{label}</div>
874+
<input
875+
value={field.state.value}
876+
onChange={(e) => field.handleChange(e.target.value)}
877+
/>
878+
</label>
879+
)
880+
}
881+
882+
const { useAppForm: useExtendedForm, withForm: withExtendedForm } =
883+
createFormHook({
884+
fieldComponents: { TextField },
885+
formComponents: { SubscribeButton },
886+
fieldContext,
887+
formContext,
888+
}).extendForm({
889+
fieldComponents: { ExtendedTextField },
890+
})
891+
892+
const ChildForm = withExtendedForm({
893+
defaultValues: { firstName: 'Jane', lastName: 'Smith' },
894+
render: function Render({ form }) {
895+
return (
896+
<>
897+
<form.AppField
898+
name="firstName"
899+
children={(field) => <field.TextField label="First Name" />}
900+
/>
901+
<form.AppField
902+
name="lastName"
903+
children={(field) => (
904+
<field.ExtendedTextField label="Last Name" />
905+
)}
906+
/>
907+
</>
908+
)
909+
},
910+
})
911+
912+
function Parent() {
913+
const form = useExtendedForm({
914+
defaultValues: { firstName: 'Jane', lastName: 'Smith' },
915+
})
916+
return <ChildForm form={form} />
917+
}
918+
919+
const { getByLabelText } = render(<Parent />)
920+
expect(getByLabelText('First Name')).toHaveValue('Jane')
921+
expect(getByLabelText('Last Name')).toHaveValue('Smith')
922+
})
923+
924+
it('should return a new extendForm from the extended hook', () => {
925+
function ExtendedTextField({ label }: { label: string }) {
926+
const field = useFieldContext<string>()
927+
return (
928+
<label>
929+
<div>{label}</div>
930+
<input
931+
value={field.state.value}
932+
onChange={(e) => field.handleChange(e.target.value)}
933+
/>
934+
</label>
935+
)
936+
}
937+
938+
const base = createFormHook({
939+
fieldComponents: { TextField },
940+
formComponents: { SubscribeButton },
941+
fieldContext,
942+
formContext,
943+
})
944+
945+
const extended = base.extendForm({
946+
fieldComponents: { ExtendedTextField },
947+
})
948+
949+
// extendForm should itself expose extendForm
950+
expect(typeof extended.extendForm).toBe('function')
951+
})
952+
})
953+
703954
it('should render FieldGroup Subscribe without selector (default identity)', async () => {
704955
const formOpts = formOptions({
705956
defaultValues: {

0 commit comments

Comments
 (0)