expect-webdriverio 5.7.0 → 6.0.1
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 +16 -14
- package/lib/constants.js +4 -0
- package/lib/index.js +30 -18
- package/lib/matchers/asymmetrics/asymmetricsUtils.js +24 -0
- package/lib/matchers/asymmetrics/oneOf.js +51 -0
- package/lib/matchers/browser/toHaveClipboardText.js +8 -9
- package/lib/matchers/browser/toHaveLocalStorageItem.js +22 -14
- package/lib/matchers/browser/toHaveTitle.js +9 -10
- package/lib/matchers/browser/toHaveUrl.js +8 -9
- package/lib/matchers/element/toBeExisting.js +8 -3
- package/lib/matchers/element/toBeSelected.js +6 -13
- package/lib/matchers/element/toHaveAttribute.js +46 -45
- package/lib/matchers/element/toHaveChildren.js +30 -32
- package/lib/matchers/element/toHaveComputedLabel.js +21 -20
- package/lib/matchers/element/toHaveComputedRole.js +21 -20
- package/lib/matchers/element/toHaveElementClass.js +57 -0
- package/lib/matchers/element/toHaveElementProperty.js +40 -32
- package/lib/matchers/element/toHaveHTML.js +22 -23
- package/lib/matchers/element/toHaveHeight.js +23 -28
- package/lib/matchers/element/toHaveSize.js +16 -14
- package/lib/matchers/element/toHaveStyle.js +20 -14
- package/lib/matchers/element/toHaveText.js +26 -44
- package/lib/matchers/element/toHaveWidth.js +23 -28
- package/lib/matchers/elements/toBeElementsArrayOfSize.js +25 -30
- package/lib/matchers/mock/toBeRequestedTimes.js +12 -17
- package/lib/matchers/mock/toBeRequestedWith.js +8 -9
- package/lib/matchers/modifiers/some.js +15 -0
- package/lib/matchers.js +1 -1
- package/lib/tsconfig.build.tsbuildinfo +1 -1
- package/lib/util/commandOptionsUtils.js +6 -0
- package/lib/util/elementsUtil.js +74 -4
- package/lib/util/executeCommand.js +94 -6
- package/lib/util/formatMessage.js +87 -32
- package/lib/util/numberOptionsUtil.js +92 -0
- package/lib/util/refetchElements.js +30 -5
- package/lib/util/stringUtil.js +12 -0
- package/lib/util/waitUntil.js +44 -0
- package/lib/utils.js +84 -91
- package/package.json +11 -10
- package/types/expect-webdriverio.d.ts +587 -134
- package/lib/matchers/element/toHaveClass.js +0 -61
|
@@ -1,46 +1,41 @@
|
|
|
1
1
|
import { DEFAULT_OPTIONS } from '../../constants.js';
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import { wrapExpectedWithArray } from '../../util/elementsUtil.js';
|
|
3
|
+
import { executeCommandWithStrategy } from '../../util/executeCommand.js';
|
|
4
|
+
import { validateNumberArrayAndExtractOptions } from '../../util/numberOptionsUtil.js';
|
|
5
|
+
import { enhanceError, waitUntil, } from '../../utils.js';
|
|
6
|
+
async function condition(el, expectedNumber) {
|
|
4
7
|
const actualWidth = await el.getSize('width');
|
|
5
8
|
return {
|
|
6
|
-
|
|
7
|
-
|
|
9
|
+
success: expectedNumber?.asymmetricMatch(actualWidth) ?? false,
|
|
10
|
+
actual: actualWidth
|
|
8
11
|
};
|
|
9
12
|
}
|
|
10
13
|
export async function toHaveWidth(received, expectedValue, options = DEFAULT_OPTIONS) {
|
|
11
|
-
const isNot = this
|
|
12
|
-
const { expectation = 'width', verb = 'have' } = this;
|
|
14
|
+
const { expectation = 'width', verb = 'have', isNot, matcherName = 'toHaveWidth' } = this;
|
|
13
15
|
await options.beforeAssertion?.({
|
|
14
|
-
matcherName
|
|
16
|
+
matcherName,
|
|
15
17
|
expectedValue,
|
|
16
18
|
options,
|
|
17
19
|
});
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
const result = await executeCommand.call(this, el, condition, numberOptions, [expectedValue, numberOptions]);
|
|
32
|
-
el = result.el;
|
|
33
|
-
actualWidth = result.values;
|
|
34
|
-
return result.success;
|
|
35
|
-
}, isNot, { ...numberOptions, ...options });
|
|
36
|
-
const error = numberError(numberOptions);
|
|
37
|
-
const message = enhanceError(el, error, actualWidth, this, verb, expectation, '', options);
|
|
20
|
+
const { numberMatcher: expectedNumber, commandOptions } = validateNumberArrayAndExtractOptions(expectedValue, options);
|
|
21
|
+
const { success: pass, actual: actualWidth, subject: elements, context: { isSome } = {} } = await waitUntil(async (iteration) => {
|
|
22
|
+
return await executeCommandWithStrategy({
|
|
23
|
+
unresolvedElements: received,
|
|
24
|
+
expectedValues: expectedNumber,
|
|
25
|
+
singleElementCompare: (element, expectedNumber) => condition(element, expectedNumber),
|
|
26
|
+
context: { isNot, iteration },
|
|
27
|
+
strategy: 'NewStrictMultipleElements',
|
|
28
|
+
strictConfiguration: { allowArrayWithSingleElement: false }
|
|
29
|
+
});
|
|
30
|
+
}, isNot, { wait: commandOptions.wait, interval: commandOptions.interval });
|
|
31
|
+
const expectedValues = wrapExpectedWithArray(elements, actualWidth, expectedNumber);
|
|
32
|
+
const message = enhanceError(elements, expectedValues, actualWidth, { isNot, isSome }, verb, expectation, '', commandOptions);
|
|
38
33
|
const result = {
|
|
39
34
|
pass,
|
|
40
35
|
message: () => message
|
|
41
36
|
};
|
|
42
37
|
await options.afterAssertion?.({
|
|
43
|
-
matcherName
|
|
38
|
+
matcherName,
|
|
44
39
|
expectedValue,
|
|
45
40
|
options,
|
|
46
41
|
result
|
|
@@ -1,47 +1,42 @@
|
|
|
1
|
-
import { waitUntil, enhanceError,
|
|
2
|
-
import { refetchElements } from '../../util/refetchElements.js';
|
|
1
|
+
import { waitUntil, enhanceError, } from '../../utils.js';
|
|
2
|
+
import { refetchElements, syncronizeElements } from '../../util/refetchElements.js';
|
|
3
3
|
import { DEFAULT_OPTIONS } from '../../constants.js';
|
|
4
|
+
import { validateNumberAndExtractOptions } from '../../util/numberOptionsUtil.js';
|
|
5
|
+
import { awaitElementArray, isStrictlyElementArray } from '../../util/elementsUtil.js';
|
|
4
6
|
export async function toBeElementsArrayOfSize(received, expectedValue, options = DEFAULT_OPTIONS) {
|
|
5
|
-
const isNot = this
|
|
6
|
-
const { expectation = 'elements array of size', verb = 'be' } = this;
|
|
7
|
+
const { expectation = 'elements array of size', verb = 'be', isNot, matcherName = 'toBeElementsArrayOfSize' } = this;
|
|
7
8
|
await options.beforeAssertion?.({
|
|
8
|
-
matcherName
|
|
9
|
+
matcherName,
|
|
9
10
|
expectedValue,
|
|
10
11
|
options,
|
|
11
12
|
});
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
let elements = await received;
|
|
23
|
-
const originalLength = elements.length;
|
|
24
|
-
const pass = await waitUntil(async () => {
|
|
25
|
-
const isPassing = compareNumbers(elements.length, numberOptions);
|
|
26
|
-
if (isPassing) {
|
|
27
|
-
return isPassing;
|
|
13
|
+
const { numberMatcher: expectedNumber, commandOptions } = validateNumberAndExtractOptions(expectedValue, options);
|
|
14
|
+
let { elements, other } = await awaitElementArray(received);
|
|
15
|
+
const originalLength = elements ? elements.length : undefined;
|
|
16
|
+
const { success: pass } = await waitUntil(async (iteration) => {
|
|
17
|
+
if (!elements) {
|
|
18
|
+
return { success: false, subject: elements, actual: undefined, abort: true };
|
|
19
|
+
}
|
|
20
|
+
else if (iteration > 0) {
|
|
21
|
+
elements = await refetchElements(elements);
|
|
28
22
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (Array.isArray(received) && pass) {
|
|
33
|
-
for (let index = originalLength; index < elements.length; index++) {
|
|
34
|
-
received.push(elements[index]);
|
|
23
|
+
const isPassing = expectedNumber.asymmetricMatch(elements.length);
|
|
24
|
+
if (isPassing) {
|
|
25
|
+
return { success: isPassing, subject: elements, actual: elements.length };
|
|
35
26
|
}
|
|
27
|
+
return { success: false, subject: elements, actual: elements.length };
|
|
28
|
+
}, isNot, { wait: commandOptions.wait, interval: commandOptions.interval });
|
|
29
|
+
if (pass && originalLength !== undefined && elements !== received && (isStrictlyElementArray(received) || received instanceof Promise) && isStrictlyElementArray(elements)) {
|
|
30
|
+
await syncronizeElements(received, elements);
|
|
36
31
|
}
|
|
37
|
-
const
|
|
38
|
-
const message = enhanceError(elements,
|
|
32
|
+
const actual = originalLength;
|
|
33
|
+
const message = enhanceError(elements ?? other, expectedNumber, actual, this, verb, expectation, '', commandOptions);
|
|
39
34
|
const result = {
|
|
40
35
|
pass,
|
|
41
36
|
message: () => message
|
|
42
37
|
};
|
|
43
38
|
await options.afterAssertion?.({
|
|
44
|
-
matcherName
|
|
39
|
+
matcherName,
|
|
45
40
|
expectedValue,
|
|
46
41
|
options,
|
|
47
42
|
result
|
|
@@ -1,30 +1,25 @@
|
|
|
1
|
-
import { waitUntil, enhanceError
|
|
2
|
-
import { numberError } from '../../util/formatMessage.js';
|
|
1
|
+
import { waitUntil, enhanceError } from '../../utils.js';
|
|
3
2
|
import { DEFAULT_OPTIONS } from '../../constants.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const { expectation = `called${typeof expectedValue === 'number' ? ' ' + expectedValue : ''} time${expectedValue !== 1 ? 's' : ''}`,
|
|
3
|
+
import { validateNumberAndExtractOptions } from '../../util/numberOptionsUtil.js';
|
|
4
|
+
export async function toBeRequestedTimes(received, expectedValue, options = DEFAULT_OPTIONS) {
|
|
5
|
+
const { verb = 'be', isNot, matcherName = 'toBeRequestedTimes', expectation = `called${typeof expectedValue === 'number' ? ' ' + expectedValue : ''} time${expectedValue !== 1 ? 's' : ''}`, } = this;
|
|
7
6
|
await options.beforeAssertion?.({
|
|
8
|
-
matcherName
|
|
7
|
+
matcherName,
|
|
9
8
|
expectedValue,
|
|
10
9
|
options,
|
|
11
10
|
});
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return compareNumbers(actual, numberOptions);
|
|
19
|
-
}, isNot, { ...numberOptions, ...options });
|
|
20
|
-
const error = numberError(numberOptions);
|
|
21
|
-
const message = enhanceError('mock', error, actual, this, verb, expectation, '', numberOptions);
|
|
11
|
+
const { numberMatcher: expectedNumber, commandOptions } = validateNumberAndExtractOptions(expectedValue, options);
|
|
12
|
+
const { success: pass, actual } = await waitUntil(async () => {
|
|
13
|
+
const actual = received.calls.length;
|
|
14
|
+
return { success: expectedNumber.asymmetricMatch(actual), subject: received, actual };
|
|
15
|
+
}, isNot, { wait: commandOptions.wait, interval: commandOptions.interval });
|
|
16
|
+
const message = enhanceError('mock', expectedNumber, actual, this, verb, expectation, '', commandOptions);
|
|
22
17
|
const result = {
|
|
23
18
|
pass,
|
|
24
19
|
message: () => message
|
|
25
20
|
};
|
|
26
21
|
await options.afterAssertion?.({
|
|
27
|
-
matcherName
|
|
22
|
+
matcherName,
|
|
28
23
|
expectedValue,
|
|
29
24
|
options,
|
|
30
25
|
result
|
|
@@ -10,26 +10,25 @@ function reduceHeaders(headers) {
|
|
|
10
10
|
}, {});
|
|
11
11
|
}
|
|
12
12
|
export async function toBeRequestedWith(received, expectedValue = {}, options = DEFAULT_OPTIONS) {
|
|
13
|
-
const
|
|
14
|
-
const { expectation = 'called with', verb = 'be' } = this;
|
|
13
|
+
const { expectation = 'called with', verb = 'be', isNot, matcherName = 'toBeRequestedWith' } = this;
|
|
15
14
|
await options.beforeAssertion?.({
|
|
16
|
-
matcherName
|
|
15
|
+
matcherName,
|
|
17
16
|
expectedValue,
|
|
18
17
|
options,
|
|
19
18
|
});
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
const { success: pass, actual } = await waitUntil(async () => {
|
|
20
|
+
let lastCall;
|
|
22
21
|
for (const call of received.calls) {
|
|
23
|
-
|
|
22
|
+
lastCall = call;
|
|
24
23
|
if (methodMatcher(call.request.method, expectedValue.method) &&
|
|
25
24
|
statusCodeMatcher(call.response.status, expectedValue.statusCode) &&
|
|
26
25
|
urlMatcher(call.request.url, expectedValue.url) &&
|
|
27
26
|
headersMatcher(reduceHeaders(call.request.headers), expectedValue.requestHeaders) &&
|
|
28
27
|
headersMatcher(reduceHeaders(call.response.headers), expectedValue.responseHeaders)) {
|
|
29
|
-
return true;
|
|
28
|
+
return { success: true, subject: call, actual: call };
|
|
30
29
|
}
|
|
31
30
|
}
|
|
32
|
-
return false;
|
|
31
|
+
return { success: false, subject: lastCall, actual: lastCall };
|
|
33
32
|
}, isNot, { ...options, wait: isNot ? 0 : options.wait });
|
|
34
33
|
const message = enhanceError('mock', minifyRequestedWith(expectedValue), minifyRequestMock(actual, expectedValue) || 'was not called', this, verb, expectation, '', options);
|
|
35
34
|
const result = {
|
|
@@ -37,7 +36,7 @@ export async function toBeRequestedWith(received, expectedValue = {}, options =
|
|
|
37
36
|
message: () => message
|
|
38
37
|
};
|
|
39
38
|
await options.afterAssertion?.({
|
|
40
|
-
matcherName
|
|
39
|
+
matcherName,
|
|
41
40
|
expectedValue,
|
|
42
41
|
options,
|
|
43
42
|
result
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const SOME_TAG = 'expect-webdriverio.some';
|
|
2
|
+
const SOME_SYMBOL = Symbol.for(SOME_TAG);
|
|
3
|
+
export class SomeElementsWrapper {
|
|
4
|
+
elements;
|
|
5
|
+
[SOME_SYMBOL] = true;
|
|
6
|
+
constructor(elements) {
|
|
7
|
+
this.elements = elements;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function some(elements) {
|
|
11
|
+
return new SomeElementsWrapper(elements);
|
|
12
|
+
}
|
|
13
|
+
export function isSomeWrapper(value) {
|
|
14
|
+
return value instanceof SomeElementsWrapper;
|
|
15
|
+
}
|
package/lib/matchers.js
CHANGED
|
@@ -12,9 +12,9 @@ export * from './matchers/element/toBeFocused.js';
|
|
|
12
12
|
export * from './matchers/element/toBeSelected.js';
|
|
13
13
|
export * from './matchers/element/toHaveAttribute.js';
|
|
14
14
|
export * from './matchers/element/toHaveChildren.js';
|
|
15
|
-
export * from './matchers/element/toHaveClass.js';
|
|
16
15
|
export * from './matchers/element/toHaveComputedLabel.js';
|
|
17
16
|
export * from './matchers/element/toHaveComputedRole.js';
|
|
17
|
+
export * from './matchers/element/toHaveElementClass.js';
|
|
18
18
|
export * from './matchers/element/toHaveElementProperty.js';
|
|
19
19
|
export * from './matchers/element/toHaveHeight.js';
|
|
20
20
|
export * from './matchers/element/toHaveHref.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/constants.ts","../src/index.ts","../src/jasmineUtils.ts","../src/matchers.ts","../src/snapshot.ts","../src/softAssert.ts","../src/softAssertService.ts","../src/softExpect.ts","../src/types.ts","../src/utils.ts","../src/matchers/snapshot.ts","../src/matchers/browser/toHaveClipboardText.ts","../src/matchers/browser/toHaveLocalStorageItem.ts","../src/matchers/browser/toHaveTitle.ts","../src/matchers/browser/toHaveUrl.ts","../src/matchers/element/toBeClickable.ts","../src/matchers/element/toBeDisabled.ts","../src/matchers/element/toBeDisplayed.ts","../src/matchers/element/toBeDisplayedInViewport.ts","../src/matchers/element/toBeEnabled.ts","../src/matchers/element/toBeExisting.ts","../src/matchers/element/toBeFocused.ts","../src/matchers/element/toBeSelected.ts","../src/matchers/element/toHaveAttribute.ts","../src/matchers/element/toHaveChildren.ts","../src/matchers/element/
|
|
1
|
+
{"root":["../src/constants.ts","../src/index.ts","../src/jasmineUtils.ts","../src/matchers.ts","../src/snapshot.ts","../src/softAssert.ts","../src/softAssertService.ts","../src/softExpect.ts","../src/types.ts","../src/utils.ts","../src/matchers/snapshot.ts","../src/matchers/asymmetrics/asymmetricsUtils.ts","../src/matchers/asymmetrics/oneOf.ts","../src/matchers/browser/toHaveClipboardText.ts","../src/matchers/browser/toHaveLocalStorageItem.ts","../src/matchers/browser/toHaveTitle.ts","../src/matchers/browser/toHaveUrl.ts","../src/matchers/element/toBeClickable.ts","../src/matchers/element/toBeDisabled.ts","../src/matchers/element/toBeDisplayed.ts","../src/matchers/element/toBeDisplayedInViewport.ts","../src/matchers/element/toBeEnabled.ts","../src/matchers/element/toBeExisting.ts","../src/matchers/element/toBeFocused.ts","../src/matchers/element/toBeSelected.ts","../src/matchers/element/toHaveAttribute.ts","../src/matchers/element/toHaveChildren.ts","../src/matchers/element/toHaveComputedLabel.ts","../src/matchers/element/toHaveComputedRole.ts","../src/matchers/element/toHaveElementClass.ts","../src/matchers/element/toHaveElementProperty.ts","../src/matchers/element/toHaveHTML.ts","../src/matchers/element/toHaveHeight.ts","../src/matchers/element/toHaveHref.ts","../src/matchers/element/toHaveId.ts","../src/matchers/element/toHaveSize.ts","../src/matchers/element/toHaveStyle.ts","../src/matchers/element/toHaveText.ts","../src/matchers/element/toHaveValue.ts","../src/matchers/element/toHaveWidth.ts","../src/matchers/elements/toBeElementsArrayOfSize.ts","../src/matchers/mock/toBeRequested.ts","../src/matchers/mock/toBeRequestedTimes.ts","../src/matchers/mock/toBeRequestedWith.ts","../src/matchers/modifiers/some.ts","../src/util/commandOptionsUtils.ts","../src/util/elementsUtil.ts","../src/util/executeCommand.ts","../src/util/formatMessage.ts","../src/util/numberOptionsUtil.ts","../src/util/refetchElements.ts","../src/util/stringUtil.ts","../src/util/waitUntil.ts"],"version":"5.9.3"}
|
package/lib/util/elementsUtil.js
CHANGED
|
@@ -1,9 +1,79 @@
|
|
|
1
|
-
export const wrapExpectedWithArray = (
|
|
2
|
-
if (Array.isArray(
|
|
3
|
-
expected =
|
|
1
|
+
export const wrapExpectedWithArray = (elements, actual, expected) => {
|
|
2
|
+
if (Array.isArray(elements) && Array.isArray(actual) && !Array.isArray(expected)) {
|
|
3
|
+
expected = Array(actual.length).fill(expected);
|
|
4
4
|
}
|
|
5
5
|
return expected;
|
|
6
6
|
};
|
|
7
|
+
export const fillSingleExpectedForElementArray = (subject, value) => {
|
|
8
|
+
if (isElementArrayLike(subject) && !Array.isArray(value)) {
|
|
9
|
+
const fillerlength = subject.length > 0 ? subject.length : 1;
|
|
10
|
+
return Array(fillerlength).fill(value);
|
|
11
|
+
}
|
|
12
|
+
return value;
|
|
13
|
+
};
|
|
14
|
+
export const isArray = (obj) => {
|
|
15
|
+
return Array.isArray(obj);
|
|
16
|
+
};
|
|
17
|
+
export const isSelector = (obj) => {
|
|
18
|
+
return !!obj
|
|
19
|
+
&& typeof obj === 'object'
|
|
20
|
+
&& 'selector' in obj
|
|
21
|
+
&& 'parent' in obj;
|
|
22
|
+
};
|
|
7
23
|
export const isElementArray = (obj) => {
|
|
8
|
-
return obj
|
|
24
|
+
return isSelector(obj)
|
|
25
|
+
&& 'foundWith' in obj;
|
|
26
|
+
};
|
|
27
|
+
export const isStrictlyElementArray = (obj) => {
|
|
28
|
+
return isElementArray(obj)
|
|
29
|
+
&& Array.isArray(obj)
|
|
30
|
+
&& 'getElements' in obj;
|
|
31
|
+
};
|
|
32
|
+
export const isElement = (obj) => {
|
|
33
|
+
return isSelector(obj)
|
|
34
|
+
&& !Array.isArray(obj)
|
|
35
|
+
&& 'getElement' in obj;
|
|
36
|
+
};
|
|
37
|
+
export const isElementArrayLike = (obj) => {
|
|
38
|
+
return !!obj && (isStrictlyElementArray(obj) || (Array.isArray(obj) && obj.every(isElement)));
|
|
39
|
+
};
|
|
40
|
+
export const isArrayOfElement = (obj) => {
|
|
41
|
+
return Array.isArray(obj) && obj.every(isElement);
|
|
42
|
+
};
|
|
43
|
+
export const isElementOrArrayLike = (obj) => {
|
|
44
|
+
return !!obj && (isElement(obj) || isElementArrayLike(obj));
|
|
45
|
+
};
|
|
46
|
+
export const awaitElementOrArray = async (received) => {
|
|
47
|
+
if (!received || typeof received !== 'object') {
|
|
48
|
+
return { other: received };
|
|
49
|
+
}
|
|
50
|
+
let awaitedElements = received;
|
|
51
|
+
if (awaitedElements instanceof Promise) {
|
|
52
|
+
awaitedElements = await awaitedElements;
|
|
53
|
+
}
|
|
54
|
+
if (!isElementOrArrayLike(awaitedElements)) {
|
|
55
|
+
return { other: awaitedElements };
|
|
56
|
+
}
|
|
57
|
+
if ('getElement' in awaitedElements) {
|
|
58
|
+
const element = await awaitedElements.getElement();
|
|
59
|
+
return { selector: element, element };
|
|
60
|
+
}
|
|
61
|
+
if ('getElements' in awaitedElements) {
|
|
62
|
+
const elements = await awaitedElements.getElements();
|
|
63
|
+
return { selector: elements, elements, isEmptyElements: elements.length === 0 };
|
|
64
|
+
}
|
|
65
|
+
return { selector: awaitedElements, elements: awaitedElements, isEmptyElements: awaitedElements.length === 0 };
|
|
66
|
+
};
|
|
67
|
+
export const awaitElementArray = async (received) => {
|
|
68
|
+
let awaitedElements = received;
|
|
69
|
+
if (awaitedElements instanceof Promise) {
|
|
70
|
+
awaitedElements = await awaitedElements;
|
|
71
|
+
}
|
|
72
|
+
if (!isElementArrayLike(awaitedElements)) {
|
|
73
|
+
return { other: awaitedElements };
|
|
74
|
+
}
|
|
75
|
+
if ('getElements' in awaitedElements) {
|
|
76
|
+
return { elements: await awaitedElements.getElements() };
|
|
77
|
+
}
|
|
78
|
+
return { elements: awaitedElements };
|
|
9
79
|
};
|
|
@@ -1,8 +1,96 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { isSomeWrapper } from '../matchers/modifiers/some.js';
|
|
2
|
+
import { awaitElementOrArray, isElement, isStrictlyElementArray } from './elementsUtil.js';
|
|
3
|
+
import { refreshElementArray } from './refetchElements.js';
|
|
4
|
+
export async function executeCommandWithStrategy({ unresolvedElements, expectedValues, singleElementCompare, context: { isNot, iteration }, strategy = 'NewStrictMultipleElements', strictConfiguration = { allowEmptyElements: false, allowArrayWithSingleElement: false } }) {
|
|
5
|
+
const isSome = isSomeWrapper(unresolvedElements);
|
|
6
|
+
if (strategy === 'LegacyLooseMultipleElements') {
|
|
7
|
+
if (isSome) {
|
|
8
|
+
throw new Error('some(elements) works only when enabling `useToHaveTextStrictMultiElementsCompareStrategy`');
|
|
9
|
+
}
|
|
10
|
+
return legacyMultipleElementResultsStrategy(unresolvedElements, expectedValues, singleElementCompare, isNot);
|
|
11
|
+
}
|
|
12
|
+
const actualReceived = isSome ? unresolvedElements.elements : unresolvedElements;
|
|
13
|
+
return multipleElementResultsStrategy(actualReceived, expectedValues, singleElementCompare, { isNot, isSome, iteration }, strictConfiguration);
|
|
14
|
+
}
|
|
15
|
+
export const legacyMultipleElementResultsStrategy = async (unresolvedElements, expectedValues, singleElementCompare, _isNot) => {
|
|
16
|
+
const { selector, other, isEmptyElements } = await awaitElementOrArray(unresolvedElements);
|
|
17
|
+
const subject = selector ?? other;
|
|
18
|
+
if (!selector || isEmptyElements) {
|
|
19
|
+
return {
|
|
20
|
+
subject: subject,
|
|
21
|
+
success: false,
|
|
22
|
+
actual: undefined,
|
|
23
|
+
abort: true,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
if (isElement(selector)) {
|
|
27
|
+
const compareResult = await singleElementCompare(selector, expectedValues);
|
|
28
|
+
return {
|
|
29
|
+
subject,
|
|
30
|
+
...compareResult,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const settled = await Promise.allSettled(Array.from(selector).map((element, index) => singleElementCompare(element, expectedValues, index)));
|
|
34
|
+
const firstRejection = settled.find((r) => r.status === 'rejected');
|
|
35
|
+
if (firstRejection) {
|
|
36
|
+
throw firstRejection.reason;
|
|
37
|
+
}
|
|
38
|
+
const results = settled.map((r) => r.value);
|
|
3
39
|
return {
|
|
4
|
-
|
|
5
|
-
success:
|
|
6
|
-
|
|
40
|
+
subject,
|
|
41
|
+
success: results.length > 0 && results.every((res) => res.success === true),
|
|
42
|
+
actual: results.map(({ actual: value }) => value),
|
|
7
43
|
};
|
|
8
|
-
}
|
|
44
|
+
};
|
|
45
|
+
export const multipleElementResultsStrategy = async (unresolvedElements, expectedValues, singleElementCompare, { isNot, isSome, iteration }, { allowEmptyElements = false, allowArrayWithSingleElement = false } = {}) => {
|
|
46
|
+
const { selector, other } = await awaitElementOrArray(unresolvedElements);
|
|
47
|
+
if (iteration > 0 && isStrictlyElementArray(selector)) {
|
|
48
|
+
await refreshElementArray(selector);
|
|
49
|
+
}
|
|
50
|
+
const subject = selector ?? other;
|
|
51
|
+
if (!selector || (Array.isArray(selector) && selector.length === 0)) {
|
|
52
|
+
return {
|
|
53
|
+
subject,
|
|
54
|
+
success: isNot ? !allowEmptyElements : false,
|
|
55
|
+
actual: undefined,
|
|
56
|
+
abort: !allowEmptyElements && !isStrictlyElementArray(selector),
|
|
57
|
+
context: { isSome },
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (isElement(selector)) {
|
|
61
|
+
const forceFailure = !allowArrayWithSingleElement && Array.isArray(expectedValues);
|
|
62
|
+
const compareResult = await singleElementCompare(selector, forceFailure ? undefined : expectedValues);
|
|
63
|
+
const success = forceFailure ? !!isNot : compareResult.success;
|
|
64
|
+
return { subject, success, actual: compareResult.actual, abort: forceFailure, context: { isSome } };
|
|
65
|
+
}
|
|
66
|
+
const lengthMismatch = Array.isArray(expectedValues) && expectedValues.length !== selector.length;
|
|
67
|
+
const settled = await Promise.allSettled(Array.from(selector).map(async (element, index) => {
|
|
68
|
+
const indexedExpected = Array.isArray(expectedValues) ? expectedValues[index] : expectedValues;
|
|
69
|
+
const forceElementFailure = Array.isArray(indexedExpected)
|
|
70
|
+
|| (lengthMismatch && Array.isArray(expectedValues) && index >= expectedValues.length);
|
|
71
|
+
const compareResult = await singleElementCompare(element, forceElementFailure ? undefined : indexedExpected, index);
|
|
72
|
+
return forceElementFailure ? { success: false, actual: compareResult.actual } : compareResult;
|
|
73
|
+
}));
|
|
74
|
+
const firstRejection = settled.find((r) => r.status === 'rejected');
|
|
75
|
+
if (firstRejection) {
|
|
76
|
+
throw firstRejection.reason;
|
|
77
|
+
}
|
|
78
|
+
const results = settled.map((r) => r.value);
|
|
79
|
+
if (Array.isArray(expectedValues) && expectedValues.length > selector.length) {
|
|
80
|
+
results.push(...Array(expectedValues.length - selector.length).fill({ success: false, actual: undefined }));
|
|
81
|
+
}
|
|
82
|
+
if (lengthMismatch) {
|
|
83
|
+
return { subject, success: !!isNot, actual: results.map(({ actual }) => actual), context: { isSome } };
|
|
84
|
+
}
|
|
85
|
+
const isNotEmpty = results.length > 0;
|
|
86
|
+
const checkFn = isSome ? isAtLeastOneTrue : isAllTrue;
|
|
87
|
+
const checkNotFn = isSome ? isAtLeastOneFalse : isAllFalse;
|
|
88
|
+
const success = isNot
|
|
89
|
+
? !(isNotEmpty && checkNotFn(results))
|
|
90
|
+
: isNotEmpty && checkFn(results);
|
|
91
|
+
return { subject, success, actual: results.map(({ actual }) => actual), context: { isSome } };
|
|
92
|
+
};
|
|
93
|
+
const isAllTrue = (results) => results.every((res) => res.success === true);
|
|
94
|
+
const isAllFalse = (results) => results.every((res) => res.success === false);
|
|
95
|
+
const isAtLeastOneTrue = (results) => results.some((res) => res.success === true);
|
|
96
|
+
const isAtLeastOneFalse = (results) => results.some((res) => res.success === false);
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { printDiffOrStringify, printExpected, printReceived } from 'jest-matcher-utils';
|
|
1
|
+
import { printDiffOrStringify, printExpected, printReceived, RECEIVED_COLOR, EXPECTED_COLOR, INVERTED_COLOR, stringify } from 'jest-matcher-utils';
|
|
2
2
|
import { equals } from '../jasmineUtils.js';
|
|
3
|
-
import {
|
|
3
|
+
import { isArrayOfElement, isElementArrayLike, isElementOrArrayLike, isStrictlyElementArray } from './elementsUtil.js';
|
|
4
|
+
import { toJsonString } from './stringUtil.js';
|
|
5
|
+
import { isJasmineStringAsymmetricMatcher, toArray } from '../utils.js';
|
|
6
|
+
export const isDefined = (value) => value !== null && value !== undefined;
|
|
4
7
|
export const getSelector = (el) => {
|
|
5
8
|
let result = typeof el.selector === 'string' ? el.selector : '<fn>';
|
|
6
9
|
if (Array.isArray(el) && el.props.length > 0) {
|
|
@@ -9,27 +12,36 @@ export const getSelector = (el) => {
|
|
|
9
12
|
return result;
|
|
10
13
|
};
|
|
11
14
|
export const getSelectors = (el) => {
|
|
15
|
+
if (!el || typeof el !== 'object') {
|
|
16
|
+
return '';
|
|
17
|
+
}
|
|
12
18
|
const selectors = [];
|
|
13
19
|
let parent;
|
|
14
|
-
if (
|
|
20
|
+
if (isStrictlyElementArray(el)) {
|
|
15
21
|
selectors.push(`${(el).foundWith}(\`${getSelector(el)}\`)`);
|
|
16
22
|
parent = el.parent;
|
|
17
23
|
}
|
|
18
|
-
else if (
|
|
24
|
+
else if (isArrayOfElement(el)) {
|
|
25
|
+
return `[${el.map(getSelectors).join(',')}]`;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
19
28
|
parent = el;
|
|
20
29
|
}
|
|
21
|
-
while (parent && 'selector' in parent) {
|
|
30
|
+
while (!!parent && typeof parent === 'object' && 'selector' in parent) {
|
|
22
31
|
const selector = getSelector(parent);
|
|
23
|
-
const index = parent.index ? `[${parent.index}]` : '';
|
|
24
|
-
selectors.push(`${parent.index ? '$' : ''}$(\`${selector}\`)${index}`);
|
|
32
|
+
const index = isDefined(parent.index) ? `[${parent.index}]` : '';
|
|
33
|
+
selectors.push(`${isDefined(parent.index) ? '$' : ''}$(\`${selector}\`)${index}`);
|
|
25
34
|
parent = parent.parent;
|
|
26
35
|
}
|
|
27
36
|
return selectors.reverse().join('.');
|
|
28
37
|
};
|
|
29
38
|
const not = (isNot) => `${isNot ? 'not ' : ''}`;
|
|
30
39
|
export const enhanceError = (subject, expected, actual, context, verb, expectation, expectedValueArgument2 = '', { message = '', containing = false } = {}) => {
|
|
31
|
-
const { isNot
|
|
32
|
-
|
|
40
|
+
const { isNot, useNotInLabel = true } = context;
|
|
41
|
+
let subjectStr = (isElementOrArrayLike(subject) ? getSelectors(subject) : toJsonString(subject));
|
|
42
|
+
if (subjectStr.length > 100) {
|
|
43
|
+
subjectStr = `${subjectStr.substring(0, 100)}...`;
|
|
44
|
+
}
|
|
33
45
|
let contain = '';
|
|
34
46
|
if (containing) {
|
|
35
47
|
contain = ' containing';
|
|
@@ -37,44 +49,87 @@ export const enhanceError = (subject, expected, actual, context, verb, expectati
|
|
|
37
49
|
if (verb) {
|
|
38
50
|
verb += ' ';
|
|
39
51
|
}
|
|
52
|
+
const isNotInLabel = useNotInLabel && isNot;
|
|
40
53
|
const label = {
|
|
41
|
-
expected:
|
|
42
|
-
received:
|
|
54
|
+
expected: isNotInLabel ? 'Expected [not]' : 'Expected',
|
|
55
|
+
received: isNotInLabel ? 'Received ' : 'Received'
|
|
43
56
|
};
|
|
44
|
-
|
|
57
|
+
let diffString = '';
|
|
58
|
+
if (isJasmineStringAsymmetricMatcher(expected)) {
|
|
59
|
+
expected = expected.jasmineToString();
|
|
60
|
+
}
|
|
61
|
+
else if (isElementOrArrayLike(subject) && Array.isArray(expected)) {
|
|
62
|
+
expected = expected.map(item => isJasmineStringAsymmetricMatcher(item) ? item.jasmineToString() : item);
|
|
63
|
+
}
|
|
64
|
+
if (isNotInLabel && isElementOrArrayLike(subject) && Array.isArray(expected) && Array.isArray(actual) && expected.length === actual.length) {
|
|
65
|
+
const { expectedFormatted, receivedFormatted } = printArrayWithMatchingItemInRed(expected, actual);
|
|
66
|
+
diffString = `\
|
|
67
|
+
${label.expected}: ${expectedFormatted}
|
|
68
|
+
${label.received}: ${receivedFormatted}`;
|
|
69
|
+
}
|
|
70
|
+
else if (equals(actual, expected)) {
|
|
71
|
+
diffString =
|
|
72
|
+
`\
|
|
45
73
|
${label.expected}: ${printExpected(expected)}
|
|
46
|
-
${label.received}: ${printReceived(actual)}
|
|
47
|
-
|
|
74
|
+
${label.received}: ${printReceived(actual)}`;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
diffString = printDiffOrStringify(expected, actual, label.expected, label.received, true);
|
|
78
|
+
}
|
|
48
79
|
if (message) {
|
|
49
80
|
message += '\n';
|
|
50
81
|
}
|
|
51
82
|
if (expectedValueArgument2) {
|
|
52
83
|
expectedValueArgument2 = ` ${expectedValueArgument2}`;
|
|
53
84
|
}
|
|
85
|
+
const some = context.isSome ? 'some of ' : '';
|
|
54
86
|
const msg = `\
|
|
55
|
-
${message}Expect ${
|
|
87
|
+
${message}Expect ${some}${subjectStr} ${not(isNot)}to ${verb}${expectation}${expectedValueArgument2}${contain}
|
|
56
88
|
|
|
57
89
|
${diffString}`;
|
|
58
90
|
return msg;
|
|
59
91
|
};
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
export const numberError = (options = {}) => {
|
|
67
|
-
if (typeof options.eq === 'number') {
|
|
68
|
-
return options.eq;
|
|
69
|
-
}
|
|
70
|
-
if (options.gte && options.lte) {
|
|
71
|
-
return `>= ${options.gte} && <= ${options.lte}`;
|
|
92
|
+
const printArrayWithMatchingItemInRed = (expectedArray, actualArray) => {
|
|
93
|
+
const matchingIndices = [];
|
|
94
|
+
for (let i = 0; i < expectedArray.length; i++) {
|
|
95
|
+
if (equals(expectedArray[i], actualArray[i])) {
|
|
96
|
+
matchingIndices.push(i);
|
|
97
|
+
}
|
|
72
98
|
}
|
|
73
|
-
|
|
74
|
-
|
|
99
|
+
const expectedFormatted = `[${expectedArray
|
|
100
|
+
.map((item, i) => {
|
|
101
|
+
const stringified = stringify(item);
|
|
102
|
+
return matchingIndices.includes(i)
|
|
103
|
+
? RECEIVED_COLOR(INVERTED_COLOR(stringified))
|
|
104
|
+
: EXPECTED_COLOR(stringified);
|
|
105
|
+
})
|
|
106
|
+
.join(', ')}]`;
|
|
107
|
+
const receivedFormatted = `[${actualArray
|
|
108
|
+
.map((item, i) => {
|
|
109
|
+
const stringified = stringify(item);
|
|
110
|
+
return matchingIndices.includes(i)
|
|
111
|
+
? RECEIVED_COLOR(INVERTED_COLOR(stringified))
|
|
112
|
+
: EXPECTED_COLOR(stringified);
|
|
113
|
+
})
|
|
114
|
+
.join(', ')}]`;
|
|
115
|
+
return { expectedFormatted, receivedFormatted };
|
|
116
|
+
};
|
|
117
|
+
export const enhanceErrorBe = (subject, results, context, options) => {
|
|
118
|
+
const { isNot, verb, expectation } = context;
|
|
119
|
+
let expected;
|
|
120
|
+
let actual;
|
|
121
|
+
const expectedValue = `${not(isNot)}${expectation}`;
|
|
122
|
+
const actualValue = `${not(!isNot)}${expectation}`;
|
|
123
|
+
if (isElementArrayLike(subject)) {
|
|
124
|
+
expected = subject.length === 0 ? 'at least one result' : Array(subject.length).fill(expectedValue);
|
|
125
|
+
actual = toArray(results).map(result => isSuccess(isNot, result) ? `${not(isNot)}${expectation}` : `${not(!isNot)}${expectation}`);
|
|
75
126
|
}
|
|
76
|
-
|
|
77
|
-
|
|
127
|
+
else {
|
|
128
|
+
expected = expectedValue;
|
|
129
|
+
actual = actualValue;
|
|
78
130
|
}
|
|
79
|
-
return '
|
|
131
|
+
return enhanceError(subject, expected, actual, { ...context, useNotInLabel: false }, verb, expectation, '', options);
|
|
132
|
+
};
|
|
133
|
+
const isSuccess = (isNot, success) => {
|
|
134
|
+
return isNot ? !success : success;
|
|
80
135
|
};
|