UNPKG

@oslokommune/punkt-testing-utils

Version:
251 lines (250 loc) 14.2 kB
import { getElementError } from "@testing-library/dom"; //#region src/index.ts var PKT_CUSTOM_FORMFIELDS = [ "pkt-backlink", "pkt-checkbox", "pkt-combobox", "pkt-progressbar", "pkt-radiobutton", "pkt-select", "pkt-textarea", "pkt-textinput" ]; var PKT_CUSTOM_ELEMENTS = [ "pkt-input-wrapper", "pkt-icon", "pkt-helptext", "pkt-loader", "pkt-backlink", ...PKT_CUSTOM_FORMFIELDS ]; var setupPktTestingLibrary = (options) => { const tools = { fireEvent: options.fireEvent, findAllByLabelText: options.findAllByLabelText, findAllByDisplayValue: options.findAllByDisplayValue, findAllByTestId: options.findAllByTestId, findAllByText: options.findAllByText, getAllByLabelText: options.getAllByLabelText, getAllByDisplayValue: options.getAllByDisplayValue, getAllByTestId: options.getAllByTestId, getAllByText: options.getAllByText }; return { findPktElementByLabelText: withTestingLibrary(tools, findPktElementByLabel), waitForPktElementsToBeDefined, getPktElementByLabelText: withTestingLibrary(tools, getPktElementByLabel), setPktElementChecked: withTestingLibrary(tools, setPktElementChecked), isPktElementChecked: withTestingLibrary(tools, isPktElementChecked), getPktSelectOptions: withTestingLibrary(tools, getPktSelectOptions), getAllPktElementsByLabelText: withTestingLibrary(tools, getAllPktElementsByLabelText), setPktElementValue: withTestingLibrary(tools, setPktElementValue), pktClickButton: withTestingLibrary(tools, pktClickButton), setPktSelectedOptionsByLabel: withTestingLibrary(tools, setPktSelectedOptionsByLabel), getPktElementByDisplayValue: withTestingLibrary(tools, getPktElementByDisplayValue), findPktElementByDisplayValue: withTestingLibrary(tools, findPktElementByDisplayValue) }; }; var syncQueryFunctionResolver = (testingLibrary) => (queryType) => { const query = { text: testingLibrary.getAllByText, label: testingLibrary.getAllByLabelText, testid: testingLibrary.getAllByTestId, displayValue: testingLibrary.getAllByDisplayValue }[queryType]; if (query) return query; else throw new Error("Unsupported query type: " + queryType); }; var asyncQueryFunctionResolver = (testingLibrary) => (queryType) => { const query = { text: testingLibrary.findAllByText, label: testingLibrary.findAllByLabelText, testid: testingLibrary.findAllByTestId, displayValue: testingLibrary.findAllByDisplayValue }[queryType]; if (query) return query; else throw new Error("Unsupported query type: " + queryType); }; var syncResolveIdentifierOrElement = (testingLibrary) => (elementOrIdentifier, query) => typeof elementOrIdentifier === "string" || elementOrIdentifier instanceof RegExp ? getPktElementBy(testingLibrary)(query, elementOrIdentifier) : elementOrIdentifier; var getByLabelElement = (possibleLabelElement) => { const control = possibleLabelElement instanceof HTMLLabelElement && possibleLabelElement.control; if (control) return control; else throw new Error("Fant ikke noe skjema-element for labelen: " + possibleLabelElement.outerHTML); }; var asyncResolveIdentifierOrElement = (testingLibrary) => async (elementOrIdentifier, query) => elementOrIdentifier instanceof HTMLLabelElement && getByLabelElement(elementOrIdentifier) || (typeof elementOrIdentifier === "string" || elementOrIdentifier instanceof RegExp ? await findPktElementBy(testingLibrary)(query, elementOrIdentifier) : elementOrIdentifier); var asyncFuncWithStringIdentifierOrElement = (testingLibrary) => (func, query = "label") => { return async (labelOrPktElement, ...restArgs) => { return await func(await asyncResolveIdentifierOrElement(testingLibrary)(labelOrPktElement, query), ...restArgs); }; }; var syncFuncWithStringIdentifierOrElement = (testingLibrary) => (func, query = "label") => { return (labelOrPktElement, ...restArgs) => { return func(syncResolveIdentifierOrElement(testingLibrary)(labelOrPktElement, query), ...restArgs); }; }; var withTestingLibrary = (testingLibrary, fn) => fn(testingLibrary); /** * Sørger for at alle brukte custom elements fra Punkt er definerte og erstattet med implementasjoner. * Bør kalles etter `render`, før noen operasjoner og antakelser gjøres. * * F.eks.: * <code> * render(<div><PktTextinput id="textinput" name={'textInput'} label="My text input" aria-label="My text input" /></div>); * await waitForPktElementsToBeDefined(); * * await setPktElementValue("My text input", "new value"); * </code> */ var waitForPktElementsToBeDefined = async () => await Promise.all(PKT_CUSTOM_ELEMENTS.map((elementName) => { if (document.querySelector(elementName) !== null) return window.customElements.whenDefined(elementName); }).filter((promise) => promise)); var findPossibleWrappingPktCustomElement = (testingLibrary) => (innerElement, isByRole, args) => { const pktElement = innerElement?.closest(PKT_CUSTOM_FORMFIELDS.join(", ")); if (pktElement) return pktElement; else return innerElement; }; var getPktElementBy = (testingLibrary) => (query = "label", identifier, container) => { try { const innerElement = doElementSearch(identifier, query, syncQueryFunctionResolver(testingLibrary)(query), container)[0]; return findPossibleWrappingPktCustomElement(testingLibrary)(innerElement, false, []); } catch (e) { return fallbackSearchForPktSelectByLabel(testingLibrary)(identifier, query, container); } }; var removeElementBySelector = (ancestor, selector) => { Array.from(ancestor.querySelectorAll(selector)).forEach((element) => { element.parentNode?.removeChild(element); }); }; function getPureLabelText(label) { const clonedLabel = label.cloneNode(true); removeElementBySelector(clonedLabel, "pkt-helptext"); removeElementBySelector(clonedLabel, ".pkt-input-suffix"); removeElementBySelector(clonedLabel, ".pkt-input-prefix"); removeElementBySelector(clonedLabel, ".pkt-input-icon"); removeElementBySelector(clonedLabel, ".pkt-input__counter"); removeElementBySelector(clonedLabel, "option"); removeElementBySelector(clonedLabel, "pkt-listbox"); removeElementBySelector(clonedLabel, ".pkt-alert--error"); removeElementBySelector(clonedLabel, ".pkt-tag"); removeElementBySelector(clonedLabel, ".pkt-input-check__input-helptext"); removeElementBySelector(clonedLabel, ".pkt-inputwrapper__helptext"); return clonedLabel.textContent?.trim() || null; } var getPureLabelTextForLabelOwner = (labelOwner) => { const label = "labels" in labelOwner && labelOwner.labels instanceof NodeList && labelOwner.labels.length > 0 && labelOwner.labels[0] || ["input", ...PKT_CUSTOM_FORMFIELDS].includes(labelOwner.tagName.toLowerCase()) && labelOwner.querySelector("label") || null; if (label) return getPureLabelText(label); else return null; }; var labelMatcher = (labelTextToMatch) => (nodeContent, element) => { if (element instanceof HTMLElement) return getPureLabelTextForLabelOwner(element) === labelTextToMatch; else return false; }; var fallbackSearchForPktSelectByLabel = (testingLibrary) => (identifier, query, container = document.body) => { if (typeof identifier === "string" && query === "label") { const matchingLabel = Array.from(container.querySelectorAll("label")).find((labelElement) => getPureLabelText(labelElement) === identifier); const labelOwner = matchingLabel?.control || matchingLabel?.closest("pkt-select") || matchingLabel?.closest("pkt-combobox"); if (!labelOwner) throw getElementError(`Fant ikke noe element med label "${identifier}"`, container); return labelOwner; } throw getElementError(`Fant ikke noe element med ${query} "${identifier}"`, container); }; var doElementSearch = (identifier, query, queryFunc, container = document.body) => { return identifier instanceof HTMLLabelElement ? getByLabelElement(identifier) : typeof identifier === "string" && query === "label" ? queryFunc(container, labelMatcher(identifier.trim())) : queryFunc(container, identifier); }; var findPktElementBy = (testingLibrary) => async (query = "label", identifier, container) => { try { if (identifier instanceof HTMLLabelElement) return Promise.resolve(getByLabelElement(identifier)); const innerElement = (await doElementSearch(identifier, query, asyncQueryFunctionResolver(testingLibrary)(query), container))[0]; return Promise.resolve(findPossibleWrappingPktCustomElement(testingLibrary)(innerElement, false, [])); } catch (e) { return fallbackSearchForPktSelectByLabel(testingLibrary)(identifier, query, container); } }; var getPktElementByLabel = (testingLibrary) => (label, container) => label instanceof HTMLLabelElement ? getByLabelElement(label) : getPktElementBy(testingLibrary)("label", label, container); var findPktElementByLabel = (testingLibrary) => async (label, container) => label instanceof HTMLLabelElement ? getByLabelElement(label) : findPktElementBy(testingLibrary)("label", label, container); var setPktElementChecked = (testingLibrary) => asyncFuncWithStringIdentifierOrElement(testingLibrary)(async (element, checked) => { if (!("checked" in element)) throw new Error("Bare elementer som har en \"checked\"-attributt støttes"); let returnValue = false; if (element.tagName === "INPUT") { const htmlInputElement = element; if (htmlInputElement.type === "radio") if (!checked) throw new Error("Kan ikke av-velge en <input type='radio'> - prøv å velge en annen radioknapp i samme gruppe"); else { returnValue = testingLibrary.fireEvent.click(element); await new Promise((resolve) => setTimeout(resolve, 0)); } else if (htmlInputElement.type === "checkbox") { if (htmlInputElement.checked !== checked) returnValue = testingLibrary.fireEvent.click(element); } } else returnValue = testingLibrary.fireEvent.change(element, { target: { checked } }); return Promise.resolve(returnValue); }); var isPktElementChecked = (testingLibrary) => (target) => { const element = syncResolveIdentifierOrElement(testingLibrary)(target, "label"); if ("checked" in element) return !!element.checked; return element.querySelector("input[type=\"radio\"]:checked, input[type=\"checkbox\"]:checked") !== null; }; var getPktElementByDisplayValue = (testingLibrary) => (text, container) => getPktElementBy(testingLibrary)("displayValue", text, container); var findPktElementByDisplayValue = (testingLibrary) => (text, container) => findPktElementBy(testingLibrary)("displayValue", text, container); var getPktSelectOptions = (testingLibrary) => syncFuncWithStringIdentifierOrElement(testingLibrary)((selectElement, onlySelected) => { const optionElements = Array.from(selectElement.querySelectorAll("option:not(.pkt-hide), data:not(.pkt.hide)")); const filter = onlySelected ? ([, , selected]) => selected : (_) => true; const currentValue = selectElement.value; return optionElements.map((optionElement) => [ optionElement.value, optionElement.textContent?.trim(), optionElement.value === currentValue ]).filter(filter); }); var getAllPktElementsByLabelText = (testingLibrary) => (label, container) => { return testingLibrary.getAllByLabelText(container || document.body, label).map((element) => findPossibleWrappingPktCustomElement(testingLibrary)(element, false, [])); }; var setPktElementValue = (testingLibrary) => asyncFuncWithStringIdentifierOrElement(testingLibrary)(async (element, valueOrValues, optionsOrUseInputEvent = false) => { const options = typeof optionsOrUseInputEvent === "object" ? optionsOrUseInputEvent : optionsOrUseInputEvent && { useInputEventForNative: true, useChangeEventForNative: true, setValuePropForNative: true } || { useInputEventForNative: false, useChangeEventForNative: true, setValuePropForNative: false }; if (Array.isArray(valueOrValues) && valueOrValues.length > 1 && element.tagName === "PKT-SELECT") throw new Error("Multi-verdi <pkt-select> støttes ikke. Bruk <pkt-combobox> i stedet."); if (element.tagName === "PKT-SELECT" || element.tagName === "PKT-COMBOBOX") { const multiple = element.multiple; const valueAsArray = Array.isArray(valueOrValues) ? valueOrValues : [valueOrValues]; const newValue = multiple && valueAsArray || valueAsArray.length == 0 && "" || valueAsArray[0]; testingLibrary.fireEvent.change(element, { target: { value: newValue } }); return Promise.resolve(true); } else { if (options.setValuePropForNative) { if ("value" in element) element.value = valueOrValues.toString(); } if (options.useInputEventForNative) testingLibrary.fireEvent.input(element, { target: { value: valueOrValues } }); if (options.useChangeEventForNative) testingLibrary.fireEvent.change(element, { target: { value: valueOrValues } }); return Promise.resolve(true); } }); var containsRadioInput = (element) => { if (element.tagName === "INPUT" && element.type === "radio") return true; if (element.querySelector?.("input[type=\"radio\"]")) return true; if (element.tagName === "LABEL") { const label = element; const control = label.control || (label.htmlFor ? document.getElementById(label.htmlFor) : null); return !!(control && control.type === "radio"); } return false; }; var pktClickButton = (testingLibrary) => asyncFuncWithStringIdentifierOrElement(testingLibrary)(async (element) => { if (containsRadioInput(element)) throw new Error("Klikk på <pkt-radiobutton> støttes ikke - bruk setPktElementChecked i stedet"); return testingLibrary.fireEvent.click(element); }, "text"); var setPktSelectedOptionsByLabel = (testingLibrary) => asyncFuncWithStringIdentifierOrElement(testingLibrary)(async (pktSelect, ...desiredOptionLabels) => { const availableOptions = getPktSelectOptions(testingLibrary)(pktSelect); const selectedOptions = desiredOptionLabels.map((optionLabel) => availableOptions.find(([_, label]) => label === optionLabel)).filter((possibleOption) => possibleOption); if (selectedOptions.length !== desiredOptionLabels.length) throw new Error("Noen av option'ene finnes ikke i denne komponenten. Du valgte " + JSON.stringify(desiredOptionLabels) + ", mens valgmulighetene er " + JSON.stringify(availableOptions.map(([, label]) => label))); return await setPktElementValue(testingLibrary)(pktSelect, selectedOptions.map(([value]) => value)); }); //#endregion export { PKT_CUSTOM_FORMFIELDS, setupPktTestingLibrary }; //# sourceMappingURL=punkt-testing-utils.es.js.map