
// This is an invisible template tag for enabling syntax highlighting
// of any string in most editors.
export function html(strings) {
    return strings.raw[0]
}

export let test = function (name, template, callback, handleExpectedErrors = false) {
    it(name, () => {
        injectHtmlAndBootAlpine(cy, template, callback, undefined, handleExpectedErrors)
    })
}

test.only = (name, template, callback, handleExpectedErrors = false) => {
    it.only(name, () => {
        injectHtmlAndBootAlpine(cy, template, callback, undefined, handleExpectedErrors)
    })
}

test.skip = (name, template, callback, handleExpectedErrors = false) => {
    it.skip(name, () => {
        injectHtmlAndBootAlpine(cy, template, callback, undefined, handleExpectedErrors)
    })
}

test.retry = (count) => (name, template, callback, handleExpectedErrors = false) => {
    it(name, {
        retries: {
            // During "cypress run"
            runMode: count - 1,
            // During "cypress open"
            openMode: count - 1,
        }
    }, () => {
        injectHtmlAndBootAlpine(cy, template, callback, undefined, handleExpectedErrors)
    })
}

test.csp = (name, template, callback, handleExpectedErrors = false) => {
    it(name, () => {
        injectHtmlAndBootAlpine(cy, template, callback, __dirname+'/spec-csp.html', handleExpectedErrors)
    })
}

function injectHtmlAndBootAlpine(cy, templateAndPotentiallyScripts, callback, page, handleExpectedErrors = false) {
    let [template, scripts] = Array.isArray(templateAndPotentiallyScripts)
        ? templateAndPotentiallyScripts
        : [templateAndPotentiallyScripts]

    cy.visit(page || __dirname+'/spec.html')

    if( handleExpectedErrors ) {
        cy.on( 'uncaught:exception', ( error ) => {
            if( error.el === undefined && error.expression === undefined ) {
                console.warn( 'Expected all errors originating from Alpine to have el and expression.  Letting cypress fail the test.', error )
                return true
            }
            return false
        } );
    }

    cy.get('#root').then(([el]) => {
        el.innerHTML = template

        el.evalScripts(scripts)

        cy.get('[alpine-is-ready]', { timeout: 5000 }).should('be.visible');

        // We can't just simply reload a page from a test, because we need to
        // re-inject all the templates and such. This is a helper to allow
        // a test-subject method to perform a redirect all on their own.
        let reload = () => {
            cy.reload()

            cy.get('#root').then(([el]) => {
                el.innerHTML = template

                el.evalScripts(scripts)

                cy.get('[alpine-is-ready]', { timeout: 5000 }).should('be.visible');
            })
        }

        cy.window().then(window => {
            callback(cy, reload, window, window.document)
        })
    })
}

export let haveData = (key, value) => ([el]) => expect(root(el)._x_dataStack[0][key]).to.deep.equal(value);

export let haveFocus = () => el => expect(el).to.have.focus

export let notHaveFocus = () => el => expect(el).not.to.be.focused

export let haveAttribute = (name, value) => el => expect(el).to.have.attr(name, value)

export let notHaveAttribute = (name, value) => el => expect(el).not.to.have.attr(name, value)

export let haveProperty = (name, value) => el => expect(el).to.have.prop(name, value)

export let haveText = text => el => expect(el).to.have.text(text)

export let notHaveText = text => el => expect(el).not.to.have.text(text)

export let haveHtml = html => el => expect(el).to.have.html(html)

export let beChecked = () => el => expect(el).to.be.checked

export let notBeChecked = () => el => expect(el).not.to.be.checked

export let beVisible = () => el => expect(el).to.be.visible

export let notBeVisible = () => el => expect(el).not.to.be.visible

export let exist = () => el => expect(el).to.exist

export let notExist = () => el => expect(el).not.to.exist

export let beHidden = () => el => expect(el).to.be.hidden

export let haveClasses = classes => el => classes.forEach(aClass => expect(el).to.have.class(aClass))

export let notHaveClasses = classes => el => classes.forEach(aClass => expect(el).not.to.have.class(aClass))

export let haveValue = value => el => expect(el).to.have.value(value)

export let haveLength = length => el => expect(el).to.have.length(length)

export let beEqualTo = value => el => expect(el).to.eq(value)

export let notHaveComputedStyle = (name, value) => el => {
    const win = el[0].ownerDocument.defaultView
    expect(win.getComputedStyle(el[0]).getPropertyValue(name)).not.to.eq(value)
}

export let haveComputedStyle = (name, value) => el => {
    const win = el[0].ownerDocument.defaultView
    expect(win.getComputedStyle(el[0]).getPropertyValue(name)).to.eq(value)
}

export function root(el) {
    if (el._x_dataStack) return el

    if (! el.parentElement) return

    return closestRoot(el.parentElement)
}
