@furystack/shades-common-components
Version:
Common UI components for FuryStack Shades
267 lines • 12.1 kB
JavaScript
import { createInjector } from '@furystack/inject';
import { createComponent, initializeShadeRoot } from '@furystack/shades';
import { sleepAsync, usingAsync } from '@furystack/utils';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { cssVariableTheme } from '../services/css-variable-theme.js';
import { ThemeProviderService } from '../services/theme-provider-service.js';
import { Loader } from './loader.js';
describe('Loader', () => {
let originalAnimate;
let animateCalls;
beforeEach(() => {
document.body.innerHTML = '<div id="root"></div>';
animateCalls = [];
originalAnimate = Element.prototype.animate;
Element.prototype.animate = vi.fn((keyframes, options) => {
animateCalls.push({ keyframes, options });
const mockAnimation = {
onfinish: null,
oncancel: null,
cancel: vi.fn(),
play: vi.fn(),
pause: vi.fn(),
finish: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
};
return mockAnimation;
});
});
afterEach(() => {
document.body.innerHTML = '';
Element.prototype.animate = originalAnimate;
vi.restoreAllMocks();
});
it('should render as custom element', async () => {
await usingAsync(createInjector(), async (injector) => {
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(Loader, null),
});
await sleepAsync(50);
const loader = document.querySelector('shade-loader');
expect(loader).not.toBeNull();
});
});
it('should have initial opacity of 0', async () => {
await usingAsync(createInjector(), async (injector) => {
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(Loader, null),
});
await sleepAsync(50);
const loader = document.querySelector('shade-loader');
expect(loader).not.toBeNull();
const computedStyle = window.getComputedStyle(loader);
expect(computedStyle.opacity).toBe('0');
});
});
it('should have correct css styles applied', async () => {
await usingAsync(createInjector(), async (injector) => {
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(Loader, null),
});
await sleepAsync(50);
const loader = document.querySelector('shade-loader');
expect(loader).not.toBeNull();
const computedStyle = window.getComputedStyle(loader);
expect(computedStyle.display).toBe('inline-block');
expect(computedStyle.transformOrigin).toBe('center');
});
});
it('should use default delay of 500ms', async () => {
await usingAsync(createInjector(), async (injector) => {
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(Loader, null),
});
await sleepAsync(50);
const fadeInCall = animateCalls.find((call) => Array.isArray(call.keyframes) && call.keyframes.some((kf) => 'opacity' in kf));
expect(fadeInCall).toBeDefined();
expect(fadeInCall?.options?.delay).toBe(500);
});
});
it('should use custom delay when provided', async () => {
await usingAsync(createInjector(), async (injector) => {
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(Loader, { delay: 200 }),
});
await sleepAsync(50);
const fadeInCall = animateCalls.find((call) => Array.isArray(call.keyframes) && call.keyframes.some((kf) => 'opacity' in kf));
expect(fadeInCall).toBeDefined();
expect(fadeInCall?.options?.delay).toBe(200);
});
});
it('should start fade-in animation with correct parameters', async () => {
await usingAsync(createInjector(), async (injector) => {
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(Loader, { delay: 100 }),
});
await sleepAsync(50);
const fadeInCall = animateCalls.find((call) => Array.isArray(call.keyframes) &&
call.keyframes.length === 2 &&
call.keyframes[0].opacity === '0' &&
call.keyframes[1].opacity === '1');
expect(fadeInCall).toBeDefined();
const options = fadeInCall?.options;
expect(options.fill).toBe('forwards');
expect(options.duration).toBe(500);
expect(options.delay).toBe(100);
});
});
it('should start rotation animation', async () => {
await usingAsync(createInjector(), async (injector) => {
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(Loader, null),
});
await sleepAsync(50);
const rotationCall = animateCalls.find((call) => Array.isArray(call.keyframes) && call.keyframes.some((kf) => 'transform' in kf));
expect(rotationCall).toBeDefined();
const options = rotationCall?.options;
expect(options.duration).toBe(1500);
expect(options.easing).toBe('ease-in-out');
expect(options.iterations).toBe(Infinity);
});
});
it('should have rotation keyframes from 0 to 360 degrees', async () => {
await usingAsync(createInjector(), async (injector) => {
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(Loader, null),
});
await sleepAsync(50);
const rotationCall = animateCalls.find((call) => Array.isArray(call.keyframes) && call.keyframes.some((kf) => 'transform' in kf));
expect(rotationCall).toBeDefined();
const keyframes = rotationCall?.keyframes;
expect(keyframes).toHaveLength(3);
expect(keyframes[0].transform).toBe('rotate(0deg)');
expect(keyframes[1].transform).toBe('rotate(180deg)');
expect(keyframes[2].transform).toBe('rotate(360deg)');
});
});
it('should use default borderWidth of 15px', async () => {
await usingAsync(createInjector(), async (injector) => {
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(Loader, null),
});
await sleepAsync(50);
const loader = document.querySelector('shade-loader');
expect(loader).not.toBeNull();
const innerDiv = loader.querySelector('div');
expect(innerDiv).not.toBeNull();
expect(innerDiv.style.width).toBe('calc(100% - 30px)');
expect(innerDiv.style.height).toBe('calc(100% - 30px)');
const styleAttr = innerDiv.getAttribute('style') || '';
expect(styleAttr).toContain('15px solid');
});
});
it('should use custom borderWidth when provided', async () => {
await usingAsync(createInjector(), async (injector) => {
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(Loader, { borderWidth: 10 }),
});
await sleepAsync(50);
const loader = document.querySelector('shade-loader');
expect(loader).not.toBeNull();
const innerDiv = loader.querySelector('div');
expect(innerDiv).not.toBeNull();
expect(innerDiv.style.width).toBe('calc(100% - 20px)');
expect(innerDiv.style.height).toBe('calc(100% - 20px)');
const styleAttr = innerDiv.getAttribute('style') || '';
expect(styleAttr).toContain('10px solid');
});
});
it('should use custom borderColor when provided', async () => {
await usingAsync(createInjector(), async (injector) => {
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(Loader, { borderColor: "#ff0000" }),
});
await sleepAsync(50);
const loader = document.querySelector('shade-loader');
expect(loader).not.toBeNull();
const innerDiv = loader.querySelector('div');
expect(innerDiv).not.toBeNull();
expect(innerDiv.style.borderBottomColor).toBe('rgb(255, 0, 0)');
});
});
it('should use theme primary color as default borderColor', async () => {
await usingAsync(createInjector(), async (injector) => {
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(Loader, null),
});
await sleepAsync(50);
const loader = document.querySelector('shade-loader');
expect(loader).not.toBeNull();
const innerDiv = loader.querySelector('div');
expect(innerDiv).not.toBeNull();
const themeService = injector.get(ThemeProviderService);
expect(innerDiv.style.borderBottom).toContain(themeService.theme.palette.primary.main);
});
});
it('should render with circular shape', async () => {
await usingAsync(createInjector(), async (injector) => {
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(Loader, null),
});
await sleepAsync(50);
const loader = document.querySelector('shade-loader');
expect(loader).not.toBeNull();
const innerDiv = loader.querySelector('div');
expect(innerDiv).not.toBeNull();
expect(innerDiv.style.borderRadius).toBe(cssVariableTheme.shape.borderRadius.full);
expect(innerDiv.style.position).toBe('relative');
});
});
it('should have a semi-transparent main border and colored bottom border', async () => {
await usingAsync(createInjector(), async (injector) => {
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(Loader, { borderColor: "#00ff00" }),
});
await sleepAsync(50);
const loader = document.querySelector('shade-loader');
expect(loader).not.toBeNull();
const innerDiv = loader.querySelector('div');
expect(innerDiv).not.toBeNull();
expect(innerDiv.style.borderBottom).toContain('rgb(0, 255, 0)');
});
});
});
//# sourceMappingURL=loader.spec.js.map