Testing with Jest and React Testing Library
Testing with Jest and React Testing Library
About Jest
These instructions have been written with JavaScript in mind. Jest also supports TypeScript. Further instructions on how to set up Jest for TypeScript can be found on their
More information on setting up Jest can be found on
Testing with React components
The@ukic/react
package will need to be transformed before you can use these components in Jest tests.
Add atransformIgnorePatterns
field with the value["/node_modules/(?!@ukic/react)"]
to your Jest config.
About React Testing Library
getByText()
to facilitate that.
Shadow DOM Testing Library
It is not possible to exclusively use RTL to test
@ukic/react
components as they are React-wrapped web components, which use the
<ShadowRoot>
). RTL does not provide utility functions that traverse beyond the shadow DOM. With the addition of
getByShadowText()
).
// DOM tree for IcAlert.
<ic-alert class="dark hydrated" message="Foo" role="alert"><ShadowRoot>
...
<ic-typography class="ic-typography-body hydrated"><ShadowRoot><slot /></ShadowRoot>
Foo
</ic-typography>
...
</ShadowRoot></ic-alert>
Example: Testing multiple components
Below is an example of testing@ukic/react
components within an ICDS ‘test app’ using RTL, Jest and Shadow DOM Testing Library.
it('fills out values on the chooseCoffee page, tests for an error, and submits',async()=>{ container.addEventListener("icChange", callbackFn);// Check the current form stepconst stepOne = container.querySelector('ic-step[step-title="Choose coffee"]')asHTMLIcStepElement;expect(stepOne.stepType).toBe(stepStates.current);// Select radio-option from ic-radio-groupconst coffeeRadio = container.querySelector('ic-radio-option[value="house"]')asHTMLIcRadioOptionElement;await user.click(coffeeRadio);expect(callbackFn).toHaveBeenCalled();expect(coffeeRadio.selected).toBe(true);// Try submitting to see error stateconst coffeeSubmit = screen.getByShadowTestId("coffee-submit-btn")asHTMLIcButtonElement;await user.click(coffeeSubmit);// ic-alert should be visibleconst alert = container.querySelector('ic-alert')asHTMLIcAlertElement;expect(alert).not.toBeNull();// Select size from ic-selectconst sizeSelect = container.querySelector('ic-select[label="Size"]')asHTMLIcSelectElement;// await user.click(sizeSelect);// find ic-menu in the shadowRoot of ic-selectconst sizeMenuOption =awaitfindByShadowLabelText(sizeSelect,"250g")await user.click(sizeMenuOption);expect(callbackFn).toHaveBeenCalled();// Submit first page of formawait user.click(coffeeSubmit);expect(logSpy).toHaveBeenNthCalledWith(2,filledForm());});
<IcPageHeaderheading="Customise your coffee subscription"subheading="Choose your coffee, enter your details and checkout, easy as 1... 2... 3!"size="small"id="top"stickyaligned="center"><IcChipslot="heading-adornment"label="V0.0.01"size="large"/><IcStepperslot="stepper"><IcStepstepTitle="Choose coffee"stepType={handleSteps(formSteps.chooseCoffee)}/><IcStepstepTitle="Enter Details"stepType={handleSteps(formSteps.enterDetails)}/><IcStepstepTitle="Checkout"stepType={handleSteps(formSteps.checkout)}/></IcStepper></IcPageHeader><IcBackToToptarget="top"/><formonSubmit={handleSubmit}>{formSteps.chooseCoffee.current&&(<IcSectionContaineraligned="left">{formValidation &&(<IcAlertvariant="error"heading="Error"message="Please fill in all required fields"announced/>)}<IcTypographyvariant="subtitle-large"> Please choose your coffee</IcTypography><IcTypographyvariant="body"maxLines={2}> Sip back and relax as we embark on a journey through the aromatic fields of coffee-inspired lorem ipsum. In the heart of a lush, verdant valley kissed by the golden hues of dawn, there lies a quaint little plantation where the beans of legend are nurtured. Each bean, a tiny vessel of dreams and whispers of faraway lands, cradled in the earth's embrace until it bursts forth with a promise of warmth and vigor.</IcTypography><divclassName="input-container"><IcRadioGroupname="radio-group-1"label="What variety of coffee would you like?"helperText="House blend is the default option"size="small"requiredonIcChange={(ev)=>handleChange("coffeeForm","variety", ev.detail.value)}{...(formValidation&&formValues.coffeeForm.variety===""&&{validationText:"Pleasechooseanoption",validationStatus:"error",})}><IcRadioOptionvalue="house"label="House Blend"selected={formValues.coffeeForm.variety==="house"}/><IcRadioOptionvalue="liberica"label="Liberica"selected={formValues.coffeeForm.variety==="liberica"}/><IcRadioOptionvalue="arabica"label="Arabica"selected={formValues.coffeeForm.variety==="arabica"}/><IcRadioOptionvalue="mundo"label="Mundo Nova"selected={formValues.coffeeForm.variety==="mundo"}/></IcRadioGroup></div><divclassName="input-container"><IcSelectlabel="Grind"helperText="Please select a grind type"name="grind-select"options={grindOptions}size="small"className="input"value={formValues.coffeeForm.grind}onIcChange={(ev)=>handleChange("coffeeForm","grind", ev.detail.value)}{...(formValidation&&formValues.coffeeForm.grind===""&&{validationText:"Pleasechooseagrindsize",validationStatus:"error",})}/><IcSelectlabel="Size"helperText="Please select a bag size"name="size-select"requiredoptions={sizeOptions}size="small"className="input"value={formValues.coffeeForm.size}onIcChange={(ev)=>handleChange("coffeeForm","size", ev.detail.value)}{...(formValidation&&formValues.coffeeForm.size===""&&{validationText:"Pleasechooseasize",validationStatus:"error",})}/></div><divclassName="input-container"><IcButtonvariant="primary"className="button"onClick={(ev)=>handleClick(ev, next)}data-testid="coffee-submit-btn"> Add to order</IcButton></div></IcSectionContainer>)}{formSteps.enterDetails.current&&(<IcSectionContaineraligned="left">{formValidation &&(<IcAlertvariant="error"heading="Error"message="Please fill in all required fields"announced/>)}<IcTypographyvariant="subtitle-large"> Please enter your details</IcTypography><IcTypographyvariant="body"> Nearly there, we just need a few more details. Purchases must be made by an adult over the age of 18. We will never share your details with fourth parties.</IcTypography><divclassName="input-container"><IcTextFieldlabel="Name"name="name"requiredclassName="input"size="small"value={formValues.detailForm.name}onIcChange={(ev)=>handleChange("detailForm","name", ev.detail.value)}{...(formValidation&&formValues.detailForm.name===""&&{validationText:"Pleaseenteryourname",validationStatus:"error",})}autoFocus/><IcTextFieldlabel="Email"name="email"type="email"requiredsize="small"className="input"value={formValues.detailForm.email}onIcInput={(ev)=>handleChange("detailForm","email", ev.detail.value)}{...(formValidation&&(formValues.detailForm.email===""||!formValues.detailForm.email.includes("@"))&&{validationText:"Pleaseenteranemail",validationStatus:"error",})}data-test-id="email-text-field"/><IcTextFieldlabel="Phone"name="phone"type="number"requiredsize="small"className="input"value={formValues.detailForm.phone}onIcInput={(ev)=>handleChange("detailForm","phone", ev.detail.value)}{...(formValidation&&formValues.detailForm.phone===""&&{validationText:"Pleaseenteranumberonwhichwecancontactyou",validationStatus:"error",})}/></div><divclassName="input-container"><IcCheckboxGroupname="signup"onIcChange={(ev)=>handleChange("detailForm","contact", ev.detail.value)}label="Sign up for notifications about future products?"className="input"><IcCheckboxlabel="SMS"name="sms"value="sms"checked={formValues.detailForm.contact.includes("sms")}/><IcCheckboxlabel="Email"name="email"value="email"checked={formValues.detailForm.contact.includes("email")}/></IcCheckboxGroup></div><divclassName="input-container"></div><divclassName="input-container"><IcButtonvariant="secondary"onClick={(ev)=>handleClick(ev, back)}className="button"> Go Back</IcButton><IcButtonvariant="primary"onClick={(ev)=>handleClick(ev, next)}className="button"data-testid="details-submit-btn"> Add to order</IcButton></div></IcSectionContainer>)}{formSteps.checkout.current&&(<IcSectionContaineraligned="left">{formValidation &&(<IcAlertvariant="error"heading="Error"message="Please fill in all required fields"announced/>)}<IcTypographyvariant="subtitle-large">Last step!</IcTypography><IcTypographyvariant="body"> Please choose a start date for your subscription and agree to the terms and conditions. Feel free to cancel your subscriptions at any time.</IcTypography><divclassName="input-container"><IcDatePickerlabel="When would you like your subscription to start?"className="input"disablePastrequiredsize="small"data-testid="date-picker"value={formValues.checkoutForm.dateToStart}onIcChange={(ev)=>handleChange("checkoutForm","dateToStart", ev.detail.value)}{...(formValidation&&formValues.checkoutForm.dateToStart===""&&{validationText:"Pleasechooseadate",validationStatus:"error",})}/></div><divclassName="input-container"><IcRadioGrouplabel="Please agree to the terms and conditions"name="terms"requiredclassName="input"size="small"onIcChange={(ev)=>handleChange("checkoutForm","terms", ev.detail.value)}{...(((formValidation&&formValues.checkoutForm.terms==="")||(formValidation&&formValues.checkoutForm.terms==="decline"))&&{validationText:"Pleaseagreetothetermsandconditions",validationStatus:"error",})}><IcRadioOptionlabel="Agree"name="agree"value="agree"selected={formValues.checkoutForm.terms==="agree"}/><IcRadioOptionlabel="Decline"name="decline"value="decline"selected={formValues.checkoutForm.terms==="decline"}/></IcRadioGroup></div><divclassName="input-container"><IcButtonvariant="secondary"onClick={(ev)=>handleClick(ev, back)}className="button"> Go Back</IcButton><IcButtonvariant="primary"onClick={handleSubmit}className="button"> Submit order</IcButton><IcToastRegionref={toastRegionEl}><IcToastheading="Thanks for your order!"ref={toastEl}dismissMode="automatic"autoDismissTimeout={3000}variant="success"onIcDismiss={()=>resetForm()}/></IcToastRegion></div></IcSectionContainer>)}</form>
interfaceCoffeeForm{variety: string;grind: string;size: string;}interfaceDetailForm{name: string;email: string;phone: string;contact: string[];}interfaceCheckoutForm{dateToStart: string;terms: string;}exportinterfaceFormValues{coffeeForm:CoffeeForm;detailForm:DetailForm;checkoutForm:CheckoutForm;}exportinterfacechooseCoffee{active: boolean;completed: boolean;current: boolean;disabled: boolean;}exportinterfaceenterDetails{active: boolean;completed: boolean;current: boolean;disabled: boolean;}exportinterfacecheckout{active: boolean;completed: boolean;current: boolean;disabled: boolean;}exportinterfacestepTypes{active: boolean;completed: boolean;current: boolean;disabled: boolean;}exportinterfaceFormSteps{chooseCoffee: chooseCoffee;enterDetails: enterDetails;checkout: checkout;}
import{FormValues,FormSteps}from"./types";exportconst grindOptions =[{value:"whole",label:"Whole Bean (default)"},{value:"filter",label:"Filter",description:"A medium grind ideal for drip and pour-over methods",},{value:"espresso",label:"Espresso",description:"The fine grind size ensures a slow, even extraction",},{value:"aeropress",label:"Aeropress"},{value:"cafetiere",label:"Cafetiere",description:"Coarse grinds work best for brewing methods like the French press and cold brew",},];exportconst sizeOptions =[{value:"250",label:"250g"},{value:"500",label:"500g"},{value:"1000",label:"1000g"},];exportconst next ="next";exportconst back ="back";exportconstinitialFormValues:FormValues={coffeeForm:{variety:"",grind:"whole",size:"",},detailForm:{name:"",email:"",phone:"",contact:["",""],},checkoutForm:{dateToStart:"",terms:"",},};exportconstinitialFormSteps:FormSteps={chooseCoffee:{active:true,completed:false,current:true,disabled:false,},enterDetails:{active:true,completed:false,current:false,disabled:false,},checkout:{active:true,completed:false,current:false,disabled:false,},};
import{IcStepTypes}from"@ukic/web-components";import{ stepTypes }from"./types";exportconst handleSteps =(step: stepTypes):IcStepTypes|undefined=>{return step.current?("current"asIcStepTypes): step.completed?("completed"asIcStepTypes): step.active?("active"asIcStepTypes): step.disabled?("disabled"asIcStepTypes):undefined;};