import { Injector } from '@furystack/inject'
import { sleepAsync, usingAsync } from '@furystack/utils'
import { LazyLoad } from './lazy-load.js'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { initializeShadeRoot } from '../initialize.js'
import { createComponent } from '../shade-component.js'
import { flushUpdates } from '../shade.js'
describe('Lazy Load', () => {
beforeEach(() => {
document.body.innerHTML = '
'
})
afterEach(() => {
document.body.innerHTML = ''
delete (document as unknown as Record).startViewTransition
})
it('Shuld display the loader and completed state', async () => {
await usingAsync(new Injector(), async (injector) => {
const rootElement = document.getElementById('root') as HTMLDivElement
initializeShadeRoot({
injector,
rootElement,
jsxElement: (
Loading...}
component={async () => {
await sleepAsync(100)
return Loaded
}}
/>
),
})
await flushUpdates()
expect(document.body.innerHTML).toBe('')
await sleepAsync(150)
expect(document.body.innerHTML).toBe('')
})
})
it('Shuld display the failed state with a retryer', async () => {
await usingAsync(new Injector(), async (injector) => {
const rootElement = document.getElementById('root') as HTMLDivElement
const load = vi.fn(async () => {
throw Error(':(')
})
initializeShadeRoot({
injector,
rootElement,
jsxElement: (
Loading...}
component={load}
error={(e, retry) => (
)}
/>
),
})
await flushUpdates()
expect(document.body.innerHTML).toBe('')
await flushUpdates()
expect(load).toBeCalledTimes(1)
expect(document.body.innerHTML).toBe('
')
document.getElementById('retry')?.click()
expect(load).toBeCalledTimes(2)
})
})
it('Should succeed on retry after initial failure', async () => {
await usingAsync(new Injector(), async (injector) => {
const rootElement = document.getElementById('root') as HTMLDivElement
let counter = 0
const load = vi.fn(async () => {
if (!counter) {
counter += 1
throw Error(':(')
}
return success
})
initializeShadeRoot({
injector,
rootElement,
jsxElement: (
Loading...}
component={load}
error={(e, retry) => (
)}
/>
),
})
await flushUpdates()
expect(document.body.innerHTML).toBe('')
await flushUpdates()
expect(load).toBeCalledTimes(1)
expect(document.body.innerHTML).toBe('
')
document.getElementById('retry')?.click()
expect(load).toBeCalledTimes(2)
await flushUpdates()
expect(document.body.innerHTML).toBe('')
})
})
it('should call startViewTransition when viewTransition is enabled and component loads', async () => {
const startViewTransitionSpy = vi.fn((optionsOrCallback: StartViewTransitionOptions | (() => void)) => {
const update = typeof optionsOrCallback === 'function' ? optionsOrCallback : optionsOrCallback.update
update?.()
return {
finished: Promise.resolve(),
ready: Promise.resolve(),
updateCallbackDone: Promise.resolve(),
skipTransition: vi.fn(),
} as unknown as ViewTransition
})
document.startViewTransition = startViewTransitionSpy as typeof document.startViewTransition
await usingAsync(new Injector(), async (injector) => {
const rootElement = document.getElementById('root') as HTMLDivElement
initializeShadeRoot({
injector,
rootElement,
jsxElement: (
Loading...}
component={async () => {
await sleepAsync(50)
return Loaded
}}
/>
),
})
await flushUpdates()
expect(document.body.innerHTML).toContain('Loading...')
await sleepAsync(100)
expect(startViewTransitionSpy).toHaveBeenCalledTimes(1)
expect(document.body.innerHTML).toContain('Loaded')
})
})
it('should not call startViewTransition when viewTransition is not set', async () => {
const startViewTransitionSpy = vi.fn((optionsOrCallback: StartViewTransitionOptions | (() => void)) => {
const update = typeof optionsOrCallback === 'function' ? optionsOrCallback : optionsOrCallback.update
update?.()
return {
finished: Promise.resolve(),
ready: Promise.resolve(),
updateCallbackDone: Promise.resolve(),
skipTransition: vi.fn(),
} as unknown as ViewTransition
})
document.startViewTransition = startViewTransitionSpy as typeof document.startViewTransition
await usingAsync(new Injector(), async (injector) => {
const rootElement = document.getElementById('root') as HTMLDivElement
initializeShadeRoot({
injector,
rootElement,
jsxElement: (
Loading...}
component={async () => {
await sleepAsync(50)
return Loaded
}}
/>
),
})
await flushUpdates()
await sleepAsync(100)
expect(startViewTransitionSpy).not.toHaveBeenCalled()
expect(document.body.innerHTML).toContain('Loaded')
})
})
})