import Alpine from 'alpinejs'
import { wait } from '@testing-library/dom'
const timeout = ms => new Promise(resolve => setTimeout(resolve, ms))
global.MutationObserver = class {
observe() {}
}
test('transition in', async () => {
// Hijack "requestAnimationFrame" for finer-tuned control in this test.
var frameStack = []
jest.spyOn(window, 'requestAnimationFrame').mockImplementation((callback) => {
frameStack.push(callback)
});
// Hijack "getComputeStyle" because js-dom is weird with it.
// (hardcoding 10ms transition time for later assertions)
jest.spyOn(window, 'getComputedStyle').mockImplementation(el => {
return { transitionDuration: '.01s' }
});
document.body.innerHTML = `
`
Alpine.start()
await wait(() => { expect(document.querySelector('span').getAttribute('style')).toEqual('display: none;') })
document.querySelector('button').click()
// Wait out the intial Alpine refresh debounce.
await new Promise((resolve) =>
setTimeout(() => {
resolve();
}, 5)
)
expect(document.querySelector('span').classList.contains('enter')).toEqual(true)
expect(document.querySelector('span').classList.contains('enter-start')).toEqual(true)
expect(document.querySelector('span').classList.contains('enter-end')).toEqual(false)
expect(document.querySelector('span').getAttribute('style')).toEqual('display: none;')
frameStack.pop()()
expect(document.querySelector('span').classList.contains('enter')).toEqual(true)
expect(document.querySelector('span').classList.contains('enter-start')).toEqual(true)
expect(document.querySelector('span').classList.contains('enter-end')).toEqual(false)
expect(document.querySelector('span').getAttribute('style')).toEqual(null)
frameStack.pop()()
expect(document.querySelector('span').classList.contains('enter')).toEqual(true)
expect(document.querySelector('span').classList.contains('enter-start')).toEqual(false)
expect(document.querySelector('span').classList.contains('enter-end')).toEqual(true)
expect(document.querySelector('span').getAttribute('style')).toEqual(null)
await new Promise((resolve) =>
setTimeout(() => {
expect(document.querySelector('span').classList.contains('enter')).toEqual(false)
expect(document.querySelector('span').classList.contains('enter-start')).toEqual(false)
expect(document.querySelector('span').classList.contains('enter-end')).toEqual(false)
expect(document.querySelector('span').getAttribute('style')).toEqual(null)
resolve();
}, 10)
)
})
test('transition out', async () => {
// Hijack "requestAnimationFrame" for finer-tuned control in this test.
var frameStack = []
jest.spyOn(window, 'requestAnimationFrame').mockImplementation((callback) => {
frameStack.push(callback)
});
// Hijack "getComputeStyle" because js-dom is weird with it.
// (hardcoding 10ms transition time for later assertions)
jest.spyOn(window, 'getComputedStyle').mockImplementation(el => {
return { transitionDuration: '.01s' }
});
document.body.innerHTML = `
`
Alpine.start()
await wait(() => { expect(document.querySelector('span').getAttribute('style')).toEqual(null) })
document.querySelector('button').click()
// Wait out the intial Alpine refresh debounce.
await new Promise((resolve) =>
setTimeout(() => {
resolve();
}, 5)
)
expect(document.querySelector('span').classList.contains('leave')).toEqual(true)
expect(document.querySelector('span').classList.contains('leave-start')).toEqual(true)
expect(document.querySelector('span').classList.contains('leave-end')).toEqual(false)
expect(document.querySelector('span').getAttribute('style')).toEqual(null)
frameStack.pop()()
expect(document.querySelector('span').classList.contains('leave')).toEqual(true)
expect(document.querySelector('span').classList.contains('leave-start')).toEqual(true)
expect(document.querySelector('span').classList.contains('leave-end')).toEqual(false)
expect(document.querySelector('span').getAttribute('style')).toEqual(null)
frameStack.pop()()
expect(document.querySelector('span').classList.contains('leave')).toEqual(true)
expect(document.querySelector('span').classList.contains('leave-start')).toEqual(false)
expect(document.querySelector('span').classList.contains('leave-end')).toEqual(true)
expect(document.querySelector('span').getAttribute('style')).toEqual(null)
await timeout(10)
expect(document.querySelector('span').classList.contains('leave')).toEqual(false)
expect(document.querySelector('span').classList.contains('leave-start')).toEqual(false)
expect(document.querySelector('span').classList.contains('leave-end')).toEqual(false)
expect(document.querySelector('span').getAttribute('style')).toEqual('display: none;')
})
test('if only transition leave directives are present, don\'t transition in at all', async () => {
var frameStack = []
jest.spyOn(window, 'requestAnimationFrame').mockImplementation((callback) => {
frameStack.push(callback)
});
document.body.innerHTML = `
`
Alpine.start()
await wait(() => { expect(document.querySelector('span').getAttribute('style')).toEqual('display: none;') })
document.querySelector('button').click()
await timeout(10)
expect(frameStack.length).toEqual(0)
expect(document.querySelector('span').getAttribute('style')).toEqual(null)
})
test('if only transition enter directives are present, don\'t transition out at all', async () => {
var frameStack = []
jest.spyOn(window, 'requestAnimationFrame').mockImplementation((callback) => {
frameStack.push(callback)
});
document.body.innerHTML = `
`
Alpine.start()
await wait(() => { expect(document.querySelector('span').getAttribute('style')).toEqual('display: none;') })
document.querySelector('button').click()
// Wait out the intial Alpine refresh debounce.
await new Promise((resolve) =>
setTimeout(() => {
resolve();
}, 5)
)
expect(document.querySelector('span').classList.contains('enter')).toEqual(true)
expect(document.querySelector('span').getAttribute('style')).toEqual('display: none;')
frameStack.pop()()
expect(document.querySelector('span').classList.contains('enter')).toEqual(true)
expect(document.querySelector('span').getAttribute('style')).toEqual(null)
frameStack.pop()()
expect(document.querySelector('span').classList.contains('enter')).toEqual(true)
expect(document.querySelector('span').getAttribute('style')).toEqual(null)
await new Promise((resolve) =>
setTimeout(() => {
expect(document.querySelector('span').classList.contains('enter')).toEqual(true)
expect(document.querySelector('span').getAttribute('style')).toEqual(null)
resolve();
}, 10)
)
})
test('transition in not called when item is already visible', async () => {
// Hijack "requestAnimationFrame" for finer-tuned control in this test.
var frameStack = []
jest.spyOn(window, 'requestAnimationFrame').mockImplementation((callback) => {
frameStack.push(callback)
});
// Hijack "getComputeStyle" because js-dom is weird with it.
// (hardcoding 10ms transition time for later assertions)
jest.spyOn(window, 'getComputedStyle').mockImplementation(el => {
return { transitionDuration: '.01s' }
});
document.body.innerHTML = `
`
Alpine.start()
expect(document.querySelector('span').getAttribute('style')).toEqual(null)
document.querySelector('button').click()
// Wait out the intial Alpine refresh debounce.
await new Promise((resolve) =>
setTimeout(() => {
resolve();
}, 5)
)
// No animation queued
expect(frameStack.pop()).toEqual(undefined)
expect(document.querySelector('span').classList.contains('enter')).toEqual(false)
expect(document.querySelector('span').classList.contains('enter-start')).toEqual(false)
expect(document.querySelector('span').classList.contains('enter-end')).toEqual(false)
expect(document.querySelector('span').getAttribute('style')).toEqual(null)
})
test('transition out not called when item is already hidden', async () => {
// Hijack "requestAnimationFrame" for finer-tuned control in this test.
var frameStack = []
jest.spyOn(window, 'requestAnimationFrame').mockImplementation((callback) => {
frameStack.push(callback)
});
// Hijack "getComputeStyle" because js-dom is weird with it.
// (hardcoding 10ms transition time for later assertions)
jest.spyOn(window, 'getComputedStyle').mockImplementation(el => {
return { transitionDuration: '.01s' }
});
document.body.innerHTML = `
`
Alpine.start()
document.querySelector('button').click()
// Wait out the intial Alpine refresh debounce.
await new Promise((resolve) =>
setTimeout(() => {
resolve();
}, 5)
)
let index = 0
expect(document.querySelector('span').getAttribute('style')).toEqual(styleAttributeExpectations[index])
while(frameStack.length) {
frameStack.pop()()
expect(document.querySelector('span').getAttribute('style')).toEqual(styleAttributeExpectations[++index])
}
await new Promise(resolve => setTimeout(resolve, 30))
expect(document.querySelector('span').getAttribute('style')).toEqual(styleAttributeExpectations[++index])
document.querySelector('button').click()
// Wait out the intial Alpine refresh debounce.
await new Promise(resolve => setTimeout(resolve, 5))
expect(document.querySelector('span').getAttribute('style')).toEqual(styleAttributeExpectations[++index])
while(frameStack.length) {
frameStack.pop()()
expect(document.querySelector('span').getAttribute('style')).toEqual(styleAttributeExpectations[++index])
}
await new Promise(resolve => setTimeout(resolve, 50))
expect(document.querySelector('span').getAttribute('style')).toEqual(styleAttributeExpectations[++index])
}
test('x-transition supports css animation', async () => {
jest.spyOn(window, 'requestAnimationFrame').mockImplementation((callback) => {
setTimeout(callback, 0)
});
// (hardcoding 10ms animation time for later assertions)
jest.spyOn(window, 'getComputedStyle').mockImplementation(el => {
return {
transitionDuration: '0s',
animationDuration: '.1s'
}
});
document.body.innerHTML = `
`
Alpine.start()
await wait(() => { expect(document.querySelector('span').getAttribute('style')).toEqual('display: none;') })
// Testing animation enter
document.querySelector('button').click()
// Wait for the first requestAnimationFrame
await new Promise((resolve) =>
setTimeout(() => {
resolve();
}, 0)
)
expect(document.querySelector('span').classList.contains('animation-enter')).toEqual(true)
// The class should still be there since the animationDuration property is 100ms
await new Promise((resolve) =>
setTimeout(() => {
resolve();
}, 99)
)
expect(document.querySelector('span').classList.contains('animation-enter')).toEqual(true)
// The class shouldn't be there anymore
await new Promise((resolve) =>
setTimeout(() => {
resolve();
}, 10)
)
expect(document.querySelector('span').classList.contains('animation-enter')).toEqual(false)
// Testing animation enter
document.querySelector('button').click()
// Wait for the first requestAnimationFrame
await new Promise((resolve) =>
setTimeout(() => {
resolve();
}, 0)
)
expect(document.querySelector('span').classList.contains('animation-leave')).toEqual(true)
// The class should still be there since the animationDuration property is 100ms
await new Promise((resolve) =>
setTimeout(() => {
resolve();
}, 99)
)
expect(document.querySelector('span').classList.contains('animation-leave')).toEqual(true)
// The class shouldn't be there anymore
await new Promise((resolve) =>
setTimeout(() => {
resolve();
}, 10)
)
expect(document.querySelector('span').classList.contains('animation-leave')).toEqual(false)
})
test('x-transition do not overlap', async () => {
jest.spyOn(window, 'requestAnimationFrame').mockImplementation((callback) => {
setTimeout(callback, 0)
});
document.body.innerHTML = `
`
Alpine.start()
// Initial state
expect(document.querySelector('span').style.display).toEqual("")
expect(document.querySelector('span').style.opacity).toEqual("")
expect(document.querySelector('span').style.transform).toEqual("")
expect(document.querySelector('span').style.transformOrigin).toEqual("")
// Trigger transition out
document.querySelector('button').click()
// Trigger transition in before the previous one has finished
await timeout(10)
document.querySelector('button').click()
// Check the element is still visible and style properties are correct
await timeout(200)
expect(document.querySelector('span').style.display).toEqual("")
expect(document.querySelector('span').style.opacity).toEqual("")
expect(document.querySelector('span').style.transform).toEqual("")
expect(document.querySelector('span').style.transformOrigin).toEqual("")
// Hide the element
document.querySelector('button').click()
await timeout(200)
expect(document.querySelector('span').style.display).toEqual("none")
expect(document.querySelector('span').style.opacity).toEqual("")
expect(document.querySelector('span').style.transform).toEqual("")
expect(document.querySelector('span').style.transformOrigin).toEqual("")
// Trigger transition in
document.querySelector('button').click()
// Trigger transition out before the previous one has finished
await timeout(10)
document.querySelector('button').click()
// Check the element is hidden and style properties are correct
await timeout(200)
expect(document.querySelector('span').style.display).toEqual("none")
expect(document.querySelector('span').style.opacity).toEqual("")
expect(document.querySelector('span').style.transform).toEqual("")
expect(document.querySelector('span').style.transformOrigin).toEqual("")
})
test('x-transition using classes do not overlap', async () => {
jest.spyOn(window, 'requestAnimationFrame').mockImplementation((callback) => {
setTimeout(callback, 0)
});
jest.spyOn(window, 'getComputedStyle').mockImplementation(el => {
return { transitionDuration: '.1s' }
});
document.body.innerHTML = `
`
Alpine.start()
// Initial state
expect(document.querySelector('span').style.display).toEqual("")
const emptyClassList = document.querySelector('span').classList
// Trigger transition out
document.querySelector('button').click()
// Trigger transition in before the previous one has finished
await timeout(10)
document.querySelector('button').click()
// Check the element is still visible and class property is correct
await timeout(200)
expect(document.querySelector('span').style.display).toEqual("")
expect(document.querySelector('span').classList).toEqual(emptyClassList)
// Hide the element
document.querySelector('button').click()
await timeout(200)
expect(document.querySelector('span').style.display).toEqual("none")
expect(document.querySelector('span').classList).toEqual(emptyClassList)
// Trigger transition in
document.querySelector('button').click()
// Trigger transition out before the previous one has finished
await timeout(10)
document.querySelector('button').click()
// Check the element is hidden and class property is correct
await timeout(200)
expect(document.querySelector('span').style.display).toEqual("none")
expect(document.querySelector('span').classList).toEqual(emptyClassList)
})
test('x-transition with parent x-show does not overlap', async () => {
jest.spyOn(window, 'requestAnimationFrame').mockImplementation((callback) => {
setTimeout(callback, 0)
});
document.body.innerHTML = `
`
Alpine.start()
// Initial state
expect(document.querySelector('span').style.display).toEqual("")
expect(document.querySelector('span').style.opacity).toEqual("")
expect(document.querySelector('span').style.transform).toEqual("")
expect(document.querySelector('span').style.transformOrigin).toEqual("")
expect(document.querySelector('h1').style.display).toEqual("")
expect(document.querySelector('h1').style.opacity).toEqual("")
expect(document.querySelector('h1').style.transform).toEqual("")
expect(document.querySelector('h1').style.transformOrigin).toEqual("")
// Trigger transition out
document.querySelector('button').click()
// Trigger transition in before the previous one has finished
await timeout(10)
document.querySelector('button').click()
// Check the element is still visible and style properties are correct
await timeout(200)
expect(document.querySelector('span').style.display).toEqual("")
expect(document.querySelector('span').style.opacity).toEqual("")
expect(document.querySelector('span').style.transform).toEqual("")
expect(document.querySelector('span').style.transformOrigin).toEqual("")
expect(document.querySelector('h1').style.display).toEqual("")
expect(document.querySelector('h1').style.opacity).toEqual("")
expect(document.querySelector('h1').style.transform).toEqual("")
expect(document.querySelector('h1').style.transformOrigin).toEqual("")
// Hide the element
document.querySelector('button').click()
await timeout(200)
expect(document.querySelector('span').style.display).toEqual("none")
expect(document.querySelector('span').style.opacity).toEqual("")
expect(document.querySelector('span').style.transform).toEqual("")
expect(document.querySelector('span').style.transformOrigin).toEqual("")
expect(document.querySelector('h1').style.display).toEqual("none")
expect(document.querySelector('h1').style.opacity).toEqual("")
expect(document.querySelector('h1').style.transform).toEqual("")
expect(document.querySelector('h1').style.transformOrigin).toEqual("")
// Trigger transition in
document.querySelector('button').click()
// Trigger transition out before the previous one has finished
await timeout(10)
document.querySelector('button').click()
// Check the element is hidden and style properties are correct
await timeout(200)
expect(document.querySelector('span').style.display).toEqual("none")
expect(document.querySelector('span').style.opacity).toEqual("")
expect(document.querySelector('span').style.transform).toEqual("")
expect(document.querySelector('span').style.transformOrigin).toEqual("")
expect(document.querySelector('h1').style.display).toEqual("none")
expect(document.querySelector('h1').style.opacity).toEqual("")
expect(document.querySelector('h1').style.transform).toEqual("")
expect(document.querySelector('h1').style.transformOrigin).toEqual("")
})