@furystack/shades-common-components
Version:
Common UI components for FuryStack Shades
238 lines • 11.8 kB
JavaScript
import { createInjector } from '@furystack/inject';
import { createComponent, flushUpdates, initializeShadeRoot } from '@furystack/shades';
import { usingAsync } from '@furystack/utils';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { CollectionService } from '../../services/collection-service.js';
import { DataGridBody } from './body.js';
describe('DataGridBody', () => {
beforeEach(() => {
document.body.innerHTML = '<div id="root"></div>';
});
afterEach(() => {
document.body.innerHTML = '';
});
it('should render default empty component when no data', async () => {
await usingAsync(createInjector(), async (injector) => {
await usingAsync(new CollectionService(), async (service) => {
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: (createComponent("table", null,
createComponent(DataGridBody, { service: service, columns: ['id', 'name'] }))),
});
await flushUpdates();
const body = document.querySelector('tbody[is="shade-data-grid-body"]');
expect(body).not.toBeNull();
expect(body?.textContent).toContain('- No Data -');
});
});
});
it('should render custom empty component when provided and no data', async () => {
await usingAsync(createInjector(), async (injector) => {
await usingAsync(new CollectionService(), async (service) => {
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: (createComponent("table", null,
createComponent(DataGridBody, { service: service, columns: ['id', 'name'], emptyComponent: createComponent("div", { "data-testid": "custom-empty" }, "Custom Empty State") }))),
});
await flushUpdates();
const body = document.querySelector('tbody[is="shade-data-grid-body"]');
expect(body).not.toBeNull();
expect(body?.querySelector('[data-testid="custom-empty"]')).not.toBeNull();
expect(body?.textContent).toContain('Custom Empty State');
});
});
});
it('should render rows for each data entry', async () => {
await usingAsync(createInjector(), async (injector) => {
await usingAsync(new CollectionService(), async (service) => {
const rootElement = document.getElementById('root');
service.data.setValue({
count: 2,
entries: [
{ id: 1, name: 'First' },
{ id: 2, name: 'Second' },
],
});
initializeShadeRoot({
injector,
rootElement,
jsxElement: (createComponent("table", null,
createComponent(DataGridBody, { service: service, columns: ['id', 'name'] }))),
});
await flushUpdates();
const body = document.querySelector('tbody[is="shade-data-grid-body"]');
expect(body).not.toBeNull();
const rows = body?.querySelectorAll('shades-data-grid-row');
expect(rows?.length).toBe(2);
});
});
});
it('should render cell content from entry properties', async () => {
await usingAsync(createInjector(), async (injector) => {
await usingAsync(new CollectionService(), async (service) => {
const rootElement = document.getElementById('root');
service.data.setValue({
count: 1,
entries: [{ id: 42, name: 'Test Entry' }],
});
initializeShadeRoot({
injector,
rootElement,
jsxElement: (createComponent("table", null,
createComponent(DataGridBody, { service: service, columns: ['id', 'name'] }))),
});
await flushUpdates();
const body = document.querySelector('tbody[is="shade-data-grid-body"]');
const cells = body?.querySelectorAll('td');
expect(cells?.length).toBe(2);
expect(cells?.[0]?.textContent).toBe('42');
expect(cells?.[1]?.textContent).toBe('Test Entry');
});
});
});
it('should re-render when data observable changes', async () => {
await usingAsync(createInjector(), async (injector) => {
await usingAsync(new CollectionService(), async (service) => {
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: (createComponent("table", null,
createComponent(DataGridBody, { service: service, columns: ['id', 'name'] }))),
});
await flushUpdates();
let body = document.querySelector('tbody[is="shade-data-grid-body"]');
expect(body?.textContent).toContain('- No Data -');
service.data.setValue({
count: 1,
entries: [{ id: 1, name: 'New Entry' }],
});
await flushUpdates();
body = document.querySelector('tbody[is="shade-data-grid-body"]');
const rows = body?.querySelectorAll('shades-data-grid-row');
expect(rows?.length).toBe(1);
});
});
});
it('should call onRowClick callback when row is clicked', async () => {
await usingAsync(createInjector(), async (injector) => {
await usingAsync(new CollectionService(), async (service) => {
const rootElement = document.getElementById('root');
const onRowClick = vi.fn();
const entry = { id: 1, name: 'Clickable' };
service.data.setValue({
count: 1,
entries: [entry],
});
initializeShadeRoot({
injector,
rootElement,
jsxElement: (createComponent("table", null,
createComponent(DataGridBody, { service: service, columns: ['id', 'name'], onRowClick: onRowClick }))),
});
await flushUpdates();
const cell = document.querySelector('td');
cell.click();
expect(onRowClick).toHaveBeenCalledWith(entry, expect.any(MouseEvent));
});
});
});
it('should call onRowDoubleClick callback when row is double-clicked', async () => {
await usingAsync(createInjector(), async (injector) => {
await usingAsync(new CollectionService(), async (service) => {
const rootElement = document.getElementById('root');
const onRowDoubleClick = vi.fn();
const entry = { id: 1, name: 'DoubleClickable' };
service.data.setValue({
count: 1,
entries: [entry],
});
initializeShadeRoot({
injector,
rootElement,
jsxElement: (createComponent("table", null,
createComponent(DataGridBody, { service: service, columns: ['id', 'name'], onRowDoubleClick: onRowDoubleClick }))),
});
await flushUpdates();
const cell = document.querySelector('td');
const dblClickEvent = new MouseEvent('dblclick', { bubbles: true });
cell.dispatchEvent(dblClickEvent);
expect(onRowDoubleClick).toHaveBeenCalledWith(entry, expect.any(MouseEvent));
});
});
});
it('should use custom row components when provided', async () => {
await usingAsync(createInjector(), async (injector) => {
await usingAsync(new CollectionService(), async (service) => {
const rootElement = document.getElementById('root');
service.data.setValue({
count: 1,
entries: [{ id: 1, name: 'Custom' }],
});
initializeShadeRoot({
injector,
rootElement,
jsxElement: (createComponent("table", null,
createComponent(DataGridBody, { service: service, columns: ['id', 'name'], rowComponents: {
id: (entry) => createComponent("span", { "data-testid": "custom-id" },
"ID: ",
entry.id),
name: (entry) => createComponent("strong", { "data-testid": "custom-name" }, entry.name),
} }))),
});
await flushUpdates();
const customId = document.querySelector('[data-testid="custom-id"]');
const customName = document.querySelector('[data-testid="custom-name"]');
expect(customId?.textContent).toContain('ID: 1');
expect(customName?.textContent).toBe('Custom');
});
});
});
it('should use default row component when column-specific one is not provided', async () => {
await usingAsync(createInjector(), async (injector) => {
await usingAsync(new CollectionService(), async (service) => {
const rootElement = document.getElementById('root');
service.data.setValue({
count: 1,
entries: [{ id: 1, name: 'Default' }],
});
initializeShadeRoot({
injector,
rootElement,
jsxElement: (createComponent("table", null,
createComponent(DataGridBody, { service: service, columns: ['id', 'name'], rowComponents: {
default: (entry) => createComponent("em", { "data-testid": "default-cell" }, JSON.stringify(entry)),
} }))),
});
await flushUpdates();
const defaultCells = document.querySelectorAll('[data-testid="default-cell"]');
expect(defaultCells.length).toBe(2);
});
});
});
it('should render with empty entries array', async () => {
await usingAsync(createInjector(), async (injector) => {
await usingAsync(new CollectionService(), async (service) => {
const rootElement = document.getElementById('root');
service.data.setValue({
count: 0,
entries: [],
});
initializeShadeRoot({
injector,
rootElement,
jsxElement: (createComponent("table", null,
createComponent(DataGridBody, { service: service, columns: ['id', 'name'] }))),
});
await flushUpdates();
const body = document.querySelector('tbody[is="shade-data-grid-body"]');
expect(body?.textContent).toContain('- No Data -');
});
});
});
});
//# sourceMappingURL=body.spec.js.map