@najemi-software/vitest-dom 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,12 @@ var __export = (target, all) => {
4
4
  __defProp(target, name, { get: all[name], enumerable: true });
5
5
  };
6
6
 
7
+ // src/matchers/to-be-checked.js
8
+ import { createRequire } from "module";
9
+
7
10
  // src/matchers/utils.ts
11
+ import isEqual from "lodash-es/isEqual.js";
12
+ import isFunction from "lodash-es/isFunction.js";
8
13
  import redent from "redent";
9
14
 
10
15
  // src/matchers/css-parse.js
@@ -36,9 +41,7 @@ function cssParse(css, options) {
36
41
  Position.prototype.content = css;
37
42
  let errorsList = [];
38
43
  function error(msg) {
39
- let err = new Error(
40
- options.source + ":" + lineno + ":" + column + ": " + msg
41
- );
44
+ let err = new Error(options.source + ":" + lineno + ":" + column + ": " + msg);
42
45
  err.reason = msg;
43
46
  err.filename = options.source;
44
47
  err.line = lineno;
@@ -393,8 +396,6 @@ function addParent(obj, parent) {
393
396
  var css_parse_default = cssParse;
394
397
 
395
398
  // src/matchers/utils.ts
396
- import isEqual from "lodash-es/isEqual.js";
397
- import isFunction from "lodash-es/isFunction.js";
398
399
  var GenericTypeError = class extends Error {
399
400
  constructor(expectedString, received, matcherFn, context) {
400
401
  super();
@@ -404,23 +405,13 @@ var GenericTypeError = class extends Error {
404
405
  }
405
406
  let withType = "";
406
407
  try {
407
- withType = printWithType(
408
- "Received",
409
- received,
410
- context.utils.printReceived
411
- );
408
+ withType = printWithType("Received", received, context.utils.printReceived);
412
409
  } catch (e) {
413
410
  }
414
411
  this.message = [
415
- context.utils.matcherHint(
416
- `${context.isNot ? ".not" : ""}.${matcherFn.name}`,
417
- "received",
418
- ""
419
- ),
412
+ context.utils.matcherHint(`${context.isNot ? ".not" : ""}.${matcherFn.name}`, "received", ""),
420
413
  "",
421
- `${context.utils.RECEIVED_COLOR(
422
- "received"
423
- )} value must ${expectedString}.`,
414
+ `${context.utils.RECEIVED_COLOR("received")} value must ${expectedString}.`,
424
415
  withType
425
416
  ].join("\n");
426
417
  }
@@ -496,13 +487,9 @@ function getMessage(context, matcher, expectedLabel, expectedValue, receivedLabe
496
487
  `${matcher}
497
488
  `,
498
489
  `${expectedLabel}:
499
- ${context.utils.EXPECTED_COLOR(
500
- redent(display(context, expectedValue), 2)
501
- )}`,
490
+ ${context.utils.EXPECTED_COLOR(redent(display(context, expectedValue), 2))}`,
502
491
  `${receivedLabel}:
503
- ${context.utils.RECEIVED_COLOR(
504
- redent(display(context, receivedValue), 2)
505
- )}`
492
+ ${context.utils.RECEIVED_COLOR(redent(display(context, receivedValue), 2))}`
506
493
  ].join("\n");
507
494
  }
508
495
  function matches(textToMatch, matcher) {
@@ -611,62 +598,107 @@ function getType(value) {
611
598
  throw new Error(`value of unknown type: ${value}`);
612
599
  }
613
600
 
614
- // src/matchers/to-be-in-the-dom.js
615
- function toBeInTheDOM(element, container) {
616
- deprecate(
617
- "toBeInTheDOM",
618
- "Please use toBeInTheDocument for searching the entire document and toContainElement for searching a specific container."
619
- );
620
- if (element) {
621
- checkHtmlElement(element, toBeInTheDOM, this);
622
- }
623
- if (container) {
624
- checkHtmlElement(container, toBeInTheDOM, this);
601
+ // src/matchers/to-be-checked.js
602
+ var require2 = createRequire(import.meta.url);
603
+ var { roles } = require2("aria-query");
604
+ function toBeChecked(element) {
605
+ checkHtmlElement(element, toBeChecked, this);
606
+ const isValidInput = () => {
607
+ return element.tagName.toLowerCase() === "input" && ["checkbox", "radio"].includes(element.type);
608
+ };
609
+ const isValidAriaElement = () => {
610
+ return roleSupportsChecked(element.getAttribute("role")) && ["true", "false"].includes(element.getAttribute("aria-checked"));
611
+ };
612
+ if (!isValidInput() && !isValidAriaElement()) {
613
+ return {
614
+ pass: false,
615
+ message: () => `only inputs with type="checkbox" or type="radio" or elements with ${supportedRolesSentence()} and a valid aria-checked attribute can be used with .toBeChecked(). Use .toHaveValue() instead`
616
+ };
625
617
  }
618
+ const isChecked = () => {
619
+ if (isValidInput())
620
+ return element.checked;
621
+ return element.getAttribute("aria-checked") === "true";
622
+ };
626
623
  return {
627
- pass: container ? container.contains(element) : !!element,
624
+ pass: isChecked(),
628
625
  message: () => {
626
+ const is = isChecked() ? "is" : "is not";
629
627
  return [
630
- this.utils.matcherHint(
631
- `${this.isNot ? ".not" : ""}.toBeInTheDOM`,
632
- "element",
633
- ""
634
- ),
628
+ this.utils.matcherHint(`${this.isNot ? ".not" : ""}.toBeChecked`, "element", ""),
635
629
  "",
636
- "Received:",
637
- ` ${this.utils.printReceived(
638
- element ? element.cloneNode(false) : element
639
- )}`
630
+ `Received element ${is} checked:`,
631
+ ` ${this.utils.printReceived(element.cloneNode(false))}`
640
632
  ].join("\n");
641
633
  }
642
634
  };
643
635
  }
636
+ function supportedRolesSentence() {
637
+ return toSentence(
638
+ supportedRoles().map((role) => `role="${role}"`),
639
+ { lastWordConnector: " or " }
640
+ );
641
+ }
642
+ function supportedRoles() {
643
+ return roles.keys().filter(roleSupportsChecked);
644
+ }
645
+ function roleSupportsChecked(role) {
646
+ return roles.get(role)?.props["aria-checked"] !== void 0;
647
+ }
644
648
 
645
- // src/matchers/to-be-in-the-document.js
646
- function toBeInTheDocument(element) {
647
- if (element !== null || !this.isNot) {
648
- checkHtmlElement(element, toBeInTheDocument, this);
649
- }
650
- const pass = element === null ? false : element.ownerDocument === element.getRootNode({ composed: true });
651
- const errorFound = () => {
652
- return `expected document not to contain element, found ${this.utils.stringify(
653
- element.cloneNode(true)
654
- )} instead`;
655
- };
656
- const errorNotFound = () => {
657
- return `element could not be found in the document`;
649
+ // src/matchers/to-be-disabled.js
650
+ var FORM_TAGS = ["fieldset", "input", "select", "optgroup", "option", "button", "textarea"];
651
+ function isFirstLegendChildOfFieldset(element, parent) {
652
+ return getTag(element) === "legend" && getTag(parent) === "fieldset" && element.isSameNode(Array.from(parent.children).find((child) => getTag(child) === "legend"));
653
+ }
654
+ function isElementDisabledByParent(element, parent) {
655
+ return isElementDisabled(parent) && !isFirstLegendChildOfFieldset(element, parent);
656
+ }
657
+ function isCustomElement(tag) {
658
+ return tag.includes("-");
659
+ }
660
+ function canElementBeDisabled(element) {
661
+ const tag = getTag(element);
662
+ return FORM_TAGS.includes(tag) || isCustomElement(tag);
663
+ }
664
+ function isElementDisabled(element) {
665
+ return canElementBeDisabled(element) && element.hasAttribute("disabled");
666
+ }
667
+ function isAncestorDisabled(element) {
668
+ const parent = element.parentElement;
669
+ return Boolean(parent) && (isElementDisabledByParent(element, parent) || isAncestorDisabled(parent));
670
+ }
671
+ function isElementOrAncestorDisabled(element) {
672
+ return canElementBeDisabled(element) && (isElementDisabled(element) || isAncestorDisabled(element));
673
+ }
674
+ function toBeDisabled(element) {
675
+ checkHtmlElement(element, toBeDisabled, this);
676
+ const isDisabled = isElementOrAncestorDisabled(element);
677
+ return {
678
+ pass: isDisabled,
679
+ message: () => {
680
+ const is = isDisabled ? "is" : "is not";
681
+ return [
682
+ this.utils.matcherHint(`${this.isNot ? ".not" : ""}.toBeDisabled`, "element", ""),
683
+ "",
684
+ `Received element ${is} disabled:`,
685
+ ` ${this.utils.printReceived(element.cloneNode(false))}`
686
+ ].join("\n");
687
+ }
658
688
  };
689
+ }
690
+ function toBeEnabled(element) {
691
+ checkHtmlElement(element, toBeEnabled, this);
692
+ const isEnabled = !isElementOrAncestorDisabled(element);
659
693
  return {
660
- pass,
694
+ pass: isEnabled,
661
695
  message: () => {
696
+ const is = isEnabled ? "is" : "is not";
662
697
  return [
663
- this.utils.matcherHint(
664
- `${this.isNot ? ".not" : ""}.toBeInTheDocument`,
665
- "element",
666
- ""
667
- ),
698
+ this.utils.matcherHint(`${this.isNot ? ".not" : ""}.toBeEnabled`, "element", ""),
668
699
  "",
669
- this.utils.RECEIVED_COLOR(this.isNot ? errorFound() : errorNotFound())
700
+ `Received element ${is} enabled:`,
701
+ ` ${this.utils.printReceived(element.cloneNode(false))}`
670
702
  ].join("\n");
671
703
  }
672
704
  };
@@ -674,20 +706,13 @@ function toBeInTheDocument(element) {
674
706
 
675
707
  // src/matchers/to-be-empty.js
676
708
  function toBeEmpty(element) {
677
- deprecate(
678
- "toBeEmpty",
679
- "Please use instead toBeEmptyDOMElement for finding empty nodes in the DOM."
680
- );
709
+ deprecate("toBeEmpty", "Please use instead toBeEmptyDOMElement for finding empty nodes in the DOM.");
681
710
  checkHtmlElement(element, toBeEmpty, this);
682
711
  return {
683
712
  pass: element.innerHTML === "",
684
713
  message: () => {
685
714
  return [
686
- this.utils.matcherHint(
687
- `${this.isNot ? ".not" : ""}.toBeEmpty`,
688
- "element",
689
- ""
690
- ),
715
+ this.utils.matcherHint(`${this.isNot ? ".not" : ""}.toBeEmpty`, "element", ""),
691
716
  "",
692
717
  "Received:",
693
718
  ` ${this.utils.printReceived(element.innerHTML)}`
@@ -703,11 +728,7 @@ function toBeEmptyDOMElement(element) {
703
728
  pass: isEmptyElement(element),
704
729
  message: () => {
705
730
  return [
706
- this.utils.matcherHint(
707
- `${this.isNot ? ".not" : ""}.toBeEmptyDOMElement`,
708
- "element",
709
- ""
710
- ),
731
+ this.utils.matcherHint(`${this.isNot ? ".not" : ""}.toBeEmptyDOMElement`, "element", ""),
711
732
  "",
712
733
  "Received:",
713
734
  ` ${this.utils.printReceived(element.innerHTML)}`
@@ -716,111 +737,274 @@ function toBeEmptyDOMElement(element) {
716
737
  };
717
738
  }
718
739
  function isEmptyElement(element) {
719
- const nonCommentChildNodes = [...element.childNodes].filter(
720
- (node) => node.nodeType !== 8
721
- );
740
+ const nonCommentChildNodes = [...element.childNodes].filter((node) => node.nodeType !== 8);
722
741
  return nonCommentChildNodes.length === 0;
723
742
  }
724
743
 
725
- // src/matchers/to-contain-element.js
726
- function toContainElement(container, element) {
727
- checkHtmlElement(container, toContainElement, this);
728
- if (element !== null) {
729
- checkHtmlElement(element, toContainElement, this);
744
+ // src/matchers/to-be-in-the-document.js
745
+ function toBeInTheDocument(element) {
746
+ if (element !== null || !this.isNot) {
747
+ checkHtmlElement(element, toBeInTheDocument, this);
730
748
  }
749
+ const pass = element === null ? false : element.ownerDocument === element.getRootNode({ composed: true });
750
+ const errorFound = () => {
751
+ return `expected document not to contain element, found ${this.utils.stringify(
752
+ element.cloneNode(true)
753
+ )} instead`;
754
+ };
755
+ const errorNotFound = () => {
756
+ return `element could not be found in the document`;
757
+ };
731
758
  return {
732
- pass: container.contains(element),
759
+ pass,
733
760
  message: () => {
734
761
  return [
735
- this.utils.matcherHint(
736
- `${this.isNot ? ".not" : ""}.toContainElement`,
737
- "element",
738
- "element"
739
- ),
762
+ this.utils.matcherHint(`${this.isNot ? ".not" : ""}.toBeInTheDocument`, "element", ""),
740
763
  "",
741
- this.utils.RECEIVED_COLOR(`${this.utils.stringify(
742
- container.cloneNode(false)
743
- )} ${this.isNot ? "contains:" : "does not contain:"} ${this.utils.stringify(element ? element.cloneNode(false) : element)}
744
- `)
764
+ this.utils.RECEIVED_COLOR(this.isNot ? errorFound() : errorNotFound())
745
765
  ].join("\n");
746
766
  }
747
767
  };
748
768
  }
749
769
 
750
- // src/matchers/to-contain-html.js
751
- function getNormalizedHtml(container, htmlText) {
752
- const div = container.ownerDocument.createElement("div");
753
- div.innerHTML = htmlText;
754
- return div.innerHTML;
755
- }
756
- function toContainHTML(container, htmlText) {
757
- checkHtmlElement(container, toContainHTML, this);
758
- if (typeof htmlText !== "string") {
759
- throw new Error(`.toContainHTML() expects a string value, got ${htmlText}`);
770
+ // src/matchers/to-be-in-the-dom.js
771
+ function toBeInTheDOM(element, container) {
772
+ deprecate(
773
+ "toBeInTheDOM",
774
+ "Please use toBeInTheDocument for searching the entire document and toContainElement for searching a specific container."
775
+ );
776
+ if (element) {
777
+ checkHtmlElement(element, toBeInTheDOM, this);
778
+ }
779
+ if (container) {
780
+ checkHtmlElement(container, toBeInTheDOM, this);
760
781
  }
761
782
  return {
762
- pass: container.outerHTML.includes(getNormalizedHtml(container, htmlText)),
783
+ pass: container ? container.contains(element) : !!element,
763
784
  message: () => {
764
785
  return [
765
- this.utils.matcherHint(
766
- `${this.isNot ? ".not" : ""}.toContainHTML`,
767
- "element",
768
- ""
769
- ),
770
- "Expected:",
771
- ` ${this.utils.EXPECTED_COLOR(htmlText)}`,
786
+ this.utils.matcherHint(`${this.isNot ? ".not" : ""}.toBeInTheDOM`, "element", ""),
787
+ "",
772
788
  "Received:",
773
- ` ${this.utils.printReceived(container.cloneNode(true))}`
789
+ ` ${this.utils.printReceived(element ? element.cloneNode(false) : element)}`
774
790
  ].join("\n");
775
791
  }
776
792
  };
777
793
  }
778
794
 
779
- // src/matchers/to-have-text-content.js
780
- function toHaveTextContent(node, checkWith, options = { normalizeWhitespace: true }) {
781
- checkNode(node, toHaveTextContent, this);
782
- const textContent = options.normalizeWhitespace ? normalize(node.textContent) : node.textContent.replace(/\u00a0/g, " ");
783
- const checkingWithEmptyString = textContent !== "" && checkWith === "";
795
+ // src/matchers/to-be-invalid.js
796
+ var FORM_TAGS2 = ["form", "input", "select", "textarea"];
797
+ function isElementHavingAriaInvalid(element) {
798
+ return element.hasAttribute("aria-invalid") && element.getAttribute("aria-invalid") !== "false";
799
+ }
800
+ function isSupportsValidityMethod(element) {
801
+ return FORM_TAGS2.includes(getTag(element));
802
+ }
803
+ function isElementInvalid(element) {
804
+ const isHaveAriaInvalid = isElementHavingAriaInvalid(element);
805
+ if (isSupportsValidityMethod(element)) {
806
+ return isHaveAriaInvalid || !element.checkValidity();
807
+ } else {
808
+ return isHaveAriaInvalid;
809
+ }
810
+ }
811
+ function toBeInvalid(element) {
812
+ checkHtmlElement(element, toBeInvalid, this);
813
+ const isInvalid = isElementInvalid(element);
784
814
  return {
785
- pass: !checkingWithEmptyString && matches(textContent, checkWith),
815
+ pass: isInvalid,
786
816
  message: () => {
787
- const to = this.isNot ? "not to" : "to";
788
- return getMessage(
789
- this,
790
- this.utils.matcherHint(
791
- `${this.isNot ? ".not" : ""}.toHaveTextContent`,
792
- "element",
793
- ""
794
- ),
795
- checkingWithEmptyString ? `Checking with empty string will always match, use .toBeEmptyDOMElement() instead` : `Expected element ${to} have text content`,
796
- checkWith,
797
- "Received",
798
- textContent
799
- );
817
+ const is = isInvalid ? "is" : "is not";
818
+ return [
819
+ this.utils.matcherHint(`${this.isNot ? ".not" : ""}.toBeInvalid`, "element", ""),
820
+ "",
821
+ `Received element ${is} currently invalid:`,
822
+ ` ${this.utils.printReceived(element.cloneNode(false))}`
823
+ ].join("\n");
800
824
  }
801
825
  };
802
826
  }
803
-
804
- // src/matchers/to-have-accessible-description.js
805
- import { computeAccessibleDescription } from "dom-accessibility-api";
806
- function toHaveAccessibleDescription(htmlElement, expectedAccessibleDescription) {
807
- checkHtmlElement(htmlElement, toHaveAccessibleDescription, this);
808
- const actualAccessibleDescription = computeAccessibleDescription(htmlElement);
809
- const missingExpectedValue = arguments.length === 1;
810
- let pass = false;
811
- if (missingExpectedValue) {
812
- pass = actualAccessibleDescription !== "";
813
- } else {
814
- pass = expectedAccessibleDescription instanceof RegExp ? expectedAccessibleDescription.test(actualAccessibleDescription) : this.equals(
815
- actualAccessibleDescription,
816
- expectedAccessibleDescription
817
- );
818
- }
827
+ function toBeValid(element) {
828
+ checkHtmlElement(element, toBeValid, this);
829
+ const isValid = !isElementInvalid(element);
819
830
  return {
820
- pass,
831
+ pass: isValid,
821
832
  message: () => {
822
- const to = this.isNot ? "not to" : "to";
823
- return getMessage(
833
+ const is = isValid ? "is" : "is not";
834
+ return [
835
+ this.utils.matcherHint(`${this.isNot ? ".not" : ""}.toBeValid`, "element", ""),
836
+ "",
837
+ `Received element ${is} currently valid:`,
838
+ ` ${this.utils.printReceived(element.cloneNode(false))}`
839
+ ].join("\n");
840
+ }
841
+ };
842
+ }
843
+
844
+ // src/matchers/to-be-partially-checked.js
845
+ function toBePartiallyChecked(element) {
846
+ checkHtmlElement(element, toBePartiallyChecked, this);
847
+ const isValidInput = () => {
848
+ return element.tagName.toLowerCase() === "input" && element.type === "checkbox";
849
+ };
850
+ const isValidAriaElement = () => {
851
+ return element.getAttribute("role") === "checkbox";
852
+ };
853
+ if (!isValidInput() && !isValidAriaElement()) {
854
+ return {
855
+ pass: false,
856
+ message: () => 'only inputs with type="checkbox" or elements with role="checkbox" and a valid aria-checked attribute can be used with .toBePartiallyChecked(). Use .toHaveValue() instead'
857
+ };
858
+ }
859
+ const isPartiallyChecked = () => {
860
+ const isAriaMixed = element.getAttribute("aria-checked") === "mixed";
861
+ if (isValidInput()) {
862
+ return element.indeterminate || isAriaMixed;
863
+ }
864
+ return isAriaMixed;
865
+ };
866
+ return {
867
+ pass: isPartiallyChecked(),
868
+ message: () => {
869
+ const is = isPartiallyChecked() ? "is" : "is not";
870
+ return [
871
+ this.utils.matcherHint(`${this.isNot ? ".not" : ""}.toBePartiallyChecked`, "element", ""),
872
+ "",
873
+ `Received element ${is} partially checked:`,
874
+ ` ${this.utils.printReceived(element.cloneNode(false))}`
875
+ ].join("\n");
876
+ }
877
+ };
878
+ }
879
+
880
+ // src/matchers/to-be-required.js
881
+ var FORM_TAGS3 = ["select", "textarea"];
882
+ var ARIA_FORM_TAGS = ["input", "select", "textarea"];
883
+ var UNSUPPORTED_INPUT_TYPES = ["color", "hidden", "range", "submit", "image", "reset"];
884
+ var SUPPORTED_ARIA_ROLES = ["combobox", "gridcell", "radiogroup", "spinbutton", "tree"];
885
+ function isRequiredOnFormTagsExceptInput(element) {
886
+ return FORM_TAGS3.includes(getTag(element)) && element.hasAttribute("required");
887
+ }
888
+ function isRequiredOnSupportedInput(element) {
889
+ return getTag(element) === "input" && element.hasAttribute("required") && (element.hasAttribute("type") && !UNSUPPORTED_INPUT_TYPES.includes(element.getAttribute("type")) || !element.hasAttribute("type"));
890
+ }
891
+ function isElementRequiredByARIA(element) {
892
+ return element.hasAttribute("aria-required") && element.getAttribute("aria-required") === "true" && (ARIA_FORM_TAGS.includes(getTag(element)) || element.hasAttribute("role") && SUPPORTED_ARIA_ROLES.includes(element.getAttribute("role")));
893
+ }
894
+ function toBeRequired(element) {
895
+ checkHtmlElement(element, toBeRequired, this);
896
+ const isRequired = isRequiredOnFormTagsExceptInput(element) || isRequiredOnSupportedInput(element) || isElementRequiredByARIA(element);
897
+ return {
898
+ pass: isRequired,
899
+ message: () => {
900
+ const is = isRequired ? "is" : "is not";
901
+ return [
902
+ this.utils.matcherHint(`${this.isNot ? ".not" : ""}.toBeRequired`, "element", ""),
903
+ "",
904
+ `Received element ${is} required:`,
905
+ ` ${this.utils.printReceived(element.cloneNode(false))}`
906
+ ].join("\n");
907
+ }
908
+ };
909
+ }
910
+
911
+ // src/matchers/to-be-visible.js
912
+ function isStyleVisible(element) {
913
+ const { getComputedStyle } = element.ownerDocument.defaultView;
914
+ const { display: display2, visibility, opacity } = getComputedStyle(element);
915
+ return display2 !== "none" && visibility !== "hidden" && visibility !== "collapse" && opacity !== "0" && opacity !== 0;
916
+ }
917
+ function isAttributeVisible(element, previousElement) {
918
+ let detailsVisibility;
919
+ if (previousElement) {
920
+ detailsVisibility = element.nodeName === "DETAILS" && previousElement.nodeName !== "SUMMARY" ? element.hasAttribute("open") : true;
921
+ } else {
922
+ detailsVisibility = element.nodeName === "DETAILS" ? element.hasAttribute("open") : true;
923
+ }
924
+ return !element.hasAttribute("hidden") && detailsVisibility;
925
+ }
926
+ function isElementVisible(element, previousElement) {
927
+ return isStyleVisible(element) && isAttributeVisible(element, previousElement) && (!element.parentElement || isElementVisible(element.parentElement, element));
928
+ }
929
+ function toBeVisible(element) {
930
+ checkHtmlElement(element, toBeVisible, this);
931
+ const isInDocument = element.ownerDocument === element.getRootNode({ composed: true });
932
+ const isVisible = isInDocument && isElementVisible(element);
933
+ return {
934
+ pass: isVisible,
935
+ message: () => {
936
+ const is = isVisible ? "is" : "is not";
937
+ return [
938
+ this.utils.matcherHint(`${this.isNot ? ".not" : ""}.toBeVisible`, "element", ""),
939
+ "",
940
+ `Received element ${is} visible${isInDocument ? "" : " (element is not in the document)"}:`,
941
+ ` ${this.utils.printReceived(element.cloneNode(false))}`
942
+ ].join("\n");
943
+ }
944
+ };
945
+ }
946
+
947
+ // src/matchers/to-contain-element.js
948
+ function toContainElement(container, element) {
949
+ checkHtmlElement(container, toContainElement, this);
950
+ if (element !== null) {
951
+ checkHtmlElement(element, toContainElement, this);
952
+ }
953
+ return {
954
+ pass: container.contains(element),
955
+ message: () => {
956
+ return [
957
+ this.utils.matcherHint(`${this.isNot ? ".not" : ""}.toContainElement`, "element", "element"),
958
+ "",
959
+ this.utils.RECEIVED_COLOR(`${this.utils.stringify(container.cloneNode(false))} ${this.isNot ? "contains:" : "does not contain:"} ${this.utils.stringify(element ? element.cloneNode(false) : element)}
960
+ `)
961
+ ].join("\n");
962
+ }
963
+ };
964
+ }
965
+
966
+ // src/matchers/to-contain-html.js
967
+ function getNormalizedHtml(container, htmlText) {
968
+ const div = container.ownerDocument.createElement("div");
969
+ div.innerHTML = htmlText;
970
+ return div.innerHTML;
971
+ }
972
+ function toContainHTML(container, htmlText) {
973
+ checkHtmlElement(container, toContainHTML, this);
974
+ if (typeof htmlText !== "string") {
975
+ throw new Error(`.toContainHTML() expects a string value, got ${htmlText}`);
976
+ }
977
+ return {
978
+ pass: container.outerHTML.includes(getNormalizedHtml(container, htmlText)),
979
+ message: () => {
980
+ return [
981
+ this.utils.matcherHint(`${this.isNot ? ".not" : ""}.toContainHTML`, "element", ""),
982
+ "Expected:",
983
+ ` ${this.utils.EXPECTED_COLOR(htmlText)}`,
984
+ "Received:",
985
+ ` ${this.utils.printReceived(container.cloneNode(true))}`
986
+ ].join("\n");
987
+ }
988
+ };
989
+ }
990
+
991
+ // src/matchers/to-have-accessible-description.js
992
+ import { computeAccessibleDescription } from "dom-accessibility-api";
993
+ function toHaveAccessibleDescription(htmlElement, expectedAccessibleDescription) {
994
+ checkHtmlElement(htmlElement, toHaveAccessibleDescription, this);
995
+ const actualAccessibleDescription = computeAccessibleDescription(htmlElement);
996
+ const missingExpectedValue = arguments.length === 1;
997
+ let pass = false;
998
+ if (missingExpectedValue) {
999
+ pass = actualAccessibleDescription !== "";
1000
+ } else {
1001
+ pass = expectedAccessibleDescription instanceof RegExp ? expectedAccessibleDescription.test(actualAccessibleDescription) : this.equals(actualAccessibleDescription, expectedAccessibleDescription);
1002
+ }
1003
+ return {
1004
+ pass,
1005
+ message: () => {
1006
+ const to = this.isNot ? "not to" : "to";
1007
+ return getMessage(
824
1008
  this,
825
1009
  this.utils.matcherHint(
826
1010
  `${this.isNot ? ".not" : ""}.${toHaveAccessibleDescription.name}`,
@@ -891,11 +1075,7 @@ function toHaveAttribute(htmlElement, name, expectedValue) {
891
1075
  this.utils.printExpected(name),
892
1076
  {
893
1077
  secondArgument: isExpectedValuePresent ? this.utils.printExpected(expectedValue) : void 0,
894
- comment: getAttributeComment(
895
- this.utils.stringify,
896
- name,
897
- expectedValue
898
- )
1078
+ comment: getAttributeComment(this.utils.stringify, name, expectedValue)
899
1079
  }
900
1080
  );
901
1081
  return getMessage(
@@ -993,587 +1173,76 @@ function toHaveClass(htmlElement, ...params) {
993
1173
  };
994
1174
  }
995
1175
 
996
- // src/matchers/to-have-style.js
997
- import chalk from "chalk";
998
- function getStyleDeclaration(document, css) {
999
- const styles = {};
1000
- const copy = document.createElement("div");
1001
- Object.keys(css).forEach((property) => {
1002
- copy.style[property] = css[property];
1003
- styles[property] = copy.style[property];
1004
- });
1005
- return styles;
1006
- }
1007
- function isSubset2(styles, computedStyle) {
1008
- return !!Object.keys(styles).length && Object.entries(styles).every(
1009
- ([prop, value]) => computedStyle[prop] === value || computedStyle.getPropertyValue(prop.toLowerCase()) === value
1010
- );
1011
- }
1012
- function printoutStyles(styles) {
1013
- return Object.keys(styles).sort().map((prop) => `${prop}: ${styles[prop]};`).join("\n");
1014
- }
1015
- function expectedDiff(diffFn, expected, computedStyles) {
1016
- const received = Array.from(computedStyles).filter((prop) => expected[prop] !== void 0).reduce(
1017
- (obj, prop) => Object.assign(obj, { [prop]: computedStyles.getPropertyValue(prop) }),
1018
- {}
1019
- );
1020
- const diffOutput = diffFn(printoutStyles(expected), printoutStyles(received));
1021
- return diffOutput.replace(`${chalk.red("+ Received")}
1022
- `, "");
1023
- }
1024
- function toHaveStyle(htmlElement, css) {
1025
- checkHtmlElement(htmlElement, toHaveStyle, this);
1026
- const parsedCSS = typeof css === "object" ? css : parseCSS(css, toHaveStyle, this);
1027
- const { getComputedStyle } = htmlElement.ownerDocument.defaultView;
1028
- const expected = getStyleDeclaration(htmlElement.ownerDocument, parsedCSS);
1029
- const received = getComputedStyle(htmlElement);
1030
- return {
1031
- pass: isSubset2(expected, received),
1032
- message: () => {
1033
- const matcher = `${this.isNot ? ".not" : ""}.toHaveStyle`;
1034
- return [
1035
- this.utils.matcherHint(matcher, "element", ""),
1036
- expectedDiff(this.utils.diff, expected, received)
1037
- ].join("\n\n");
1038
- }
1039
- };
1040
- }
1041
-
1042
- // src/matchers/to-have-focus.js
1043
- function toHaveFocus(element) {
1044
- checkHtmlElement(element, toHaveFocus, this);
1176
+ // src/matchers/to-have-description.js
1177
+ function toHaveDescription(htmlElement, checkWith) {
1178
+ deprecate("toHaveDescription", "Please use toHaveAccessibleDescription.");
1179
+ checkHtmlElement(htmlElement, toHaveDescription, this);
1180
+ const expectsDescription = checkWith !== void 0;
1181
+ const descriptionIDRaw = htmlElement.getAttribute("aria-describedby") || "";
1182
+ const descriptionIDs = descriptionIDRaw.split(/\s+/).filter(Boolean);
1183
+ let description = "";
1184
+ if (descriptionIDs.length > 0) {
1185
+ const document = htmlElement.ownerDocument;
1186
+ const descriptionEls = descriptionIDs.map((descriptionID) => document.getElementById(descriptionID)).filter(Boolean);
1187
+ description = normalize(descriptionEls.map((el) => el.textContent).join(" "));
1188
+ }
1045
1189
  return {
1046
- pass: element.ownerDocument.activeElement === element,
1190
+ pass: expectsDescription ? checkWith instanceof RegExp ? checkWith.test(description) : this.equals(description, checkWith) : Boolean(description),
1047
1191
  message: () => {
1048
- return [
1049
- this.utils.matcherHint(
1050
- `${this.isNot ? ".not" : ""}.toHaveFocus`,
1051
- "element",
1052
- ""
1053
- ),
1054
- "",
1055
- ...this.isNot ? [
1056
- "Received element is focused:",
1057
- ` ${this.utils.printReceived(element)}`
1058
- ] : [
1059
- "Expected element with focus:",
1060
- ` ${this.utils.printExpected(element)}`,
1061
- "Received element with focus:",
1062
- ` ${this.utils.printReceived(
1063
- element.ownerDocument.activeElement
1064
- )}`
1065
- ]
1066
- ].join("\n");
1192
+ const to = this.isNot ? "not to" : "to";
1193
+ return getMessage(
1194
+ this,
1195
+ this.utils.matcherHint(`${this.isNot ? ".not" : ""}.toHaveDescription`, "element", ""),
1196
+ `Expected the element ${to} have description`,
1197
+ this.utils.printExpected(checkWith),
1198
+ "Received",
1199
+ this.utils.printReceived(description)
1200
+ );
1067
1201
  }
1068
1202
  };
1069
1203
  }
1070
1204
 
1071
- // src/matchers/to-have-form-values.ts
1072
- import escape from "css.escape";
1073
- import { isEqualWith, uniq } from "lodash-es";
1074
- function getMultiElementValue(elements) {
1075
- const types = uniq(elements.map((element) => element.type));
1076
- if (types.length !== 1) {
1205
+ // src/matchers/to-have-display-value.js
1206
+ function toHaveDisplayValue(htmlElement, expectedValue) {
1207
+ checkHtmlElement(htmlElement, toHaveDisplayValue, this);
1208
+ const tagName = htmlElement.tagName.toLowerCase();
1209
+ if (!["select", "input", "textarea"].includes(tagName)) {
1077
1210
  throw new Error(
1078
- "Multiple form elements with the same name must be of the same type"
1079
- );
1080
- }
1081
- switch (types[0]) {
1082
- case "radio": {
1083
- const theChosenOne = elements.find((radio) => radio.checked);
1084
- return theChosenOne ? theChosenOne.value : void 0;
1085
- }
1086
- case "checkbox":
1087
- return elements.filter((checkbox) => checkbox.checked).map((checkbox) => checkbox.value);
1088
- default:
1089
- return elements.map((element) => element.value);
1090
- }
1091
- }
1092
- function getFormValue(container, name) {
1093
- container.elements;
1094
- const elements = [
1095
- ...container.querySelectorAll(`[name="${escape(name)}"]`)
1096
- ];
1097
- if (elements.length === 0) {
1098
- return void 0;
1099
- }
1100
- switch (elements.length) {
1101
- case 1:
1102
- return getSingleElementValue(elements[0]);
1103
- default:
1104
- return getMultiElementValue(elements);
1105
- }
1106
- }
1107
- function getPureName(name) {
1108
- return /\[\]$/.test(name) ? name.slice(0, -2) : name;
1109
- }
1110
- function getAllFormValues(container) {
1111
- const names = Array.from(container.elements).map(
1112
- (element) => element.name
1113
- );
1114
- return names.reduce(
1115
- (obj, name) => ({
1116
- ...obj,
1117
- [getPureName(name)]: getFormValue(container, name)
1118
- }),
1119
- {}
1120
- );
1121
- }
1122
- function toHaveFormValues(formElement, expectedValues) {
1123
- checkHtmlElement(formElement, toHaveFormValues, this);
1124
- if (!formElement.elements) {
1125
- throw new Error("toHaveFormValues must be called on a form or a fieldset");
1126
- }
1127
- const formValues = getAllFormValues(formElement);
1128
- return {
1129
- pass: Object.entries(expectedValues).every(
1130
- ([name, expectedValue]) => isEqualWith(formValues[name], expectedValue, compareArraysAsSet)
1131
- ),
1132
- message: () => {
1133
- const to = this.isNot ? "not to" : "to";
1134
- const matcher = `${this.isNot ? ".not" : ""}.toHaveFormValues`;
1135
- const commonKeyValues = Object.keys(formValues).filter((key) => expectedValues.hasOwnProperty(key)).reduce((obj, key) => ({ ...obj, [key]: formValues[key] }), {});
1136
- return [
1137
- this.utils.matcherHint(matcher, "element", ""),
1138
- `Expected the element ${to} have form values`,
1139
- this.utils.diff(expectedValues, commonKeyValues)
1140
- ].join("\n\n");
1141
- }
1142
- };
1143
- }
1144
-
1145
- // src/matchers/to-be-visible.js
1146
- function isStyleVisible(element) {
1147
- const { getComputedStyle } = element.ownerDocument.defaultView;
1148
- const { display: display2, visibility, opacity } = getComputedStyle(element);
1149
- return display2 !== "none" && visibility !== "hidden" && visibility !== "collapse" && opacity !== "0" && opacity !== 0;
1150
- }
1151
- function isAttributeVisible(element, previousElement) {
1152
- let detailsVisibility;
1153
- if (previousElement) {
1154
- detailsVisibility = element.nodeName === "DETAILS" && previousElement.nodeName !== "SUMMARY" ? element.hasAttribute("open") : true;
1155
- } else {
1156
- detailsVisibility = element.nodeName === "DETAILS" ? element.hasAttribute("open") : true;
1157
- }
1158
- return !element.hasAttribute("hidden") && detailsVisibility;
1159
- }
1160
- function isElementVisible(element, previousElement) {
1161
- return isStyleVisible(element) && isAttributeVisible(element, previousElement) && (!element.parentElement || isElementVisible(element.parentElement, element));
1162
- }
1163
- function toBeVisible(element) {
1164
- checkHtmlElement(element, toBeVisible, this);
1165
- const isInDocument = element.ownerDocument === element.getRootNode({ composed: true });
1166
- const isVisible = isInDocument && isElementVisible(element);
1167
- return {
1168
- pass: isVisible,
1169
- message: () => {
1170
- const is = isVisible ? "is" : "is not";
1171
- return [
1172
- this.utils.matcherHint(
1173
- `${this.isNot ? ".not" : ""}.toBeVisible`,
1174
- "element",
1175
- ""
1176
- ),
1177
- "",
1178
- `Received element ${is} visible${isInDocument ? "" : " (element is not in the document)"}:`,
1179
- ` ${this.utils.printReceived(element.cloneNode(false))}`
1180
- ].join("\n");
1181
- }
1182
- };
1183
- }
1184
-
1185
- // src/matchers/to-be-disabled.js
1186
- var FORM_TAGS = [
1187
- "fieldset",
1188
- "input",
1189
- "select",
1190
- "optgroup",
1191
- "option",
1192
- "button",
1193
- "textarea"
1194
- ];
1195
- function isFirstLegendChildOfFieldset(element, parent) {
1196
- return getTag(element) === "legend" && getTag(parent) === "fieldset" && element.isSameNode(
1197
- Array.from(parent.children).find((child) => getTag(child) === "legend")
1198
- );
1199
- }
1200
- function isElementDisabledByParent(element, parent) {
1201
- return isElementDisabled(parent) && !isFirstLegendChildOfFieldset(element, parent);
1202
- }
1203
- function isCustomElement(tag) {
1204
- return tag.includes("-");
1205
- }
1206
- function canElementBeDisabled(element) {
1207
- const tag = getTag(element);
1208
- return FORM_TAGS.includes(tag) || isCustomElement(tag);
1209
- }
1210
- function isElementDisabled(element) {
1211
- return canElementBeDisabled(element) && element.hasAttribute("disabled");
1212
- }
1213
- function isAncestorDisabled(element) {
1214
- const parent = element.parentElement;
1215
- return Boolean(parent) && (isElementDisabledByParent(element, parent) || isAncestorDisabled(parent));
1216
- }
1217
- function isElementOrAncestorDisabled(element) {
1218
- return canElementBeDisabled(element) && (isElementDisabled(element) || isAncestorDisabled(element));
1219
- }
1220
- function toBeDisabled(element) {
1221
- checkHtmlElement(element, toBeDisabled, this);
1222
- const isDisabled = isElementOrAncestorDisabled(element);
1223
- return {
1224
- pass: isDisabled,
1225
- message: () => {
1226
- const is = isDisabled ? "is" : "is not";
1227
- return [
1228
- this.utils.matcherHint(
1229
- `${this.isNot ? ".not" : ""}.toBeDisabled`,
1230
- "element",
1231
- ""
1232
- ),
1233
- "",
1234
- `Received element ${is} disabled:`,
1235
- ` ${this.utils.printReceived(element.cloneNode(false))}`
1236
- ].join("\n");
1237
- }
1238
- };
1239
- }
1240
- function toBeEnabled(element) {
1241
- checkHtmlElement(element, toBeEnabled, this);
1242
- const isEnabled = !isElementOrAncestorDisabled(element);
1243
- return {
1244
- pass: isEnabled,
1245
- message: () => {
1246
- const is = isEnabled ? "is" : "is not";
1247
- return [
1248
- this.utils.matcherHint(
1249
- `${this.isNot ? ".not" : ""}.toBeEnabled`,
1250
- "element",
1251
- ""
1252
- ),
1253
- "",
1254
- `Received element ${is} enabled:`,
1255
- ` ${this.utils.printReceived(element.cloneNode(false))}`
1256
- ].join("\n");
1257
- }
1258
- };
1259
- }
1260
-
1261
- // src/matchers/to-be-required.js
1262
- var FORM_TAGS2 = ["select", "textarea"];
1263
- var ARIA_FORM_TAGS = ["input", "select", "textarea"];
1264
- var UNSUPPORTED_INPUT_TYPES = [
1265
- "color",
1266
- "hidden",
1267
- "range",
1268
- "submit",
1269
- "image",
1270
- "reset"
1271
- ];
1272
- var SUPPORTED_ARIA_ROLES = [
1273
- "combobox",
1274
- "gridcell",
1275
- "radiogroup",
1276
- "spinbutton",
1277
- "tree"
1278
- ];
1279
- function isRequiredOnFormTagsExceptInput(element) {
1280
- return FORM_TAGS2.includes(getTag(element)) && element.hasAttribute("required");
1281
- }
1282
- function isRequiredOnSupportedInput(element) {
1283
- return getTag(element) === "input" && element.hasAttribute("required") && (element.hasAttribute("type") && !UNSUPPORTED_INPUT_TYPES.includes(element.getAttribute("type")) || !element.hasAttribute("type"));
1284
- }
1285
- function isElementRequiredByARIA(element) {
1286
- return element.hasAttribute("aria-required") && element.getAttribute("aria-required") === "true" && (ARIA_FORM_TAGS.includes(getTag(element)) || element.hasAttribute("role") && SUPPORTED_ARIA_ROLES.includes(element.getAttribute("role")));
1287
- }
1288
- function toBeRequired(element) {
1289
- checkHtmlElement(element, toBeRequired, this);
1290
- const isRequired = isRequiredOnFormTagsExceptInput(element) || isRequiredOnSupportedInput(element) || isElementRequiredByARIA(element);
1291
- return {
1292
- pass: isRequired,
1293
- message: () => {
1294
- const is = isRequired ? "is" : "is not";
1295
- return [
1296
- this.utils.matcherHint(
1297
- `${this.isNot ? ".not" : ""}.toBeRequired`,
1298
- "element",
1299
- ""
1300
- ),
1301
- "",
1302
- `Received element ${is} required:`,
1303
- ` ${this.utils.printReceived(element.cloneNode(false))}`
1304
- ].join("\n");
1305
- }
1306
- };
1307
- }
1308
-
1309
- // src/matchers/to-be-invalid.js
1310
- var FORM_TAGS3 = ["form", "input", "select", "textarea"];
1311
- function isElementHavingAriaInvalid(element) {
1312
- return element.hasAttribute("aria-invalid") && element.getAttribute("aria-invalid") !== "false";
1313
- }
1314
- function isSupportsValidityMethod(element) {
1315
- return FORM_TAGS3.includes(getTag(element));
1316
- }
1317
- function isElementInvalid(element) {
1318
- const isHaveAriaInvalid = isElementHavingAriaInvalid(element);
1319
- if (isSupportsValidityMethod(element)) {
1320
- return isHaveAriaInvalid || !element.checkValidity();
1321
- } else {
1322
- return isHaveAriaInvalid;
1323
- }
1324
- }
1325
- function toBeInvalid(element) {
1326
- checkHtmlElement(element, toBeInvalid, this);
1327
- const isInvalid = isElementInvalid(element);
1328
- return {
1329
- pass: isInvalid,
1330
- message: () => {
1331
- const is = isInvalid ? "is" : "is not";
1332
- return [
1333
- this.utils.matcherHint(
1334
- `${this.isNot ? ".not" : ""}.toBeInvalid`,
1335
- "element",
1336
- ""
1337
- ),
1338
- "",
1339
- `Received element ${is} currently invalid:`,
1340
- ` ${this.utils.printReceived(element.cloneNode(false))}`
1341
- ].join("\n");
1342
- }
1343
- };
1344
- }
1345
- function toBeValid(element) {
1346
- checkHtmlElement(element, toBeValid, this);
1347
- const isValid = !isElementInvalid(element);
1348
- return {
1349
- pass: isValid,
1350
- message: () => {
1351
- const is = isValid ? "is" : "is not";
1352
- return [
1353
- this.utils.matcherHint(
1354
- `${this.isNot ? ".not" : ""}.toBeValid`,
1355
- "element",
1356
- ""
1357
- ),
1358
- "",
1359
- `Received element ${is} currently valid:`,
1360
- ` ${this.utils.printReceived(element.cloneNode(false))}`
1361
- ].join("\n");
1362
- }
1363
- };
1364
- }
1365
-
1366
- // src/matchers/to-have-value.js
1367
- import { isEqualWith as isEqualWith2 } from "lodash-es";
1368
- function toHaveValue(htmlElement, expectedValue) {
1369
- checkHtmlElement(htmlElement, toHaveValue, this);
1370
- if (htmlElement.tagName.toLowerCase() === "input" && ["checkbox", "radio"].includes(htmlElement.type)) {
1371
- throw new Error(
1372
- "input with type=checkbox or type=radio cannot be used with .toHaveValue(). Use .toBeChecked() for type=checkbox or .toHaveFormValues() instead"
1373
- );
1374
- }
1375
- const receivedValue = getSingleElementValue(htmlElement);
1376
- const expectsValue = expectedValue !== void 0;
1377
- let expectedTypedValue = expectedValue;
1378
- let receivedTypedValue = receivedValue;
1379
- if (expectedValue == receivedValue && expectedValue !== receivedValue) {
1380
- expectedTypedValue = `${expectedValue} (${typeof expectedValue})`;
1381
- receivedTypedValue = `${receivedValue} (${typeof receivedValue})`;
1382
- }
1383
- return {
1384
- pass: expectsValue ? isEqualWith2(receivedValue, expectedValue, compareArraysAsSet) : Boolean(receivedValue),
1385
- message: () => {
1386
- const to = this.isNot ? "not to" : "to";
1387
- const matcher = this.utils.matcherHint(
1388
- `${this.isNot ? ".not" : ""}.toHaveValue`,
1389
- "element",
1390
- expectedValue
1391
- );
1392
- return getMessage(
1393
- this,
1394
- matcher,
1395
- `Expected the element ${to} have value`,
1396
- expectsValue ? expectedTypedValue : "(any)",
1397
- "Received",
1398
- receivedTypedValue
1399
- );
1400
- }
1401
- };
1402
- }
1403
-
1404
- // src/matchers/to-have-display-value.js
1405
- function toHaveDisplayValue(htmlElement, expectedValue) {
1406
- checkHtmlElement(htmlElement, toHaveDisplayValue, this);
1407
- const tagName = htmlElement.tagName.toLowerCase();
1408
- if (!["select", "input", "textarea"].includes(tagName)) {
1409
- throw new Error(
1410
- ".toHaveDisplayValue() currently supports only input, textarea or select elements, try with another matcher instead."
1211
+ ".toHaveDisplayValue() currently supports only input, textarea or select elements, try with another matcher instead."
1411
1212
  );
1412
1213
  }
1413
1214
  if (tagName === "input" && ["radio", "checkbox"].includes(htmlElement.type)) {
1414
1215
  throw new Error(
1415
- `.toHaveDisplayValue() currently does not support input[type="${htmlElement.type}"], try with another matcher instead.`
1416
- );
1417
- }
1418
- const values = getValues(tagName, htmlElement);
1419
- const expectedValues = getExpectedValues(expectedValue);
1420
- const numberOfMatchesWithValues = expectedValues.filter(
1421
- (expected) => values.some(
1422
- (value) => expected instanceof RegExp ? expected.test(value) : this.equals(value, String(expected))
1423
- )
1424
- ).length;
1425
- const matchedWithAllValues = numberOfMatchesWithValues === values.length;
1426
- const matchedWithAllExpectedValues = numberOfMatchesWithValues === expectedValues.length;
1427
- return {
1428
- pass: matchedWithAllValues && matchedWithAllExpectedValues,
1429
- message: () => getMessage(
1430
- this,
1431
- this.utils.matcherHint(
1432
- `${this.isNot ? ".not" : ""}.toHaveDisplayValue`,
1433
- "element",
1434
- ""
1435
- ),
1436
- `Expected element ${this.isNot ? "not " : ""}to have display value`,
1437
- expectedValue,
1438
- "Received",
1439
- values
1440
- )
1441
- };
1442
- }
1443
- function getValues(tagName, htmlElement) {
1444
- return tagName === "select" ? Array.from(htmlElement).filter((option) => option.selected).map((option) => option.textContent) : [htmlElement.value];
1445
- }
1446
- function getExpectedValues(expectedValue) {
1447
- return expectedValue instanceof Array ? expectedValue : [expectedValue];
1448
- }
1449
-
1450
- // src/matchers/to-be-checked.js
1451
- import { createRequire } from "module";
1452
- var require2 = createRequire(import.meta.url);
1453
- var { roles } = require2("aria-query");
1454
- function toBeChecked(element) {
1455
- checkHtmlElement(element, toBeChecked, this);
1456
- const isValidInput = () => {
1457
- return element.tagName.toLowerCase() === "input" && ["checkbox", "radio"].includes(element.type);
1458
- };
1459
- const isValidAriaElement = () => {
1460
- return roleSupportsChecked(element.getAttribute("role")) && ["true", "false"].includes(element.getAttribute("aria-checked"));
1461
- };
1462
- if (!isValidInput() && !isValidAriaElement()) {
1463
- return {
1464
- pass: false,
1465
- message: () => `only inputs with type="checkbox" or type="radio" or elements with ${supportedRolesSentence()} and a valid aria-checked attribute can be used with .toBeChecked(). Use .toHaveValue() instead`
1466
- };
1467
- }
1468
- const isChecked = () => {
1469
- if (isValidInput())
1470
- return element.checked;
1471
- return element.getAttribute("aria-checked") === "true";
1472
- };
1473
- return {
1474
- pass: isChecked(),
1475
- message: () => {
1476
- const is = isChecked() ? "is" : "is not";
1477
- return [
1478
- this.utils.matcherHint(
1479
- `${this.isNot ? ".not" : ""}.toBeChecked`,
1480
- "element",
1481
- ""
1482
- ),
1483
- "",
1484
- `Received element ${is} checked:`,
1485
- ` ${this.utils.printReceived(element.cloneNode(false))}`
1486
- ].join("\n");
1487
- }
1488
- };
1489
- }
1490
- function supportedRolesSentence() {
1491
- return toSentence(
1492
- supportedRoles().map((role) => `role="${role}"`),
1493
- { lastWordConnector: " or " }
1494
- );
1495
- }
1496
- function supportedRoles() {
1497
- return roles.keys().filter(roleSupportsChecked);
1498
- }
1499
- function roleSupportsChecked(role) {
1500
- return roles.get(role)?.props["aria-checked"] !== void 0;
1501
- }
1502
-
1503
- // src/matchers/to-be-partially-checked.js
1504
- function toBePartiallyChecked(element) {
1505
- checkHtmlElement(element, toBePartiallyChecked, this);
1506
- const isValidInput = () => {
1507
- return element.tagName.toLowerCase() === "input" && element.type === "checkbox";
1508
- };
1509
- const isValidAriaElement = () => {
1510
- return element.getAttribute("role") === "checkbox";
1511
- };
1512
- if (!isValidInput() && !isValidAriaElement()) {
1513
- return {
1514
- pass: false,
1515
- message: () => 'only inputs with type="checkbox" or elements with role="checkbox" and a valid aria-checked attribute can be used with .toBePartiallyChecked(). Use .toHaveValue() instead'
1516
- };
1517
- }
1518
- const isPartiallyChecked = () => {
1519
- const isAriaMixed = element.getAttribute("aria-checked") === "mixed";
1520
- if (isValidInput()) {
1521
- return element.indeterminate || isAriaMixed;
1522
- }
1523
- return isAriaMixed;
1524
- };
1525
- return {
1526
- pass: isPartiallyChecked(),
1527
- message: () => {
1528
- const is = isPartiallyChecked() ? "is" : "is not";
1529
- return [
1530
- this.utils.matcherHint(
1531
- `${this.isNot ? ".not" : ""}.toBePartiallyChecked`,
1532
- "element",
1533
- ""
1534
- ),
1535
- "",
1536
- `Received element ${is} partially checked:`,
1537
- ` ${this.utils.printReceived(element.cloneNode(false))}`
1538
- ].join("\n");
1539
- }
1540
- };
1541
- }
1542
-
1543
- // src/matchers/to-have-description.js
1544
- function toHaveDescription(htmlElement, checkWith) {
1545
- deprecate("toHaveDescription", "Please use toHaveAccessibleDescription.");
1546
- checkHtmlElement(htmlElement, toHaveDescription, this);
1547
- const expectsDescription = checkWith !== void 0;
1548
- const descriptionIDRaw = htmlElement.getAttribute("aria-describedby") || "";
1549
- const descriptionIDs = descriptionIDRaw.split(/\s+/).filter(Boolean);
1550
- let description = "";
1551
- if (descriptionIDs.length > 0) {
1552
- const document = htmlElement.ownerDocument;
1553
- const descriptionEls = descriptionIDs.map((descriptionID) => document.getElementById(descriptionID)).filter(Boolean);
1554
- description = normalize(
1555
- descriptionEls.map((el) => el.textContent).join(" ")
1216
+ `.toHaveDisplayValue() currently does not support input[type="${htmlElement.type}"], try with another matcher instead.`
1556
1217
  );
1557
1218
  }
1219
+ const values = getValues(tagName, htmlElement);
1220
+ const expectedValues = getExpectedValues(expectedValue);
1221
+ const numberOfMatchesWithValues = expectedValues.filter(
1222
+ (expected) => values.some(
1223
+ (value) => expected instanceof RegExp ? expected.test(value) : this.equals(value, String(expected))
1224
+ )
1225
+ ).length;
1226
+ const matchedWithAllValues = numberOfMatchesWithValues === values.length;
1227
+ const matchedWithAllExpectedValues = numberOfMatchesWithValues === expectedValues.length;
1558
1228
  return {
1559
- pass: expectsDescription ? checkWith instanceof RegExp ? checkWith.test(description) : this.equals(description, checkWith) : Boolean(description),
1560
- message: () => {
1561
- const to = this.isNot ? "not to" : "to";
1562
- return getMessage(
1563
- this,
1564
- this.utils.matcherHint(
1565
- `${this.isNot ? ".not" : ""}.toHaveDescription`,
1566
- "element",
1567
- ""
1568
- ),
1569
- `Expected the element ${to} have description`,
1570
- this.utils.printExpected(checkWith),
1571
- "Received",
1572
- this.utils.printReceived(description)
1573
- );
1574
- }
1229
+ pass: matchedWithAllValues && matchedWithAllExpectedValues,
1230
+ message: () => getMessage(
1231
+ this,
1232
+ this.utils.matcherHint(`${this.isNot ? ".not" : ""}.toHaveDisplayValue`, "element", ""),
1233
+ `Expected element ${this.isNot ? "not " : ""}to have display value`,
1234
+ expectedValue,
1235
+ "Received",
1236
+ values
1237
+ )
1575
1238
  };
1576
1239
  }
1240
+ function getValues(tagName, htmlElement) {
1241
+ return tagName === "select" ? Array.from(htmlElement).filter((option) => option.selected).map((option) => option.textContent) : [htmlElement.value];
1242
+ }
1243
+ function getExpectedValues(expectedValue) {
1244
+ return expectedValue instanceof Array ? expectedValue : [expectedValue];
1245
+ }
1577
1246
 
1578
1247
  // src/matchers/to-have-errormessage.js
1579
1248
  function toHaveErrorMessage(htmlElement, checkWith) {
@@ -1601,9 +1270,7 @@ function toHaveErrorMessage(htmlElement, checkWith) {
1601
1270
  if (errormessageIDs.length > 0) {
1602
1271
  const document = htmlElement.ownerDocument;
1603
1272
  const errormessageEls = errormessageIDs.map((errormessageID) => document.getElementById(errormessageID)).filter(Boolean);
1604
- errormessage = normalize(
1605
- errormessageEls.map((el) => el.textContent).join(" ")
1606
- );
1273
+ errormessage = normalize(errormessageEls.map((el) => el.textContent).join(" "));
1607
1274
  }
1608
1275
  return {
1609
1276
  pass: expectsErrorMessage ? checkWith instanceof RegExp ? checkWith.test(errormessage) : this.equals(errormessage, checkWith) : Boolean(errormessage),
@@ -1611,11 +1278,7 @@ function toHaveErrorMessage(htmlElement, checkWith) {
1611
1278
  const to = this.isNot ? "not to" : "to";
1612
1279
  return getMessage(
1613
1280
  this,
1614
- this.utils.matcherHint(
1615
- `${this.isNot ? ".not" : ""}.toHaveErrorMessage`,
1616
- "element",
1617
- ""
1618
- ),
1281
+ this.utils.matcherHint(`${this.isNot ? ".not" : ""}.toHaveErrorMessage`, "element", ""),
1619
1282
  `Expected the element ${to} have error message`,
1620
1283
  this.utils.printExpected(checkWith),
1621
1284
  "Received",
@@ -1625,6 +1288,196 @@ function toHaveErrorMessage(htmlElement, checkWith) {
1625
1288
  };
1626
1289
  }
1627
1290
 
1291
+ // src/matchers/to-have-focus.js
1292
+ function toHaveFocus(element) {
1293
+ checkHtmlElement(element, toHaveFocus, this);
1294
+ return {
1295
+ pass: element.ownerDocument.activeElement === element,
1296
+ message: () => {
1297
+ return [
1298
+ this.utils.matcherHint(`${this.isNot ? ".not" : ""}.toHaveFocus`, "element", ""),
1299
+ "",
1300
+ ...this.isNot ? ["Received element is focused:", ` ${this.utils.printReceived(element)}`] : [
1301
+ "Expected element with focus:",
1302
+ ` ${this.utils.printExpected(element)}`,
1303
+ "Received element with focus:",
1304
+ ` ${this.utils.printReceived(element.ownerDocument.activeElement)}`
1305
+ ]
1306
+ ].join("\n");
1307
+ }
1308
+ };
1309
+ }
1310
+
1311
+ // src/matchers/to-have-form-values.ts
1312
+ import escape from "css.escape";
1313
+ import { isEqualWith, uniq } from "lodash-es";
1314
+ function getMultiElementValue(elements) {
1315
+ const types = uniq(elements.map((element) => element.type));
1316
+ if (types.length !== 1) {
1317
+ throw new Error("Multiple form elements with the same name must be of the same type");
1318
+ }
1319
+ switch (types[0]) {
1320
+ case "radio": {
1321
+ const theChosenOne = elements.find((radio) => radio.checked);
1322
+ return theChosenOne ? theChosenOne.value : void 0;
1323
+ }
1324
+ case "checkbox":
1325
+ return elements.filter((checkbox) => checkbox.checked).map((checkbox) => checkbox.value);
1326
+ default:
1327
+ return elements.map((element) => element.value);
1328
+ }
1329
+ }
1330
+ function getFormValue(container, name) {
1331
+ container.elements;
1332
+ const elements = [...container.querySelectorAll(`[name="${escape(name)}"]`)];
1333
+ if (elements.length === 0) {
1334
+ return void 0;
1335
+ }
1336
+ switch (elements.length) {
1337
+ case 1:
1338
+ return getSingleElementValue(elements[0]);
1339
+ default:
1340
+ return getMultiElementValue(elements);
1341
+ }
1342
+ }
1343
+ function getPureName(name) {
1344
+ return /\[\]$/.test(name) ? name.slice(0, -2) : name;
1345
+ }
1346
+ function getAllFormValues(container) {
1347
+ const names = Array.from(container.elements).map((element) => element.name);
1348
+ return names.reduce(
1349
+ (obj, name) => ({
1350
+ ...obj,
1351
+ [getPureName(name)]: getFormValue(container, name)
1352
+ }),
1353
+ {}
1354
+ );
1355
+ }
1356
+ function toHaveFormValues(formElement, expectedValues) {
1357
+ checkHtmlElement(formElement, toHaveFormValues, this);
1358
+ if (!formElement.elements) {
1359
+ throw new Error("toHaveFormValues must be called on a form or a fieldset");
1360
+ }
1361
+ const formValues = getAllFormValues(formElement);
1362
+ return {
1363
+ pass: Object.entries(expectedValues).every(
1364
+ ([name, expectedValue]) => isEqualWith(formValues[name], expectedValue, compareArraysAsSet)
1365
+ ),
1366
+ message: () => {
1367
+ const to = this.isNot ? "not to" : "to";
1368
+ const matcher = `${this.isNot ? ".not" : ""}.toHaveFormValues`;
1369
+ const commonKeyValues = Object.keys(formValues).filter((key) => expectedValues.hasOwnProperty(key)).reduce((obj, key) => ({ ...obj, [key]: formValues[key] }), {});
1370
+ return [
1371
+ this.utils.matcherHint(matcher, "element", ""),
1372
+ `Expected the element ${to} have form values`,
1373
+ this.utils.diff(expectedValues, commonKeyValues)
1374
+ ].join("\n\n");
1375
+ }
1376
+ };
1377
+ }
1378
+
1379
+ // src/matchers/to-have-style.js
1380
+ import chalk from "chalk";
1381
+ function getStyleDeclaration(document, css) {
1382
+ const styles = {};
1383
+ const copy = document.createElement("div");
1384
+ Object.keys(css).forEach((property) => {
1385
+ copy.style[property] = css[property];
1386
+ styles[property] = copy.style[property];
1387
+ });
1388
+ return styles;
1389
+ }
1390
+ function isSubset2(styles, computedStyle) {
1391
+ return !!Object.keys(styles).length && Object.entries(styles).every(
1392
+ ([prop, value]) => computedStyle[prop] === value || computedStyle.getPropertyValue(prop.toLowerCase()) === value
1393
+ );
1394
+ }
1395
+ function printoutStyles(styles) {
1396
+ return Object.keys(styles).sort().map((prop) => `${prop}: ${styles[prop]};`).join("\n");
1397
+ }
1398
+ function expectedDiff(diffFn, expected, computedStyles) {
1399
+ const received = Array.from(computedStyles).filter((prop) => expected[prop] !== void 0).reduce((obj, prop) => Object.assign(obj, { [prop]: computedStyles.getPropertyValue(prop) }), {});
1400
+ const diffOutput = diffFn(printoutStyles(expected), printoutStyles(received));
1401
+ return diffOutput.replace(`${chalk.red("+ Received")}
1402
+ `, "");
1403
+ }
1404
+ function toHaveStyle(htmlElement, css) {
1405
+ checkHtmlElement(htmlElement, toHaveStyle, this);
1406
+ const parsedCSS = typeof css === "object" ? css : parseCSS(css, toHaveStyle, this);
1407
+ const { getComputedStyle } = htmlElement.ownerDocument.defaultView;
1408
+ const expected = getStyleDeclaration(htmlElement.ownerDocument, parsedCSS);
1409
+ const received = getComputedStyle(htmlElement);
1410
+ return {
1411
+ pass: isSubset2(expected, received),
1412
+ message: () => {
1413
+ const matcher = `${this.isNot ? ".not" : ""}.toHaveStyle`;
1414
+ return [
1415
+ this.utils.matcherHint(matcher, "element", ""),
1416
+ expectedDiff(this.utils.diff, expected, received)
1417
+ ].join("\n\n");
1418
+ }
1419
+ };
1420
+ }
1421
+
1422
+ // src/matchers/to-have-text-content.js
1423
+ function toHaveTextContent(node, checkWith, options = { normalizeWhitespace: true }) {
1424
+ checkNode(node, toHaveTextContent, this);
1425
+ const textContent = options.normalizeWhitespace ? normalize(node.textContent) : node.textContent.replace(/\u00a0/g, " ");
1426
+ const checkingWithEmptyString = textContent !== "" && checkWith === "";
1427
+ return {
1428
+ pass: !checkingWithEmptyString && matches(textContent, checkWith),
1429
+ message: () => {
1430
+ const to = this.isNot ? "not to" : "to";
1431
+ return getMessage(
1432
+ this,
1433
+ this.utils.matcherHint(`${this.isNot ? ".not" : ""}.toHaveTextContent`, "element", ""),
1434
+ checkingWithEmptyString ? `Checking with empty string will always match, use .toBeEmptyDOMElement() instead` : `Expected element ${to} have text content`,
1435
+ checkWith,
1436
+ "Received",
1437
+ textContent
1438
+ );
1439
+ }
1440
+ };
1441
+ }
1442
+
1443
+ // src/matchers/to-have-value.js
1444
+ import { isEqualWith as isEqualWith2 } from "lodash-es";
1445
+ function toHaveValue(htmlElement, expectedValue) {
1446
+ checkHtmlElement(htmlElement, toHaveValue, this);
1447
+ if (htmlElement.tagName.toLowerCase() === "input" && ["checkbox", "radio"].includes(htmlElement.type)) {
1448
+ throw new Error(
1449
+ "input with type=checkbox or type=radio cannot be used with .toHaveValue(). Use .toBeChecked() for type=checkbox or .toHaveFormValues() instead"
1450
+ );
1451
+ }
1452
+ const receivedValue = getSingleElementValue(htmlElement);
1453
+ const expectsValue = expectedValue !== void 0;
1454
+ let expectedTypedValue = expectedValue;
1455
+ let receivedTypedValue = receivedValue;
1456
+ if (expectedValue == receivedValue && expectedValue !== receivedValue) {
1457
+ expectedTypedValue = `${expectedValue} (${typeof expectedValue})`;
1458
+ receivedTypedValue = `${receivedValue} (${typeof receivedValue})`;
1459
+ }
1460
+ return {
1461
+ pass: expectsValue ? isEqualWith2(receivedValue, expectedValue, compareArraysAsSet) : Boolean(receivedValue),
1462
+ message: () => {
1463
+ const to = this.isNot ? "not to" : "to";
1464
+ const matcher = this.utils.matcherHint(
1465
+ `${this.isNot ? ".not" : ""}.toHaveValue`,
1466
+ "element",
1467
+ expectedValue
1468
+ );
1469
+ return getMessage(
1470
+ this,
1471
+ matcher,
1472
+ `Expected the element ${to} have value`,
1473
+ expectsValue ? expectedTypedValue : "(any)",
1474
+ "Received",
1475
+ receivedTypedValue
1476
+ );
1477
+ }
1478
+ };
1479
+ }
1480
+
1628
1481
  // src/matchers/matchers.ts
1629
1482
  var matchers_exports = {};
1630
1483
  __export(matchers_exports, {
@@ -1657,32 +1510,32 @@ __export(matchers_exports, {
1657
1510
  });
1658
1511
 
1659
1512
  export {
1660
- toBeInTheDOM,
1661
- toBeInTheDocument,
1513
+ toBeChecked,
1514
+ toBeDisabled,
1515
+ toBeEnabled,
1662
1516
  toBeEmpty,
1663
1517
  toBeEmptyDOMElement,
1518
+ toBeInTheDocument,
1519
+ toBeInTheDOM,
1520
+ toBeInvalid,
1521
+ toBeValid,
1522
+ toBePartiallyChecked,
1523
+ toBeRequired,
1524
+ toBeVisible,
1664
1525
  toContainElement,
1665
1526
  toContainHTML,
1666
- toHaveTextContent,
1667
1527
  toHaveAccessibleDescription,
1668
1528
  toHaveAccessibleName,
1669
1529
  toHaveAttribute,
1670
1530
  toHaveClass,
1671
- toHaveStyle,
1531
+ toHaveDescription,
1532
+ toHaveDisplayValue,
1533
+ toHaveErrorMessage,
1672
1534
  toHaveFocus,
1673
1535
  toHaveFormValues,
1674
- toBeVisible,
1675
- toBeDisabled,
1676
- toBeEnabled,
1677
- toBeRequired,
1678
- toBeInvalid,
1679
- toBeValid,
1536
+ toHaveStyle,
1537
+ toHaveTextContent,
1680
1538
  toHaveValue,
1681
- toHaveDisplayValue,
1682
- toBeChecked,
1683
- toBePartiallyChecked,
1684
- toHaveDescription,
1685
- toHaveErrorMessage,
1686
1539
  matchers_exports
1687
1540
  };
1688
- //# sourceMappingURL=chunk-LZZAJDBO.js.map
1541
+ //# sourceMappingURL=chunk-JWT7CU2B.js.map