@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.
- package/README.md +13 -13
- package/dist/{chunk-LZZAJDBO.js → chunk-JWT7CU2B.js} +601 -748
- package/dist/chunk-JWT7CU2B.js.map +1 -0
- package/dist/extend-expect.js +1 -1
- package/dist/extend-expect.js.map +1 -1
- package/dist/matchers.d.ts +41 -65
- package/dist/matchers.js +1 -1
- package/package.json +83 -81
- package/dist/chunk-LZZAJDBO.js.map +0 -1
|
@@ -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-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
)
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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:
|
|
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
|
-
|
|
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-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
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
|
-
|
|
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-
|
|
726
|
-
function
|
|
727
|
-
|
|
728
|
-
|
|
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
|
|
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(
|
|
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-
|
|
751
|
-
function
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
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.
|
|
783
|
+
pass: container ? container.contains(element) : !!element,
|
|
763
784
|
message: () => {
|
|
764
785
|
return [
|
|
765
|
-
this.utils.matcherHint(
|
|
766
|
-
|
|
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(
|
|
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-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
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:
|
|
815
|
+
pass: isInvalid,
|
|
786
816
|
message: () => {
|
|
787
|
-
const
|
|
788
|
-
return
|
|
789
|
-
this,
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
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
|
-
|
|
805
|
-
|
|
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
|
|
823
|
-
return
|
|
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-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
const
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
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:
|
|
1190
|
+
pass: expectsDescription ? checkWith instanceof RegExp ? checkWith.test(description) : this.equals(description, checkWith) : Boolean(description),
|
|
1047
1191
|
message: () => {
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
),
|
|
1054
|
-
"",
|
|
1055
|
-
|
|
1056
|
-
|
|
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-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
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
|
-
"
|
|
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:
|
|
1560
|
-
message: () =>
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
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
|
-
|
|
1661
|
-
|
|
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
|
-
|
|
1531
|
+
toHaveDescription,
|
|
1532
|
+
toHaveDisplayValue,
|
|
1533
|
+
toHaveErrorMessage,
|
|
1672
1534
|
toHaveFocus,
|
|
1673
1535
|
toHaveFormValues,
|
|
1674
|
-
|
|
1675
|
-
|
|
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-
|
|
1541
|
+
//# sourceMappingURL=chunk-JWT7CU2B.js.map
|