11import { zodResolver } from '@hookform/resolvers/zod' ;
22import { ControlledCurrencyInput } from '@lambdacurry/medusa-forms/controlled/ControlledCurrencyInput' ;
33import type { Meta , StoryObj } from '@storybook/react-vite' ;
4+ import { expect , userEvent , waitFor , within } from '@storybook/test' ;
5+ import { useEffect } from 'react' ;
46import { FormProvider , useForm } from 'react-hook-form' ;
57import { z } from 'zod' ;
68
@@ -21,7 +23,7 @@ export default meta;
2123type Story = StoryObj < typeof meta > ;
2224
2325interface CurrencyFormData {
24- price : string ;
26+ price : string | number ;
2527}
2628
2729// Base wrapper component for stories
@@ -61,6 +63,84 @@ const CurrencyInputWithHookForm = ({
6163 ) ;
6264} ;
6365
66+ const CurrencyInputWithStringState = ( ) => {
67+ const form = useForm < CurrencyFormData > ( {
68+ defaultValues : { price : '' } ,
69+ } ) ;
70+ const price = form . watch ( 'price' ) ;
71+
72+ return (
73+ < FormProvider { ...form } >
74+ < div className = "w-[400px] space-y-4" >
75+ < ControlledCurrencyInput < CurrencyFormData > name = "price" label = "String price" symbol = "$" code = "usd" />
76+ < pre className = "rounded bg-gray-100 p-2 text-xs" >
77+ { JSON . stringify ( { value : price , type : typeof price } , null , 2 ) }
78+ </ pre >
79+ </ div >
80+ </ FormProvider >
81+ ) ;
82+ } ;
83+
84+ const CurrencyInputWithRequiredError = ( ) => {
85+ const form = useForm < CurrencyFormData > ( {
86+ defaultValues : { price : '' } ,
87+ mode : 'onChange' ,
88+ } ) ;
89+
90+ useEffect ( ( ) => {
91+ form . trigger ( 'price' ) . then ( ( ) => undefined ) ;
92+ } , [ form ] ) ;
93+
94+ return (
95+ < FormProvider { ...form } >
96+ < div className = "w-[400px]" >
97+ < ControlledCurrencyInput
98+ name = "price"
99+ label = "Required price"
100+ symbol = "$"
101+ code = "usd"
102+ rules = { { required : 'Price is required' } }
103+ />
104+ </ div >
105+ </ FormProvider >
106+ ) ;
107+ } ;
108+
109+ const CurrencyInputWithValueAsNumber = ( ) => {
110+ const form = useForm < CurrencyFormData > ( {
111+ defaultValues : { price : '' } ,
112+ } ) ;
113+ const price = form . watch ( 'price' ) ;
114+
115+ return (
116+ < FormProvider { ...form } >
117+ < div className = "w-[400px] space-y-4" >
118+ < ControlledCurrencyInput < CurrencyFormData >
119+ name = "price"
120+ label = "Numeric price"
121+ symbol = "$"
122+ code = "usd"
123+ rules = { { valueAsNumber : true } }
124+ />
125+ < pre className = "rounded bg-gray-100 p-2 text-xs" >
126+ { JSON . stringify ( { value : price , type : typeof price } , null , 2 ) }
127+ </ pre >
128+ </ div >
129+ </ FormProvider >
130+ ) ;
131+ } ;
132+
133+ const getStateOutput = ( canvas : ReturnType < typeof within > ) => canvas . getByText ( ( content ) => content . includes ( '"type"' ) ) ;
134+ const getInputByName = ( canvasElement : HTMLElement , name : string ) => {
135+ const input = canvasElement . querySelector < HTMLInputElement > ( `input[name="${ name } "]` ) ;
136+
137+ if ( ! input ) {
138+ throw new Error ( `Input with name "${ name } " was not found.` ) ;
139+ }
140+
141+ return input ;
142+ } ;
143+
64144// 1. Different Currency Symbols
65145export const USDCurrency : Story = {
66146 args : {
@@ -194,6 +274,74 @@ export const RequiredFieldValidation: Story = {
194274 ) ,
195275} ;
196276
277+ export const RequiredRuleError : Story = {
278+ args : {
279+ name : 'price' ,
280+ symbol : '$' ,
281+ code : 'usd' ,
282+ } ,
283+ render : ( ) => < CurrencyInputWithRequiredError /> ,
284+ play : async ( { canvasElement } ) => {
285+ const canvas = within ( canvasElement ) ;
286+
287+ await waitFor ( ( ) => {
288+ expect ( canvas . getByText ( 'Price is required' ) ) . toBeInTheDocument ( ) ;
289+ } ) ;
290+ } ,
291+ } ;
292+
293+ export const DefaultStringValue : Story = {
294+ args : {
295+ name : 'price' ,
296+ symbol : '$' ,
297+ code : 'usd' ,
298+ } ,
299+ render : ( ) => < CurrencyInputWithStringState /> ,
300+ play : async ( { canvasElement } ) => {
301+ const canvas = within ( canvasElement ) ;
302+ const input = getInputByName ( canvasElement , 'price' ) ;
303+ const state = getStateOutput ( canvas ) ;
304+
305+ await userEvent . type ( input , '1234' ) ;
306+
307+ await waitFor ( ( ) => {
308+ expect ( input . value ) . toContain ( '1,234' ) ;
309+ expect ( state ) . toHaveTextContent ( '"value": "1234"' ) ;
310+ expect ( state ) . toHaveTextContent ( '"type": "string"' ) ;
311+ } ) ;
312+ } ,
313+ } ;
314+
315+ export const ValueAsNumber : Story = {
316+ args : {
317+ name : 'price' ,
318+ symbol : '$' ,
319+ code : 'usd' ,
320+ } ,
321+ render : ( ) => < CurrencyInputWithValueAsNumber /> ,
322+ play : async ( { canvasElement } ) => {
323+ const canvas = within ( canvasElement ) ;
324+ const input = getInputByName ( canvasElement , 'price' ) ;
325+ const state = getStateOutput ( canvas ) ;
326+
327+ await userEvent . type ( input , '1234' ) ;
328+
329+ await waitFor ( ( ) => {
330+ expect ( input . value ) . toContain ( '1,234' ) ;
331+ expect ( state ) . toHaveTextContent ( '"value": 1234' ) ;
332+ expect ( state ) . toHaveTextContent ( '"type": "number"' ) ;
333+ } ) ;
334+
335+ await userEvent . clear ( input ) ;
336+
337+ await waitFor ( ( ) => {
338+ expect ( input . value ) . toBe ( '' ) ;
339+ expect ( state ) . toHaveTextContent ( '"value": null' ) ;
340+ expect ( state ) . toHaveTextContent ( '"type": "number"' ) ;
341+ } ) ;
342+ } ,
343+ } ;
344+
197345const customValidationSchema = z . object ( {
198346 price : z . string ( ) . refine ( ( val ) => {
199347 const num = Number . parseFloat ( val ) ;
0 commit comments