From b31540449adcb413b57dead0d104535796b65482 Mon Sep 17 00:00:00 2001 From: Tatenda Chawanzwa Date: Sat, 9 Nov 2019 18:38:53 +0000 Subject: [PATCH] temp remove dist folder from .gitignore --- .gitignore | 1 - dist/extend-expect.js | 7 ++ dist/index.js | 153 +++++++++++++++++++++++++++ dist/to-be-checked.js | 40 +++++++ dist/to-be-disabled.js | 63 +++++++++++ dist/to-be-empty.js | 20 ++++ dist/to-be-in-the-document.js | 33 ++++++ dist/to-be-in-the-dom.js | 29 ++++++ dist/to-be-invalid.js | 45 ++++++++ dist/to-be-required.js | 39 +++++++ dist/to-be-visible.js | 38 +++++++ dist/to-contain-element.js | 26 +++++ dist/to-contain-html.js | 20 ++++ dist/to-have-attribute.js | 37 +++++++ dist/to-have-class.js | 38 +++++++ dist/to-have-focus.js | 20 ++++ dist/to-have-form-values.js | 96 +++++++++++++++++ dist/to-have-style.js | 63 +++++++++++ dist/to-have-text-content.js | 26 +++++ dist/to-have-value.js | 33 ++++++ dist/utils.js | 191 ++++++++++++++++++++++++++++++++++ 21 files changed, 1017 insertions(+), 1 deletion(-) create mode 100644 dist/extend-expect.js create mode 100644 dist/index.js create mode 100644 dist/to-be-checked.js create mode 100644 dist/to-be-disabled.js create mode 100644 dist/to-be-empty.js create mode 100644 dist/to-be-in-the-document.js create mode 100644 dist/to-be-in-the-dom.js create mode 100644 dist/to-be-invalid.js create mode 100644 dist/to-be-required.js create mode 100644 dist/to-be-visible.js create mode 100644 dist/to-contain-element.js create mode 100644 dist/to-contain-html.js create mode 100644 dist/to-have-attribute.js create mode 100644 dist/to-have-class.js create mode 100644 dist/to-have-focus.js create mode 100644 dist/to-have-form-values.js create mode 100644 dist/to-have-style.js create mode 100644 dist/to-have-text-content.js create mode 100644 dist/to-have-value.js create mode 100644 dist/utils.js diff --git a/.gitignore b/.gitignore index afbd116c..cf7cd952 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ node_modules coverage -dist .opt-in .opt-out .DS_Store diff --git a/dist/extend-expect.js b/dist/extend-expect.js new file mode 100644 index 00000000..1dac7536 --- /dev/null +++ b/dist/extend-expect.js @@ -0,0 +1,7 @@ +"use strict"; + +var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); + +var extensions = _interopRequireWildcard(require("./index")); + +expect.extend(extensions); \ No newline at end of file diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 00000000..0bb804f5 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,153 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, "toBeInTheDOM", { + enumerable: true, + get: function () { + return _toBeInTheDom.toBeInTheDOM; + } +}); +Object.defineProperty(exports, "toBeInTheDocument", { + enumerable: true, + get: function () { + return _toBeInTheDocument.toBeInTheDocument; + } +}); +Object.defineProperty(exports, "toBeEmpty", { + enumerable: true, + get: function () { + return _toBeEmpty.toBeEmpty; + } +}); +Object.defineProperty(exports, "toContainElement", { + enumerable: true, + get: function () { + return _toContainElement.toContainElement; + } +}); +Object.defineProperty(exports, "toContainHTML", { + enumerable: true, + get: function () { + return _toContainHtml.toContainHTML; + } +}); +Object.defineProperty(exports, "toHaveTextContent", { + enumerable: true, + get: function () { + return _toHaveTextContent.toHaveTextContent; + } +}); +Object.defineProperty(exports, "toHaveAttribute", { + enumerable: true, + get: function () { + return _toHaveAttribute.toHaveAttribute; + } +}); +Object.defineProperty(exports, "toHaveClass", { + enumerable: true, + get: function () { + return _toHaveClass.toHaveClass; + } +}); +Object.defineProperty(exports, "toHaveStyle", { + enumerable: true, + get: function () { + return _toHaveStyle.toHaveStyle; + } +}); +Object.defineProperty(exports, "toHaveFocus", { + enumerable: true, + get: function () { + return _toHaveFocus.toHaveFocus; + } +}); +Object.defineProperty(exports, "toHaveFormValues", { + enumerable: true, + get: function () { + return _toHaveFormValues.toHaveFormValues; + } +}); +Object.defineProperty(exports, "toBeVisible", { + enumerable: true, + get: function () { + return _toBeVisible.toBeVisible; + } +}); +Object.defineProperty(exports, "toBeDisabled", { + enumerable: true, + get: function () { + return _toBeDisabled.toBeDisabled; + } +}); +Object.defineProperty(exports, "toBeEnabled", { + enumerable: true, + get: function () { + return _toBeDisabled.toBeEnabled; + } +}); +Object.defineProperty(exports, "toBeRequired", { + enumerable: true, + get: function () { + return _toBeRequired.toBeRequired; + } +}); +Object.defineProperty(exports, "toBeInvalid", { + enumerable: true, + get: function () { + return _toBeInvalid.toBeInvalid; + } +}); +Object.defineProperty(exports, "toBeValid", { + enumerable: true, + get: function () { + return _toBeInvalid.toBeValid; + } +}); +Object.defineProperty(exports, "toHaveValue", { + enumerable: true, + get: function () { + return _toHaveValue.toHaveValue; + } +}); +Object.defineProperty(exports, "toBeChecked", { + enumerable: true, + get: function () { + return _toBeChecked.toBeChecked; + } +}); + +var _toBeInTheDom = require("./to-be-in-the-dom"); + +var _toBeInTheDocument = require("./to-be-in-the-document"); + +var _toBeEmpty = require("./to-be-empty"); + +var _toContainElement = require("./to-contain-element"); + +var _toContainHtml = require("./to-contain-html"); + +var _toHaveTextContent = require("./to-have-text-content"); + +var _toHaveAttribute = require("./to-have-attribute"); + +var _toHaveClass = require("./to-have-class"); + +var _toHaveStyle = require("./to-have-style"); + +var _toHaveFocus = require("./to-have-focus"); + +var _toHaveFormValues = require("./to-have-form-values"); + +var _toBeVisible = require("./to-be-visible"); + +var _toBeDisabled = require("./to-be-disabled"); + +var _toBeRequired = require("./to-be-required"); + +var _toBeInvalid = require("./to-be-invalid"); + +var _toHaveValue = require("./to-have-value"); + +var _toBeChecked = require("./to-be-checked"); \ No newline at end of file diff --git a/dist/to-be-checked.js b/dist/to-be-checked.js new file mode 100644 index 00000000..19ef4ed2 --- /dev/null +++ b/dist/to-be-checked.js @@ -0,0 +1,40 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.toBeChecked = toBeChecked; + +var _jestMatcherUtils = require("jest-matcher-utils"); + +var _utils = require("./utils"); + +function toBeChecked(element) { + (0, _utils.checkHtmlElement)(element, toBeChecked, this); + + const isValidInput = () => { + return element.tagName.toLowerCase() === 'input' && ['checkbox', 'radio'].includes(element.type); + }; + + if (!isValidInput() && !(() => { + return ['checkbox', 'radio'].includes(element.getAttribute('role')) && ['true', 'false'].includes(element.getAttribute('aria-checked')); + })()) { + return { + pass: false, + message: () => 'only inputs with type="checkbox" or type="radio" or elements with role="checkbox" or role="radio" and a valid aria-checked attribute can be used with .toBeChecked(). Use .toHaveValue() instead' + }; + } + + const isChecked = () => { + if (isValidInput()) return element.checked; + return element.getAttribute('aria-checked') === 'true'; + }; + + return { + pass: isChecked(), + message: () => { + const is = isChecked() ? 'is' : 'is not'; + return [(0, _jestMatcherUtils.matcherHint)(`${this.isNot ? '.not' : ''}.toBeChecked`, 'element', ''), '', `Received element ${is} checked:`, ` ${(0, _jestMatcherUtils.printReceived)(element.cloneNode(false))}`].join('\n'); + } + }; +} \ No newline at end of file diff --git a/dist/to-be-disabled.js b/dist/to-be-disabled.js new file mode 100644 index 00000000..60f432d8 --- /dev/null +++ b/dist/to-be-disabled.js @@ -0,0 +1,63 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.toBeDisabled = toBeDisabled; +exports.toBeEnabled = toBeEnabled; + +var _jestMatcherUtils = require("jest-matcher-utils"); + +var _utils = require("./utils"); + +const FORM_TAGS = ['fieldset', 'input', 'select', 'optgroup', 'option', 'button', 'textarea']; +/* + * According to specification: + * If
is disabled, the form controls that are its descendants, + * except descendants of its first optional element, are disabled + * + * https://html.spec.whatwg.org/multipage/form-elements.html#concept-fieldset-disabled + * + * This method tests whether element is first legend child of fieldset parent + */ + +function isFirstLegendChildOfFieldset(element, parent) { + return (0, _utils.getTag)(element) === 'legend' && (0, _utils.getTag)(parent) === 'fieldset' && element.isSameNode(Array.from(parent.children).find(child => (0, _utils.getTag)(child) === 'legend')); +} + +function isElementDisabledByParent(element, parent) { + return isElementDisabled(parent) && !isFirstLegendChildOfFieldset(element, parent); +} + +function isElementDisabled(element) { + return FORM_TAGS.includes((0, _utils.getTag)(element)) && element.hasAttribute('disabled'); +} + +function isAncestorDisabled(element) { + const parent = element.parentElement; + return Boolean(parent) && (isElementDisabledByParent(element, parent) || isAncestorDisabled(parent)); +} + +function toBeDisabled(element) { + (0, _utils.checkHtmlElement)(element, toBeDisabled, this); + const isDisabled = isElementDisabled(element) || isAncestorDisabled(element); + return { + pass: isDisabled, + message: () => { + const is = isDisabled ? 'is' : 'is not'; + return [(0, _jestMatcherUtils.matcherHint)(`${this.isNot ? '.not' : ''}.toBeDisabled`, 'element', ''), '', `Received element ${is} disabled:`, ` ${(0, _jestMatcherUtils.printReceived)(element.cloneNode(false))}`].join('\n'); + } + }; +} + +function toBeEnabled(element) { + (0, _utils.checkHtmlElement)(element, toBeEnabled, this); + const isEnabled = !(isElementDisabled(element) || isAncestorDisabled(element)); + return { + pass: isEnabled, + message: () => { + const is = isEnabled ? 'is' : 'is not'; + return [(0, _jestMatcherUtils.matcherHint)(`${this.isNot ? '.not' : ''}.toBeEnabled`, 'element', ''), '', `Received element ${is} enabled:`, ` ${(0, _jestMatcherUtils.printReceived)(element.cloneNode(false))}`].join('\n'); + } + }; +} \ No newline at end of file diff --git a/dist/to-be-empty.js b/dist/to-be-empty.js new file mode 100644 index 00000000..d5fbc1c7 --- /dev/null +++ b/dist/to-be-empty.js @@ -0,0 +1,20 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.toBeEmpty = toBeEmpty; + +var _jestMatcherUtils = require("jest-matcher-utils"); + +var _utils = require("./utils"); + +function toBeEmpty(element) { + (0, _utils.checkHtmlElement)(element, toBeEmpty, this); + return { + pass: element.innerHTML === '', + message: () => { + return [(0, _jestMatcherUtils.matcherHint)(`${this.isNot ? '.not' : ''}.toBeEmpty`, 'element', ''), '', 'Received:', ` ${(0, _jestMatcherUtils.printReceived)(element.innerHTML)}`].join('\n'); + } + }; +} \ No newline at end of file diff --git a/dist/to-be-in-the-document.js b/dist/to-be-in-the-document.js new file mode 100644 index 00000000..15917968 --- /dev/null +++ b/dist/to-be-in-the-document.js @@ -0,0 +1,33 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.toBeInTheDocument = toBeInTheDocument; + +var _jestMatcherUtils = require("jest-matcher-utils"); + +var _utils = require("./utils"); + +function toBeInTheDocument(element) { + if (element !== null || !this.isNot) { + (0, _utils.checkHtmlElement)(element, toBeInTheDocument, this); + } + + const pass = element === null ? false : element.ownerDocument.contains(element); + + const errorFound = () => { + return `expected document not to contain element, found ${(0, _jestMatcherUtils.stringify)(element.cloneNode(true))} instead`; + }; + + const errorNotFound = () => { + return `element could not be found in the document`; + }; + + return { + pass, + message: () => { + return [(0, _jestMatcherUtils.matcherHint)(`${this.isNot ? '.not' : ''}.toBeInTheDocument`, 'element', ''), '', (0, _jestMatcherUtils.RECEIVED_COLOR)(this.isNot ? errorFound() : errorNotFound())].join('\n'); + } + }; +} \ No newline at end of file diff --git a/dist/to-be-in-the-dom.js b/dist/to-be-in-the-dom.js new file mode 100644 index 00000000..77f68e39 --- /dev/null +++ b/dist/to-be-in-the-dom.js @@ -0,0 +1,29 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.toBeInTheDOM = toBeInTheDOM; + +var _jestMatcherUtils = require("jest-matcher-utils"); + +var _utils = require("./utils"); + +function toBeInTheDOM(element, container) { + (0, _utils.deprecate)('toBeInTheDOM', 'Please use toBeInTheDocument for searching the entire document and toContainElement for searching a specific container.'); + + if (element) { + (0, _utils.checkHtmlElement)(element, toBeInTheDOM, this); + } + + if (container) { + (0, _utils.checkHtmlElement)(container, toBeInTheDOM, this); + } + + return { + pass: container ? container.contains(element) : !!element, + message: () => { + return [(0, _jestMatcherUtils.matcherHint)(`${this.isNot ? '.not' : ''}.toBeInTheDOM`, 'element', ''), '', 'Received:', ` ${(0, _jestMatcherUtils.printReceived)(element ? element.cloneNode(false) : element)}`].join('\n'); + } + }; +} \ No newline at end of file diff --git a/dist/to-be-invalid.js b/dist/to-be-invalid.js new file mode 100644 index 00000000..2039e4b6 --- /dev/null +++ b/dist/to-be-invalid.js @@ -0,0 +1,45 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.toBeInvalid = toBeInvalid; +exports.toBeValid = toBeValid; + +var _jestMatcherUtils = require("jest-matcher-utils"); + +var _utils = require("./utils"); + +const FORM_TAGS = ['form', 'input', 'select', 'textarea']; + +function isElementHavingAriaInvalid(element) { + return element.hasAttribute('aria-invalid') && element.getAttribute('aria-invalid') !== 'false'; +} + +function isElementInvalid(element) { + return !element.checkValidity(); +} + +function toBeInvalid(element) { + (0, _utils.checkHtmlElement)(element, toBeInvalid, this); + const isInvalid = isElementHavingAriaInvalid(element) || isElementInvalid(element); + return { + pass: isInvalid, + message: () => { + const is = isInvalid ? 'is' : 'is not'; + return [(0, _jestMatcherUtils.matcherHint)(`${this.isNot ? '.not' : ''}.toBeInvalid`, 'element', ''), '', `Received element ${is} currently invalid:`, ` ${(0, _jestMatcherUtils.printReceived)(element.cloneNode(false))}`].join('\n'); + } + }; +} + +function toBeValid(element) { + (0, _utils.checkHtmlElement)(element, toBeValid, this); + const isValid = !isElementHavingAriaInvalid(element) && FORM_TAGS.includes((0, _utils.getTag)(element)) && !isElementInvalid(element); + return { + pass: isValid, + message: () => { + const is = isValid ? 'is' : 'is not'; + return [(0, _jestMatcherUtils.matcherHint)(`${this.isNot ? '.not' : ''}.toBeValid`, 'element', ''), '', `Received element ${is} currently valid:`, ` ${(0, _jestMatcherUtils.printReceived)(element.cloneNode(false))}`].join('\n'); + } + }; +} \ No newline at end of file diff --git a/dist/to-be-required.js b/dist/to-be-required.js new file mode 100644 index 00000000..7b139350 --- /dev/null +++ b/dist/to-be-required.js @@ -0,0 +1,39 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.toBeRequired = toBeRequired; + +var _jestMatcherUtils = require("jest-matcher-utils"); + +var _utils = require("./utils"); + +const FORM_TAGS = ['select', 'textarea']; +const ARIA_FORM_TAGS = ['input', 'select', 'textarea']; +const UNSUPPORTED_INPUT_TYPES = ['color', 'hidden', 'range', 'submit', 'image', 'reset']; +const SUPPORTED_ARIA_ROLES = ['combobox', 'gridcell', 'radiogroup', 'spinbutton', 'tree']; + +function isRequiredOnFormTagsExceptInput(element) { + return FORM_TAGS.includes((0, _utils.getTag)(element)) && element.hasAttribute('required'); +} + +function isRequiredOnSupportedInput(element) { + return (0, _utils.getTag)(element) === 'input' && element.hasAttribute('required') && (element.hasAttribute('type') && !UNSUPPORTED_INPUT_TYPES.includes(element.getAttribute('type')) || !element.hasAttribute('type')); +} + +function isElementRequiredByARIA(element) { + return element.hasAttribute('aria-required') && element.getAttribute('aria-required') === 'true' && (ARIA_FORM_TAGS.includes((0, _utils.getTag)(element)) || element.hasAttribute('role') && SUPPORTED_ARIA_ROLES.includes(element.getAttribute('role'))); +} + +function toBeRequired(element) { + (0, _utils.checkHtmlElement)(element, toBeRequired, this); + const isRequired = isRequiredOnFormTagsExceptInput(element) || isRequiredOnSupportedInput(element) || isElementRequiredByARIA(element); + return { + pass: isRequired, + message: () => { + const is = isRequired ? 'is' : 'is not'; + return [(0, _jestMatcherUtils.matcherHint)(`${this.isNot ? '.not' : ''}.toBeRequired`, 'element', ''), '', `Received element ${is} required:`, ` ${(0, _jestMatcherUtils.printReceived)(element.cloneNode(false))}`].join('\n'); + } + }; +} \ No newline at end of file diff --git a/dist/to-be-visible.js b/dist/to-be-visible.js new file mode 100644 index 00000000..3f4acf5f --- /dev/null +++ b/dist/to-be-visible.js @@ -0,0 +1,38 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.toBeVisible = toBeVisible; + +var _jestMatcherUtils = require("jest-matcher-utils"); + +var _utils = require("./utils"); + +function isStyleVisible(element) { + const { + getComputedStyle + } = element.ownerDocument.defaultView; + const { + display, + visibility, + opacity + } = getComputedStyle(element); + return display !== 'none' && visibility !== 'hidden' && visibility !== 'collapse' && opacity !== '0' && opacity !== 0; +} + +function isElementVisible(element) { + return isStyleVisible(element) && !element.hasAttribute('hidden') && (!element.parentElement || isElementVisible(element.parentElement)); +} + +function toBeVisible(element) { + (0, _utils.checkHtmlElement)(element, toBeVisible, this); + const isVisible = isElementVisible(element); + return { + pass: isVisible, + message: () => { + const is = isVisible ? 'is' : 'is not'; + return [(0, _jestMatcherUtils.matcherHint)(`${this.isNot ? '.not' : ''}.toBeVisible`, 'element', ''), '', `Received element ${is} visible:`, ` ${(0, _jestMatcherUtils.printReceived)(element.cloneNode(false))}`].join('\n'); + } + }; +} \ No newline at end of file diff --git a/dist/to-contain-element.js b/dist/to-contain-element.js new file mode 100644 index 00000000..464906b6 --- /dev/null +++ b/dist/to-contain-element.js @@ -0,0 +1,26 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.toContainElement = toContainElement; + +var _jestMatcherUtils = require("jest-matcher-utils"); + +var _utils = require("./utils"); + +function toContainElement(container, element) { + (0, _utils.checkHtmlElement)(container, toContainElement, this); + + if (element !== null) { + (0, _utils.checkHtmlElement)(element, toContainElement, this); + } + + return { + pass: container.contains(element), + message: () => { + return [(0, _jestMatcherUtils.matcherHint)(`${this.isNot ? '.not' : ''}.toContainElement`, 'element', 'element'), '', (0, _jestMatcherUtils.RECEIVED_COLOR)(`${(0, _jestMatcherUtils.stringify)(container.cloneNode(false))} ${this.isNot ? 'contains:' : 'does not contain:'} ${(0, _jestMatcherUtils.stringify)(element ? element.cloneNode(false) : element)} + `)].join('\n'); + } + }; +} \ No newline at end of file diff --git a/dist/to-contain-html.js b/dist/to-contain-html.js new file mode 100644 index 00000000..e6e8a04b --- /dev/null +++ b/dist/to-contain-html.js @@ -0,0 +1,20 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.toContainHTML = toContainHTML; + +var _jestMatcherUtils = require("jest-matcher-utils"); + +var _utils = require("./utils"); + +function toContainHTML(container, htmlText) { + (0, _utils.checkHtmlElement)(container, toContainHTML, this); + return { + pass: container.outerHTML.includes(htmlText), + message: () => { + return [(0, _jestMatcherUtils.matcherHint)(`${this.isNot ? '.not' : ''}.toContainHTML`, 'element', ''), '', 'Received:', ` ${(0, _jestMatcherUtils.printReceived)(container.cloneNode(true))}`].join('\n'); + } + }; +} \ No newline at end of file diff --git a/dist/to-have-attribute.js b/dist/to-have-attribute.js new file mode 100644 index 00000000..9213bf6d --- /dev/null +++ b/dist/to-have-attribute.js @@ -0,0 +1,37 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.toHaveAttribute = toHaveAttribute; + +var _jestMatcherUtils = require("jest-matcher-utils"); + +var _utils = require("./utils"); + +function printAttribute(name, value) { + return value === undefined ? name : `${name}=${(0, _jestMatcherUtils.stringify)(value)}`; +} + +function getAttributeComment(name, value) { + return value === undefined ? `element.hasAttribute(${(0, _jestMatcherUtils.stringify)(name)})` : `element.getAttribute(${(0, _jestMatcherUtils.stringify)(name)}) === ${(0, _jestMatcherUtils.stringify)(value)}`; +} + +function toHaveAttribute(htmlElement, name, expectedValue) { + (0, _utils.checkHtmlElement)(htmlElement, toHaveAttribute, this); + const isExpectedValuePresent = expectedValue !== undefined; + const hasAttribute = htmlElement.hasAttribute(name); + const receivedValue = htmlElement.getAttribute(name); + return { + pass: isExpectedValuePresent ? hasAttribute && this.equals(receivedValue, expectedValue) : hasAttribute, + message: () => { + const to = this.isNot ? 'not to' : 'to'; + const receivedAttribute = hasAttribute ? printAttribute(name, receivedValue) : null; + const matcher = (0, _jestMatcherUtils.matcherHint)(`${this.isNot ? '.not' : ''}.toHaveAttribute`, 'element', (0, _jestMatcherUtils.printExpected)(name), { + secondArgument: isExpectedValuePresent ? (0, _jestMatcherUtils.printExpected)(expectedValue) : undefined, + comment: getAttributeComment(name, expectedValue) + }); + return (0, _utils.getMessage)(matcher, `Expected the element ${to} have attribute`, printAttribute(name, expectedValue), 'Received', receivedAttribute); + } + }; +} \ No newline at end of file diff --git a/dist/to-have-class.js b/dist/to-have-class.js new file mode 100644 index 00000000..de6a0292 --- /dev/null +++ b/dist/to-have-class.js @@ -0,0 +1,38 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.toHaveClass = toHaveClass; + +var _jestMatcherUtils = require("jest-matcher-utils"); + +var _utils = require("./utils"); + +function splitClassNames(str) { + if (!str) { + return []; + } + + return str.split(/\s+/).filter(s => s.length > 0); +} + +function isSubset(subset, superset) { + return subset.every(item => superset.includes(item)); +} + +function toHaveClass(htmlElement, ...expectedClassNames) { + (0, _utils.checkHtmlElement)(htmlElement, toHaveClass, this); + const received = splitClassNames(htmlElement.getAttribute('class')); + const expected = expectedClassNames.reduce((acc, className) => acc.concat(splitClassNames(className)), []); + return expected.length > 0 ? { + pass: isSubset(expected, received), + message: () => { + const to = this.isNot ? 'not to' : 'to'; + return (0, _utils.getMessage)((0, _jestMatcherUtils.matcherHint)(`${this.isNot ? '.not' : ''}.toHaveClass`, 'element', (0, _jestMatcherUtils.printExpected)(expected.join(' '))), `Expected the element ${to} have class`, expected.join(' '), 'Received', received.join(' ')); + } + } : { + pass: this.isNot ? received.length > 0 : false, + message: () => this.isNot ? (0, _utils.getMessage)((0, _jestMatcherUtils.matcherHint)('.not.toHaveClass', 'element', ''), 'Expected the element to have classes', '(none)', 'Received', received.join(' ')) : [(0, _jestMatcherUtils.matcherHint)(`.toHaveClass`, 'element'), 'At least one expected class must be provided.'].join('\n') + }; +} \ No newline at end of file diff --git a/dist/to-have-focus.js b/dist/to-have-focus.js new file mode 100644 index 00000000..3b3bd092 --- /dev/null +++ b/dist/to-have-focus.js @@ -0,0 +1,20 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.toHaveFocus = toHaveFocus; + +var _jestMatcherUtils = require("jest-matcher-utils"); + +var _utils = require("./utils"); + +function toHaveFocus(element) { + (0, _utils.checkHtmlElement)(element, toHaveFocus, this); + return { + pass: element.ownerDocument.activeElement === element, + message: () => { + return [(0, _jestMatcherUtils.matcherHint)(`${this.isNot ? '.not' : ''}.toHaveFocus`, 'element', ''), '', 'Expected', ` ${(0, _jestMatcherUtils.printExpected)(element)}`, 'Received:', ` ${(0, _jestMatcherUtils.printReceived)(element.ownerDocument.activeElement)}`].join('\n'); + } + }; +} \ No newline at end of file diff --git a/dist/to-have-form-values.js b/dist/to-have-form-values.js new file mode 100644 index 00000000..1cb0bb8c --- /dev/null +++ b/dist/to-have-form-values.js @@ -0,0 +1,96 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.toHaveFormValues = toHaveFormValues; + +var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); + +var _jestMatcherUtils = require("jest-matcher-utils"); + +var _jestDiff = _interopRequireDefault(require("jest-diff")); + +var _isEqualWith = _interopRequireDefault(require("lodash/isEqualWith")); + +var _uniq = _interopRequireDefault(require("lodash/uniq")); + +var _css = _interopRequireDefault(require("css.escape")); + +var _utils = require("./utils"); + +function getMultiElementValue(elements) { + const types = (0, _uniq.default)(elements.map(element => element.type)); + + if (types.length !== 1) { + throw new Error('Multiple form elements with the same name must be of the same type'); + } + + switch (types[0]) { + case 'radio': + { + const theChosenOne = elements.find(radio => radio.checked); + return theChosenOne ? theChosenOne.value : undefined; + } + + case 'checkbox': + return elements.filter(checkbox => checkbox.checked).map(checkbox => checkbox.value); + + default: + // NOTE: Not even sure this is a valid use case, but just in case... + return elements.map(element => element.value); + } +} + +function getFormValue(container, name) { + const elements = [...container.querySelectorAll(`[name="${(0, _css.default)(name)}"]`)]; + /* istanbul ignore if */ + + if (elements.length === 0) { + return undefined; // shouldn't happen, but just in case + } + + switch (elements.length) { + case 1: + return (0, _utils.getSingleElementValue)(elements[0]); + + default: + return getMultiElementValue(elements); + } +} // Strips the `[]` suffix off a form value name + + +function getPureName(name) { + return /\[\]$/.test(name) ? name.slice(0, -2) : name; +} + +function getAllFormValues(container) { + const names = Array.from(container.elements).map(element => element.name); + return names.reduce((obj, name) => (0, _extends2.default)({}, obj, { + [getPureName(name)]: getFormValue(container, name) + }), {}); +} + +function toHaveFormValues(formElement, expectedValues) { + (0, _utils.checkHtmlElement)(formElement, toHaveFormValues, this); + + if (!formElement.elements) { + // TODO: Change condition to use instanceof against the appropriate element classes instead + throw new Error('toHaveFormValues must be called on a form or a fieldset'); + } + + const formValues = getAllFormValues(formElement); + return { + pass: Object.entries(expectedValues).every(([name, expectedValue]) => (0, _isEqualWith.default)(formValues[name], expectedValue, _utils.compareArraysAsSet)), + message: () => { + const to = this.isNot ? 'not to' : 'to'; + const matcher = `${this.isNot ? '.not' : ''}.toHaveFormValues`; + const commonKeyValues = Object.keys(formValues).filter(key => expectedValues.hasOwnProperty(key)).reduce((obj, key) => (0, _extends2.default)({}, obj, { + [key]: formValues[key] + }), {}); + return [(0, _jestMatcherUtils.matcherHint)(matcher, 'element', ''), `Expected the element ${to} have form values`, (0, _jestDiff.default)(expectedValues, commonKeyValues)].join('\n\n'); + } + }; +} \ No newline at end of file diff --git a/dist/to-have-style.js b/dist/to-have-style.js new file mode 100644 index 00000000..61d32c25 --- /dev/null +++ b/dist/to-have-style.js @@ -0,0 +1,63 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.toHaveStyle = toHaveStyle; + +var _jestMatcherUtils = require("jest-matcher-utils"); + +var _jestDiff = _interopRequireDefault(require("jest-diff")); + +var _chalk = _interopRequireDefault(require("chalk")); + +var _utils = require("./utils"); + +function getStyleDeclaration(document, css) { + const styles = {}; // The next block is necessary to normalize colors + + const copy = document.createElement('div'); + Object.keys(css).forEach(property => { + copy.style[property] = css[property]; + styles[property] = copy.style[property]; + }); + return styles; +} + +function isSubset(styles, computedStyle) { + return Object.entries(styles).every(([prop, value]) => computedStyle.getPropertyValue(prop.toLowerCase()) === value.toLowerCase()); +} + +function printoutStyles(styles) { + return Object.keys(styles).sort().map(prop => `${prop}: ${styles[prop]};`).join('\n'); +} // Highlights only style rules that were expected but were not found in the +// received computed styles + + +function expectedDiff(expected, computedStyles) { + const received = Array.from(computedStyles).filter(prop => expected[prop]).reduce((obj, prop) => Object.assign(obj, { + [prop]: computedStyles.getPropertyValue(prop) + }), {}); + const diffOutput = (0, _jestDiff.default)(printoutStyles(expected), printoutStyles(received)); // Remove the "+ Received" annotation because this is a one-way diff + + return diffOutput.replace(`${_chalk.default.red('+ Received')}\n`, ''); +} + +function toHaveStyle(htmlElement, css) { + (0, _utils.checkHtmlElement)(htmlElement, toHaveStyle, this); + const parsedCSS = (0, _utils.parseCSS)(css, toHaveStyle, this); + const { + getComputedStyle + } = htmlElement.ownerDocument.defaultView; + const expected = getStyleDeclaration(htmlElement.ownerDocument, parsedCSS); + const received = getComputedStyle(htmlElement); + return { + pass: isSubset(expected, received), + message: () => { + const matcher = `${this.isNot ? '.not' : ''}.toHaveStyle`; + return [(0, _jestMatcherUtils.matcherHint)(matcher, 'element', ''), expectedDiff(expected, received)].join('\n\n'); + } + }; +} \ No newline at end of file diff --git a/dist/to-have-text-content.js b/dist/to-have-text-content.js new file mode 100644 index 00000000..0c10a035 --- /dev/null +++ b/dist/to-have-text-content.js @@ -0,0 +1,26 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.toHaveTextContent = toHaveTextContent; + +var _jestMatcherUtils = require("jest-matcher-utils"); + +var _utils = require("./utils"); + +function toHaveTextContent(htmlElement, checkWith, options = { + normalizeWhitespace: true +}) { + (0, _utils.checkHtmlElement)(htmlElement, toHaveTextContent, this); + const textContent = options.normalizeWhitespace ? (0, _utils.normalize)(htmlElement.textContent) : htmlElement.textContent.replace(/\u00a0/g, ' '); // Replace   with normal spaces + + const checkingWithEmptyString = textContent !== '' && checkWith === ''; + return { + pass: !checkingWithEmptyString && (0, _utils.matches)(textContent, checkWith), + message: () => { + const to = this.isNot ? 'not to' : 'to'; + return (0, _utils.getMessage)((0, _jestMatcherUtils.matcherHint)(`${this.isNot ? '.not' : ''}.toHaveTextContent`, 'element', ''), checkingWithEmptyString ? `Checking with empty string will always match, use .toBeEmpty() instead` : `Expected element ${to} have text content`, checkWith, 'Received', textContent); + } + }; +} \ No newline at end of file diff --git a/dist/to-have-value.js b/dist/to-have-value.js new file mode 100644 index 00000000..d9301d42 --- /dev/null +++ b/dist/to-have-value.js @@ -0,0 +1,33 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.toHaveValue = toHaveValue; + +var _jestMatcherUtils = require("jest-matcher-utils"); + +var _isEqualWith = _interopRequireDefault(require("lodash/isEqualWith")); + +var _utils = require("./utils"); + +function toHaveValue(htmlElement, expectedValue) { + (0, _utils.checkHtmlElement)(htmlElement, toHaveValue, this); + + if (htmlElement.tagName.toLowerCase() === 'input' && ['checkbox', 'radio'].includes(htmlElement.type)) { + throw new Error('input with type=checkbox or type=radio cannot be used with .toHaveValue(). Use .toBeChecked() for type=checkbox or .toHaveFormValues() instead'); + } + + const receivedValue = (0, _utils.getSingleElementValue)(htmlElement); + const expectsValue = expectedValue !== undefined; + return { + pass: expectsValue ? (0, _isEqualWith.default)(receivedValue, expectedValue, _utils.compareArraysAsSet) : Boolean(receivedValue), + message: () => { + const to = this.isNot ? 'not to' : 'to'; + const matcher = (0, _jestMatcherUtils.matcherHint)(`${this.isNot ? '.not' : ''}.toHaveValue`, 'element', expectedValue); + return (0, _utils.getMessage)(matcher, `Expected the element ${to} have value`, expectsValue ? expectedValue : '(any)', 'Received', receivedValue); + } + }; +} \ No newline at end of file diff --git a/dist/utils.js b/dist/utils.js new file mode 100644 index 00000000..4461205f --- /dev/null +++ b/dist/utils.js @@ -0,0 +1,191 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.checkHtmlElement = checkHtmlElement; +exports.parseCSS = parseCSS; +exports.deprecate = deprecate; +exports.getMessage = getMessage; +exports.matches = matches; +exports.normalize = normalize; +exports.getTag = getTag; +exports.getSingleElementValue = getSingleElementValue; +exports.compareArraysAsSet = compareArraysAsSet; +exports.HtmlElementTypeError = void 0; + +var _redent = _interopRequireDefault(require("redent")); + +var _jestMatcherUtils = require("jest-matcher-utils"); + +var _css = require("css"); + +var _isEqual = _interopRequireDefault(require("lodash/isEqual")); + +class HtmlElementTypeError extends Error { + constructor(received, matcherFn, context) { + super(); + /* istanbul ignore next */ + + if (Error.captureStackTrace) { + Error.captureStackTrace(this, matcherFn); + } + + let withType = ''; + + try { + withType = (0, _jestMatcherUtils.printWithType)('Received', received, _jestMatcherUtils.printReceived); + } catch (e) {// Can throw for Document: + // https://github.com/jsdom/jsdom/issues/2304 + } + + this.message = [(0, _jestMatcherUtils.matcherHint)(`${context.isNot ? '.not' : ''}.${matcherFn.name}`, 'received', ''), '', `${(0, _jestMatcherUtils.RECEIVED_COLOR)('received')} value must be an HTMLElement or an SVGElement.`, withType].join('\n'); + } + +} + +exports.HtmlElementTypeError = HtmlElementTypeError; + +function checkHasWindow(htmlElement, ...args) { + if (!htmlElement || !htmlElement.ownerDocument || !htmlElement.ownerDocument.defaultView) { + throw new HtmlElementTypeError(htmlElement, ...args); + } +} + +function checkHtmlElement(htmlElement, ...args) { + checkHasWindow(htmlElement, ...args); + const window = htmlElement.ownerDocument.defaultView; + + if (!(htmlElement instanceof window.HTMLElement) && !(htmlElement instanceof window.SVGElement)) { + throw new HtmlElementTypeError(htmlElement, ...args); + } +} + +class InvalidCSSError extends Error { + constructor(received, matcherFn) { + super(); + /* istanbul ignore next */ + + if (Error.captureStackTrace) { + Error.captureStackTrace(this, matcherFn); + } + + this.message = [received.message, '', (0, _jestMatcherUtils.RECEIVED_COLOR)(`Failing css:`), (0, _jestMatcherUtils.RECEIVED_COLOR)(`${received.css}`)].join('\n'); + } + +} + +function parseCSS(css, ...args) { + const ast = (0, _css.parse)(`selector { ${css} }`, { + silent: true + }).stylesheet; + + if (ast.parsingErrors && ast.parsingErrors.length > 0) { + const { + reason, + line + } = ast.parsingErrors[0]; + throw new InvalidCSSError({ + css, + message: `Syntax error parsing expected css: ${reason} on line: ${line}` + }, ...args); + } + + const parsedRules = ast.rules[0].declarations.filter(d => d.type === 'declaration').reduce((obj, { + property, + value + }) => Object.assign(obj, { + [property]: value + }), {}); + return parsedRules; +} + +function display(value) { + return typeof value === 'string' ? value : (0, _jestMatcherUtils.stringify)(value); +} + +function getMessage(matcher, expectedLabel, expectedValue, receivedLabel, receivedValue) { + return [`${matcher}\n`, `${expectedLabel}:\n${(0, _jestMatcherUtils.EXPECTED_COLOR)((0, _redent.default)(display(expectedValue), 2))}`, `${receivedLabel}:\n${(0, _jestMatcherUtils.RECEIVED_COLOR)((0, _redent.default)(display(receivedValue), 2))}`].join('\n'); +} + +function matches(textToMatch, matcher) { + if (matcher instanceof RegExp) { + return matcher.test(textToMatch); + } else { + return textToMatch.includes(String(matcher)); + } +} + +function deprecate(name, replacementText) { + // Notify user that they are using deprecated functionality. + // eslint-disable-next-line no-console + console.warn(`Warning: ${name} has been deprecated and will be removed in future updates.`, replacementText); +} + +function normalize(text) { + return text.replace(/\s+/g, ' ').trim(); +} + +function getTag(element) { + return element.tagName && element.tagName.toLowerCase(); +} + +function getSelectValue({ + multiple, + options +}) { + const selectedOptions = [...options].filter(option => option.selected); + + if (multiple) { + return [...selectedOptions].map(opt => opt.value); + } + /* istanbul ignore if */ + + + if (selectedOptions.length === 0) { + return undefined; // Couldn't make this happen, but just in case + } + + return selectedOptions[0].value; +} + +function getInputValue(inputElement) { + switch (inputElement.type) { + case 'number': + return inputElement.value === '' ? null : Number(inputElement.value); + + case 'checkbox': + return inputElement.checked; + + default: + return inputElement.value; + } +} + +function getSingleElementValue(element) { + /* istanbul ignore if */ + if (!element) { + return undefined; + } + + switch (element.tagName.toLowerCase()) { + case 'input': + return getInputValue(element); + + case 'select': + return getSelectValue(element); + + default: + return element.value; + } +} + +function compareArraysAsSet(a, b) { + if (Array.isArray(a) && Array.isArray(b)) { + return (0, _isEqual.default)(new Set(a), new Set(b)); + } + + return undefined; +} \ No newline at end of file