@furystack/shades
Version:
A lightweight UI framework for FuryStack with JSX support
138 lines • 6.86 kB
JavaScript
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 = '<div id="root"></div>';
});
afterEach(() => {
document.body.innerHTML = '';
delete document.startViewTransition;
});
it('Shuld display the loader and completed state', async () => {
await usingAsync(new Injector(), async (injector) => {
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: (createComponent(LazyLoad, { loader: createComponent("div", null, "Loading..."), component: async () => {
await sleepAsync(100);
return createComponent("div", null, "Loaded");
} })),
});
await flushUpdates();
expect(document.body.innerHTML).toBe('<div id="root"><lazy-load><div>Loading...</div></lazy-load></div>');
await sleepAsync(150);
expect(document.body.innerHTML).toBe('<div id="root"><lazy-load><div>Loaded</div></lazy-load></div>');
});
});
it('Shuld display the failed state with a retryer', async () => {
await usingAsync(new Injector(), async (injector) => {
const rootElement = document.getElementById('root');
const load = vi.fn(async () => {
throw Error(':(');
});
initializeShadeRoot({
injector,
rootElement,
jsxElement: (createComponent(LazyLoad, { loader: createComponent("div", null, "Loading..."), component: load, error: (e, retry) => (createComponent("button", { id: "retry", onclick: retry }, e.message)) })),
});
await flushUpdates();
expect(document.body.innerHTML).toBe('<div id="root"><lazy-load><div>Loading...</div></lazy-load></div>');
await flushUpdates();
expect(load).toBeCalledTimes(1);
expect(document.body.innerHTML).toBe('<div id="root"><lazy-load><button id="retry">:(</button></lazy-load></div>');
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');
let counter = 0;
const load = vi.fn(async () => {
if (!counter) {
counter += 1;
throw Error(':(');
}
return createComponent("div", null, "success");
});
initializeShadeRoot({
injector,
rootElement,
jsxElement: (createComponent(LazyLoad, { loader: createComponent("div", null, "Loading..."), component: load, error: (e, retry) => (createComponent("button", { id: "retry", onclick: retry }, e.message)) })),
});
await flushUpdates();
expect(document.body.innerHTML).toBe('<div id="root"><lazy-load><div>Loading...</div></lazy-load></div>');
await flushUpdates();
expect(load).toBeCalledTimes(1);
expect(document.body.innerHTML).toBe('<div id="root"><lazy-load><button id="retry">:(</button></lazy-load></div>');
document.getElementById('retry')?.click();
expect(load).toBeCalledTimes(2);
await flushUpdates();
expect(document.body.innerHTML).toBe('<div id="root"><lazy-load><div>success</div></lazy-load></div>');
});
});
it('should call startViewTransition when viewTransition is enabled and component loads', async () => {
const startViewTransitionSpy = vi.fn((optionsOrCallback) => {
const update = typeof optionsOrCallback === 'function' ? optionsOrCallback : optionsOrCallback.update;
update?.();
return {
finished: Promise.resolve(),
ready: Promise.resolve(),
updateCallbackDone: Promise.resolve(),
skipTransition: vi.fn(),
};
});
document.startViewTransition = startViewTransitionSpy;
await usingAsync(new Injector(), async (injector) => {
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: (createComponent(LazyLoad, { viewTransition: true, loader: createComponent("div", null, "Loading..."), component: async () => {
await sleepAsync(50);
return createComponent("div", null, "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) => {
const update = typeof optionsOrCallback === 'function' ? optionsOrCallback : optionsOrCallback.update;
update?.();
return {
finished: Promise.resolve(),
ready: Promise.resolve(),
updateCallbackDone: Promise.resolve(),
skipTransition: vi.fn(),
};
});
document.startViewTransition = startViewTransitionSpy;
await usingAsync(new Injector(), async (injector) => {
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: (createComponent(LazyLoad, { loader: createComponent("div", null, "Loading..."), component: async () => {
await sleepAsync(50);
return createComponent("div", null, "Loaded");
} })),
});
await flushUpdates();
await sleepAsync(100);
expect(startViewTransitionSpy).not.toHaveBeenCalled();
expect(document.body.innerHTML).toContain('Loaded');
});
});
});
//# sourceMappingURL=lazy-load.spec.js.map