import Alpine from 'alpinejs'
import {
    checkForAlpine,
    syncWithObservedComponent,
    updateOnMutation,
    objectSetDeep,
    componentData,
    getNoopProxy,
    waitUntilReady,
} from '../src/utils'
import { waitFor } from '@testing-library/dom'

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

test('checkForAlpine > throws error when Alpine version is below 2.5', async () => {
    window.Alpine.version = '2.4.9'
    await waitFor(() => {
        expect(checkForAlpine).toThrow('')
    })
})

test('syncWithObservedComponent > can interact with another component', async () => {
    document.body.innerHTML = `
        <div x-data="{foo: 'bar'}">
            <p x-text="foo"></p>
        </div>
    `
    Alpine.start()

    const sync = syncWithObservedComponent({}, document.querySelector('[x-data]'), (syncedComponent, path, value) => {
        syncedComponent[path] = value
    })
    sync.foo = 'baz'

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

test('updateOnMutation > can react to other component chanegs', async () => {
    document.body.innerHTML = `
        <div x-data="{foo: 'bar'}">
            <button @click="foo = 'baz'"></button>
        </div>
    `
    Alpine.start()
    console.log = jest.fn()

    updateOnMutation(document.querySelector('[x-data]'), () => {
        console.log('foo')
    })

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

    await waitFor(() => {
        expect(console.log.mock.calls[0][0]).toBe('foo')
    })
})

test('objectSetDeep > can set a deep property on an object', () => {
    const object = { foo: { bar: 'baz' } }

    objectSetDeep(object, 'foo.bar', 'qux')

    expect(object.foo.bar).toBe('qux')
})

test('componentData > can extract data BEFORE Alpine is initialized', () => {
    document.body.innerHTML = `
        <div x-data="{foo: 'bar'}"></div>
    `

    const data = componentData(document.querySelector('[x-data]'))

    expect(data).toMatchObject({ foo: 'bar' })
})

test('componentData > can extract data AFTER Alpine is initialized', async () => {
    document.body.innerHTML = `
        <div x-data="{foo: 'bar'}"></div>
    `
    Alpine.start()

    const component = document.querySelector('[x-data]')

    await waitFor(() => {
        expect(component.__x.getUnobservedData()).toMatchObject({ foo: 'bar' })
        expect(componentData(component)).toMatchObject({ foo: 'bar' })
    })
})

test('componentData > can accept top level properties to scope to', async () => {
    document.body.innerHTML = `
        <div x-data="{foo: 'bar', baz: 'qux'}"></div>
    `
    Alpine.start()

    const component = document.querySelector('[x-data]')

    await waitFor(() => {
        expect(component.__x.getUnobservedData()).toMatchObject({ foo: 'bar', baz: 'qux' })
        expect(componentData(component, 'foo')).toMatchObject({ foo: 'bar' })
    })
})

test('getNoopProxy > returns an empty string when accessing a property', async () => {
    document.body.innerHTML = `
        <p>bob</p>
    `
    expect(document.querySelector('p').textContent).toBe('bob')

    const proxy = getNoopProxy()
    document.querySelector('p').textContent = proxy.foo

    expect(document.querySelector('p').textContent).toBe('')
})

test('getNoopProxy > returns an empty string when accessing a nested property', async () => {
    document.body.innerHTML = `
        <p>bob</p>
    `
    expect(document.querySelector('p').textContent).toBe('bob')

    const proxy = getNoopProxy()
    document.querySelector('p').textContent = proxy.foo.bar.baz

    expect(document.querySelector('p').textContent).toBe('')
})

test('getNoopProxy > returns an empty string when accessing a function', async () => {
    document.body.innerHTML = `
        <p>bob</p>
    `
    expect(document.querySelector('p').textContent).toBe('bob')

    const proxy = getNoopProxy()
    document.querySelector('p').textContent = proxy.foo()

    expect(document.querySelector('p').textContent).toBe('')
})

test('getNoopProxy > returns an empty string when accessing a nested function', async () => {
    document.body.innerHTML = `
        <p>bob</p>
    `
    expect(document.querySelector('p').textContent).toBe('bob')

    const proxy = getNoopProxy()
    document.querySelector('p').textContent = proxy.foo.bar()

    expect(document.querySelector('p').textContent).toBe('')
})

test('waitUntilReady > returns an empty string while the component is not ready', async () => {
    window.test = () => waitUntilReady(document.querySelector('div'), document.querySelector('p'), () => {
        return 'done'
    })
    document.body.innerHTML = `
        <div></div>
        <p x-data="{foo: 'bar', baz() {return test()}}">
            <span x-text="foo"></span>
            <span x-text="baz()"></span>
        </p>
    `
    Alpine.start()

    // Make sure Alpine kicked in
    await waitFor(() => {
        expect(document.querySelectorAll('span')[0].textContent).toBe('bar')
    })

    // Div doesn't have __x yet so the alpine component should wait
    expect(document.querySelectorAll('span')[1].textContent).toBe('')

    // We simulate a component being finally ready
    document.querySelector('div').__x = true

    // The callback should finally resolve and return the final value
    await waitFor(() => {
        expect(document.querySelectorAll('span')[1].textContent).toBe('done')
    })
})