import Alpine from 'alpinejs'
import AlpineComponentMagicMethod from '../dist/component'
import { waitFor } from '@testing-library/dom'

beforeAll(() => {
    window.Alpine = Alpine
})

beforeEach(function () {
    AlpineComponentMagicMethod.start()
})

test('$parent > component can access parent scope', async () => {
    document.body.innerHTML = `
        <div x-data="{foo: 'bar'}">
            <div x-data>
                <p x-text="$parent.foo"></p>
                <button @click="$parent.foo = 'baz'"></button>
            </div>
            <button @click="foo = 'bob'"></button>
        </div>
    `

    Alpine.start()

    await waitFor(() => {
        expect(document.querySelector('p').textContent).toEqual('bar')
    })

    document.querySelectorAll('button')[0].click()

    await waitFor(() => {
        expect(document.querySelector('p').textContent).toEqual('baz')
    })

    document.querySelectorAll('button')[1].click()

    await waitFor(() => {
        expect(document.querySelector('p').textContent).toEqual('bob')
    })
})

test('$parent > component can access parent scope when called only in a child event', async () => {
    document.body.innerHTML = `
        <div id="parent-component" x-data="{foo: 'bar'}">
            <div x-data>
                <button @click="$parent.foo = 'baz'"></button>
            </div>
        </div>
    `
    await waitFor(() => {
        expect(document.querySelector('#parent-component').__x.$data.foo === 'bar')
    })

    Alpine.start()

    document.querySelectorAll('button')[0].click()

    await waitFor(() => {
        expect(document.querySelector('#parent-component').__x.$data.foo === 'baz')
    })
})

test('$parent > component can update and watch deep object properties', async () => {
    document.body.innerHTML = `
        <div x-data="{foo: {bar: 'baz'}}">
            <div x-data>
                <p x-text="$parent.foo.bar"></p>
                <button @click="$parent.foo.bar = 'qux'"></button>
            </div>
            <button @click="foo.bar = 'bob'"></button>
        </div>
    `

    Alpine.start()

    await waitFor(() => {
        expect(document.querySelector('p').textContent).toEqual('baz')
    })

    document.querySelectorAll('button')[0].click()

    await waitFor(() => {
        expect(document.querySelector('p').textContent).toEqual('qux')
    })

    document.querySelectorAll('button')[1].click()

    await waitFor(() => {
        expect(document.querySelector('p').textContent).toEqual('bob')
    })
})

// This test was added to remove `characterData: true` from the MutationObserver options
test('$parent > will not error on characterData edits', async () => {
    document.body.innerHTML = `
    <div x-data="{foo: 'bar'}">
        <div x-data>
            <p x-text="$parent.foo"></p>
            <span>Some text</span>
        </div>
    </div>
    `
    Alpine.start()
    document.querySelector('span').firstChild.appendData('Different text')
})

test('$component > component can access external scope', async () => {
    document.body.innerHTML = `
        <div x-data>
            <p x-text="$component('ext').foo"></p>
            <button @click="$component('ext').foo = 'baz'"></button>
        </div>
        <div x-id="ext" x-data="{foo: 'bar'}">
            <button @click="foo = 'bob'"></button>
        </div>
    `

    Alpine.start()

    await waitFor(() => {
        expect(document.querySelector('p').textContent).toEqual('bar')
    })

    document.querySelectorAll('button')[0].click()

    await waitFor(() => {
        expect(document.querySelector('p').textContent).toEqual('baz')
    })

    document.querySelectorAll('button')[1].click()

    await waitFor(() => {
        expect(document.querySelector('p').textContent).toEqual('bob')
    })
})

test('$component > component can update and watch deep object properties', async () => {
    document.body.innerHTML = `
        <div x-data>
            <p x-text="$component('ext').foo.bar"></p>
            <button @click="$component('ext').foo.bar = 'qux'"></button>
        </div>
        <div x-id="ext" x-data="{foo: {bar: 'baz'}}">
            <button @click="foo.bar = 'bob'"></button>
        </div>
    `

    Alpine.start()

    await waitFor(() => {
        expect(document.querySelector('p').textContent).toEqual('baz')
    })

    document.querySelectorAll('button')[0].click()

    await waitFor(() => {
        expect(document.querySelector('p').textContent).toEqual('qux')
    })

    document.querySelectorAll('button')[1].click()

    await waitFor(() => {
        expect(document.querySelector('p').textContent).toEqual('bob')
    })
})

test('$component > this context is set correctly when functions are invoked through the helper', async () => {
    document.body.innerHTML = `
        <div x-data>
            <p x-text="$component('ext').foo"></p>
            <button @click="$component('ext').baz()"></button>
        </div>
        <div x-id="ext" x-data="{foo: 'bar', baz() {return this.foo = this.$refs.bob.textContent}}">
            <span x-ref="bob">qux</span>
        </div>
    `

    Alpine.start()

    await waitFor(() => {
        expect(document.querySelector('p').textContent).toEqual('bar')
    })

    document.querySelector('button').click()

    await waitFor(() => {
        expect(document.querySelector('p').textContent).toEqual('qux')
    })
})

test('$parent > component can access granparent scope', async () => {
    document.body.innerHTML = `
        <div x-data="{foo: 'bar', getFoo() {return this.foo}}">
            <p x-text="foo"></p>
            <div x-data>
                <div x-data>
                    <p x-text="$parent.$parent.foo"></p>
                    <p x-text="$parent.$parent.getFoo()"></p>
                    <button @click="$parent.$parent.foo = 'baz'"></button>
                </div>
                <button @click="$parent.foo = 'bob'"></button>
            </div>
            <button @click="foo = 'qux'"></button>
        </div>
    `

    Alpine.start()

    await waitFor(() => {
        expect(document.querySelectorAll('p')[0].textContent).toEqual('bar')
        expect(document.querySelectorAll('p')[1].textContent).toEqual('bar')
        expect(document.querySelectorAll('p')[2].textContent).toEqual('bar')
    })

    document.querySelectorAll('button')[0].click()

    await waitFor(() => {
        expect(document.querySelectorAll('p')[0].textContent).toEqual('baz')
        expect(document.querySelectorAll('p')[1].textContent).toEqual('baz')
        expect(document.querySelectorAll('p')[2].textContent).toEqual('baz')
    })

    document.querySelectorAll('button')[1].click()

    await waitFor(() => {
        expect(document.querySelectorAll('p')[0].textContent).toEqual('bob')
        expect(document.querySelectorAll('p')[1].textContent).toEqual('bob')
        expect(document.querySelectorAll('p')[2].textContent).toEqual('bob')
    })

    document.querySelectorAll('button')[2].click()

    await waitFor(() => {
        expect(document.querySelectorAll('p')[0].textContent).toEqual('qux')
        expect(document.querySelectorAll('p')[1].textContent).toEqual('qux')
        expect(document.querySelectorAll('p')[2].textContent).toEqual('qux')
    })
})

test('$component > component can access magic properties', async () => {
    document.body.innerHTML = `
        <div x-data>
            <p x-text="$component('ext').$refs.bob.textContent"></p>
        </div>
        <div x-id="ext" x-data="{foo: 'bar'}">
            <span x-ref="bob" x-text="foo"></span>
        </div>
    `

    Alpine.start()

    await waitFor(() => {
        expect(document.querySelector('p').textContent).toEqual('bar')
    })
})

test('$parent > x-for can loop correctly on a property from the parent scope', async () => {
    document.body.innerHTML = `
        <div x-data="{ comments: ['test', 'test2'] }">
            <div x-data>
                <template x-for="item in $parent.comments">
                  <p x-text="item"></p>
                </template>
            </div>
        </div>
    `

    Alpine.start()

    await waitFor(() => {
        expect(document.querySelectorAll('p').length).toEqual(2)
    })
})