/**
 * Copyright Microsoft Corporation. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import colors from 'colors/safe';
import { asLocatorDescription } from '@isomorphic/locatorGenerators';
import { isTextualMimeType } from '@isomorphic/mimeType';
import { isRegExp } from '@isomorphic/rtti';
import { isString } from '@isomorphic/stringUtils';
import { pollAgainstDeadline } from '@isomorphic/timeoutRunner';
import { constructURLBasedOnBaseURL, isURLPattern } from '@isomorphic/urlMatch';
import { serializeExpectedTextValues } from '@isomorphic/expectUtils';
import { monotonicTime } from '@isomorphic/index';
import { expectTypes, formatMatcherMessage } from './matcherHint';
import { toBeTruthy } from './toBeTruthy';
import { toEqual } from './toEqual';
import { toHaveURLWithPredicate } from './toHaveURL';
import { toMatchText } from './toMatchText';
import { toHaveScreenshotStepTitle } from './toMatchSnapshot';
import { expectConfig } from './expect';
export function toBeAttached(locator, options) {
    const attached = !options || options.attached === undefined || options.attached;
    const expected = attached ? 'attached' : 'detached';
    const arg = attached ? '' : '{ attached: false }';
    return toBeTruthy.call(this, 'toBeAttached', locator, 'Locator', expected, arg, async (isNot, timeout) => {
        return await locator._expect(attached ? 'to.be.attached' : 'to.be.detached', { isNot, timeout });
    }, options);
}
export function toBeChecked(locator, options) {
    const checked = options?.checked;
    const indeterminate = options?.indeterminate;
    const expectedValue = {
        checked,
        indeterminate,
    };
    let expected;
    let arg;
    if (options?.indeterminate) {
        expected = 'indeterminate';
        arg = `{ indeterminate: true }`;
    }
    else {
        expected = options?.checked === false ? 'unchecked' : 'checked';
        arg = options?.checked === false ? `{ checked: false }` : '';
    }
    return toBeTruthy.call(this, 'toBeChecked', locator, 'Locator', expected, arg, async (isNot, timeout) => {
        return await locator._expect('to.be.checked', { isNot, timeout, expectedValue });
    }, options);
}
export function toBeDisabled(locator, options) {
    return toBeTruthy.call(this, 'toBeDisabled', locator, 'Locator', 'disabled', '', async (isNot, timeout) => {
        return await locator._expect('to.be.disabled', { isNot, timeout });
    }, options);
}
export function toBeEditable(locator, options) {
    const editable = !options || options.editable === undefined || options.editable;
    const expected = editable ? 'editable' : 'readOnly';
    const arg = editable ? '' : '{ editable: false }';
    return toBeTruthy.call(this, 'toBeEditable', locator, 'Locator', expected, arg, async (isNot, timeout) => {
        return await locator._expect(editable ? 'to.be.editable' : 'to.be.readonly', { isNot, timeout });
    }, options);
}
export function toBeEmpty(locator, options) {
    return toBeTruthy.call(this, 'toBeEmpty', locator, 'Locator', 'empty', '', async (isNot, timeout) => {
        return await locator._expect('to.be.empty', { isNot, timeout });
    }, options);
}
export function toBeEnabled(locator, options) {
    const enabled = !options || options.enabled === undefined || options.enabled;
    const expected = enabled ? 'enabled' : 'disabled';
    const arg = enabled ? '' : '{ enabled: false }';
    return toBeTruthy.call(this, 'toBeEnabled', locator, 'Locator', expected, arg, async (isNot, timeout) => {
        return await locator._expect(enabled ? 'to.be.enabled' : 'to.be.disabled', { isNot, timeout });
    }, options);
}
export function toBeFocused(locator, options) {
    return toBeTruthy.call(this, 'toBeFocused', locator, 'Locator', 'focused', '', async (isNot, timeout) => {
        return await locator._expect('to.be.focused', { isNot, timeout });
    }, options);
}
export function toBeHidden(locator, options) {
    return toBeTruthy.call(this, 'toBeHidden', locator, 'Locator', 'hidden', '', async (isNot, timeout) => {
        return await locator._expect('to.be.hidden', { isNot, timeout });
    }, options);
}
export function toBeVisible(locator, options) {
    const visible = !options || options.visible === undefined || options.visible;
    const expected = visible ? 'visible' : 'hidden';
    const arg = visible ? '' : '{ visible: false }';
    return toBeTruthy.call(this, 'toBeVisible', locator, 'Locator', expected, arg, async (isNot, timeout) => {
        return await locator._expect(visible ? 'to.be.visible' : 'to.be.hidden', { isNot, timeout });
    }, options);
}
export function toBeInViewport(locator, options) {
    return toBeTruthy.call(this, 'toBeInViewport', locator, 'Locator', 'in viewport', '', async (isNot, timeout) => {
        return await locator._expect('to.be.in.viewport', { isNot, expectedNumber: options?.ratio, timeout });
    }, options);
}
export function toContainText(locator, expected, options = {}) {
    if (Array.isArray(expected)) {
        return toEqual.call(this, 'toContainText', locator, 'Locator', async (isNot, timeout) => {
            const expectedText = serializeExpectedTextValues(expected, { matchSubstring: true, normalizeWhiteSpace: true, ignoreCase: options.ignoreCase });
            return await locator._expect('to.contain.text.array', { expectedText, isNot, useInnerText: options.useInnerText, timeout });
        }, expected, { ...options, contains: true });
    }
    else {
        return toMatchText.call(this, 'toContainText', locator, 'Locator', async (isNot, timeout) => {
            const expectedText = serializeExpectedTextValues([expected], { matchSubstring: true, normalizeWhiteSpace: true, ignoreCase: options.ignoreCase });
            return await locator._expect('to.have.text', { expectedText, isNot, useInnerText: options.useInnerText, timeout });
        }, expected, { ...options, matchSubstring: true });
    }
}
export function toHaveAccessibleDescription(locator, expected, options) {
    return toMatchText.call(this, 'toHaveAccessibleDescription', locator, 'Locator', async (isNot, timeout) => {
        const expectedText = serializeExpectedTextValues([expected], { ignoreCase: options?.ignoreCase, normalizeWhiteSpace: true });
        return await locator._expect('to.have.accessible.description', { expectedText, isNot, timeout });
    }, expected, options);
}
export function toHaveAccessibleName(locator, expected, options) {
    return toMatchText.call(this, 'toHaveAccessibleName', locator, 'Locator', async (isNot, timeout) => {
        const expectedText = serializeExpectedTextValues([expected], { ignoreCase: options?.ignoreCase, normalizeWhiteSpace: true });
        return await locator._expect('to.have.accessible.name', { expectedText, isNot, timeout });
    }, expected, options);
}
export function toHaveAccessibleErrorMessage(locator, expected, options) {
    return toMatchText.call(this, 'toHaveAccessibleErrorMessage', locator, 'Locator', async (isNot, timeout) => {
        const expectedText = serializeExpectedTextValues([expected], { ignoreCase: options?.ignoreCase, normalizeWhiteSpace: true });
        return await locator._expect('to.have.accessible.error.message', { expectedText: expectedText, isNot, timeout });
    }, expected, options);
}
export function toHaveAttribute(locator, name, expected, options) {
    if (!options) {
        // Update params for the case toHaveAttribute(name, options);
        if (typeof expected === 'object' && !isRegExp(expected)) {
            options = expected;
            expected = undefined;
        }
    }
    if (expected === undefined) {
        return toBeTruthy.call(this, 'toHaveAttribute', locator, 'Locator', 'have attribute', '', async (isNot, timeout) => {
            return await locator._expect('to.have.attribute', { expressionArg: name, isNot, timeout });
        }, options);
    }
    return toMatchText.call(this, 'toHaveAttribute', locator, 'Locator', async (isNot, timeout) => {
        const expectedText = serializeExpectedTextValues([expected], { ignoreCase: options?.ignoreCase });
        return await locator._expect('to.have.attribute.value', { expressionArg: name, expectedText, isNot, timeout });
    }, expected, options);
}
export function toHaveClass(locator, expected, options) {
    if (Array.isArray(expected)) {
        return toEqual.call(this, 'toHaveClass', locator, 'Locator', async (isNot, timeout) => {
            const expectedText = serializeExpectedTextValues(expected);
            return await locator._expect('to.have.class.array', { expectedText, isNot, timeout });
        }, expected, options);
    }
    else {
        return toMatchText.call(this, 'toHaveClass', locator, 'Locator', async (isNot, timeout) => {
            const expectedText = serializeExpectedTextValues([expected]);
            return await locator._expect('to.have.class', { expectedText, isNot, timeout });
        }, expected, options);
    }
}
export function toContainClass(locator, expected, options) {
    if (Array.isArray(expected)) {
        if (expected.some(e => isRegExp(e)))
            throw new Error(`"expected" argument in toContainClass cannot contain RegExp values`);
        return toEqual.call(this, 'toContainClass', locator, 'Locator', async (isNot, timeout) => {
            const expectedText = serializeExpectedTextValues(expected);
            return await locator._expect('to.contain.class.array', { expectedText, isNot, timeout });
        }, expected, options);
    }
    else {
        if (isRegExp(expected))
            throw new Error(`"expected" argument in toContainClass cannot be a RegExp value`);
        return toMatchText.call(this, 'toContainClass', locator, 'Locator', async (isNot, timeout) => {
            const expectedText = serializeExpectedTextValues([expected]);
            return await locator._expect('to.contain.class', { expectedText, isNot, timeout });
        }, expected, options);
    }
}
export function toHaveCount(locator, expected, options) {
    return toEqual.call(this, 'toHaveCount', locator, 'Locator', async (isNot, timeout) => {
        return await locator._expect('to.have.count', { expectedNumber: expected, isNot, timeout });
    }, expected, options);
}
export function toHaveCSS(locator, name, expected, options) {
    return toMatchText.call(this, 'toHaveCSS', locator, 'Locator', async (isNot, timeout) => {
        const expectedText = serializeExpectedTextValues([expected]);
        return await locator._expect('to.have.css', { expressionArg: name, expectedText, isNot, pseudo: options?.pseudo, timeout });
    }, expected, options);
}
export function toHaveId(locator, expected, options) {
    return toMatchText.call(this, 'toHaveId', locator, 'Locator', async (isNot, timeout) => {
        const expectedText = serializeExpectedTextValues([expected]);
        return await locator._expect('to.have.id', { expectedText, isNot, timeout });
    }, expected, options);
}
export function toHaveJSProperty(locator, name, expected, options) {
    return toEqual.call(this, 'toHaveJSProperty', locator, 'Locator', async (isNot, timeout) => {
        return await locator._expect('to.have.property', { expressionArg: name, expectedValue: expected, isNot, timeout });
    }, expected, options);
}
export function toHaveRole(locator, expected, options) {
    if (!isString(expected))
        throw new Error(`"role" argument in toHaveRole must be a string`);
    return toMatchText.call(this, 'toHaveRole', locator, 'Locator', async (isNot, timeout) => {
        const expectedText = serializeExpectedTextValues([expected]);
        return await locator._expect('to.have.role', { expectedText, isNot, timeout });
    }, expected, options);
}
export function toHaveText(locator, expected, options = {}) {
    if (Array.isArray(expected)) {
        return toEqual.call(this, 'toHaveText', locator, 'Locator', async (isNot, timeout) => {
            const expectedText = serializeExpectedTextValues(expected, { normalizeWhiteSpace: true, ignoreCase: options.ignoreCase });
            return await locator._expect('to.have.text.array', { expectedText, isNot, useInnerText: options?.useInnerText, timeout });
        }, expected, options);
    }
    else {
        return toMatchText.call(this, 'toHaveText', locator, 'Locator', async (isNot, timeout) => {
            const expectedText = serializeExpectedTextValues([expected], { normalizeWhiteSpace: true, ignoreCase: options.ignoreCase });
            return await locator._expect('to.have.text', { expectedText, isNot, useInnerText: options?.useInnerText, timeout });
        }, expected, options);
    }
}
export function toHaveValue(locator, expected, options) {
    return toMatchText.call(this, 'toHaveValue', locator, 'Locator', async (isNot, timeout) => {
        const expectedText = serializeExpectedTextValues([expected]);
        return await locator._expect('to.have.value', { expectedText, isNot, timeout });
    }, expected, options);
}
export function toHaveValues(locator, expected, options) {
    return toEqual.call(this, 'toHaveValues', locator, 'Locator', async (isNot, timeout) => {
        const expectedText = serializeExpectedTextValues(expected);
        return await locator._expect('to.have.values', { expectedText, isNot, timeout });
    }, expected, options);
}
export function toHaveTitle(page, expected, options = {}) {
    return toMatchText.call(this, 'toHaveTitle', page, 'Page', async (isNot, timeout) => {
        const expectedText = serializeExpectedTextValues([expected], { normalizeWhiteSpace: true });
        return await page.mainFrame()._expect('to.have.title', { expectedText, isNot, timeout });
    }, expected, options);
}
export function toHaveURL(page, expected, options) {
    if (isURLPattern(expected))
        return toHaveURLWithPredicate.call(this, page, url => expected.test(url.href), options);
    // Ports don't support predicates. Keep separate server and client codepaths
    if (typeof expected === 'function')
        return toHaveURLWithPredicate.call(this, page, expected, options);
    const baseURL = page.context()._options.baseURL;
    expected = typeof expected === 'string' ? constructURLBasedOnBaseURL(baseURL, expected) : expected;
    return toMatchText.call(this, 'toHaveURL', page, 'Page', async (isNot, timeout) => {
        const expectedText = serializeExpectedTextValues([expected], { ignoreCase: options?.ignoreCase });
        return await page.mainFrame()._expect('to.have.url', { expectedText, isNot, timeout });
    }, expected, options);
}
export async function toBeOK(response) {
    const matcherName = 'toBeOK';
    expectTypes(response, ['APIResponse'], matcherName);
    const contentType = response.headers()['content-type'];
    const isTextEncoding = contentType && isTextualMimeType(contentType);
    const [log, text] = (this.isNot === response.ok()) ? await Promise.all([
        response._fetchLog(),
        isTextEncoding ? response.text() : null
    ]) : [];
    const message = () => formatMatcherMessage(this.utils, {
        isNot: this.isNot,
        promise: this.promise,
        matcherName,
        receiver: 'response',
        expectation: '',
        log,
    }) + (text === null ? '' : `\nResponse text:\n${colors.dim(text?.substring(0, 1000) || '')}`);
    const pass = response.ok();
    return { message, pass };
}
export async function toPass(callback, options = {}) {
    const testInfo = expectConfig().testInfo;
    const timeout = options.timeout ?? expectConfig().toPass?.timeout ?? 0;
    const intervals = options.intervals ?? expectConfig().toPass?.intervals ?? [100, 250, 500, 1000];
    const { deadline, timeoutMessage } = deadlineForMatcher(testInfo, timeout);
    const result = await pollAgainstDeadline(async () => {
        if (testInfo && expectConfig().testInfo !== testInfo)
            return { continuePolling: false, result: undefined };
        try {
            await callback();
            return { continuePolling: !!this.isNot, result: undefined };
        }
        catch (e) {
            return { continuePolling: !this.isNot, result: e };
        }
    }, deadline, intervals);
    if (result.timedOut) {
        const message = result.result ? [
            result.result.message,
            '',
            `Call Log:`,
            `- ${timeoutMessage}`,
        ].join('\n') : timeoutMessage;
        return { message: () => message, pass: !!this.isNot };
    }
    return { pass: !this.isNot, message: () => '' };
}
export function computeMatcherTitleSuffix(matcherName, receiver, args) {
    if (matcherName === 'toHaveScreenshot') {
        const title = toHaveScreenshotStepTitle(...args);
        return { short: title ? `(${title})` : '' };
    }
    if (receiver && typeof receiver === 'object' && receiver._apiName === 'Locator') {
        try {
            return { long: ' ' + asLocatorDescription('javascript', receiver._selector) };
        }
        catch {
        }
    }
    return {};
}
export function deadlineForMatcher(testInfo, timeout) {
    const startTime = monotonicTime();
    const matcherDeadline = timeout ? startTime + timeout : 0;
    const matcherMessage = `Timeout ${timeout}ms exceeded while waiting on the predicate`;
    if (!testInfo)
        return { deadline: matcherDeadline, timeoutMessage: matcherMessage };
    const { deadline: testDeadline, timeout: testTimeout } = testInfo._deadline();
    const effectiveTestDeadline = testDeadline - 250;
    const testMessage = `Test timeout of ${testTimeout}ms exceeded`;
    if (!matcherDeadline)
        return { deadline: effectiveTestDeadline, timeoutMessage: testMessage };
    return { deadline: Math.min(effectiveTestDeadline, matcherDeadline), timeoutMessage: effectiveTestDeadline < matcherDeadline ? testMessage : matcherMessage };
}
//# sourceMappingURL=matchers.js.map