PSA/ee/extensions/samples/ui-kit-showcase/test/data-dialog-feedback.test.tsx
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

226 lines
9.2 KiB
TypeScript

import React from 'react';
import { describe, expect, test } from 'vitest';
import { render, screen, within, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { DataTableDemo } from '../src/demos/DataTableDemo';
import { DialogDemo } from '../src/demos/DialogDemo';
import { ConfirmDialogDemo } from '../src/demos/ConfirmDialogDemo';
import { SpinnerDemo } from '../src/demos/SpinnerDemo';
import { LoadingIndicatorDemo } from '../src/demos/LoadingIndicatorDemo';
import { TooltipDemo } from '../src/demos/TooltipDemo';
import { ProgressDemo } from '../src/demos/ProgressDemo';
import { SkeletonDemo } from '../src/demos/SkeletonDemo';
describe('DataTable demo', () => {
test('table renders with column headers', () => {
render(<DataTableDemo />);
expect(screen.getByText('Company')).toBeInTheDocument();
expect(screen.getByText('Plan')).toBeInTheDocument();
expect(screen.getByText('Status')).toBeInTheDocument();
});
test('clicking sortable header sorts data', async () => {
const user = userEvent.setup();
render(<DataTableDemo />);
const rows = screen.getAllByRole('row');
const firstCell = rows[1].querySelectorAll('td')[0].textContent;
await user.click(screen.getByRole('button', { name: /Company/ }));
const updatedRows = screen.getAllByRole('row');
const updatedFirstCell = updatedRows[1].querySelectorAll('td')[0].textContent;
expect(updatedFirstCell).not.toBe(firstCell);
});
test('pagination controls are visible', () => {
render(<DataTableDemo />);
expect(screen.getByText(/of .* items/)).toBeInTheDocument();
});
test('page size selector changes rows per page', async () => {
const user = userEvent.setup();
render(<DataTableDemo />);
const initialRows = screen.getAllByRole('row').length;
const selector = screen.getByRole('combobox');
await user.click(selector);
await user.click(await screen.findByRole('option', { name: '10 per page' }));
const updatedRows = screen.getAllByRole('row').length;
expect(updatedRows).toBeGreaterThan(initialRows);
});
test('responsive mode hides columns when narrow', async () => {
render(<DataTableDemo />);
const table = screen.getByRole('table');
const container = table.parentElement?.parentElement as HTMLElement;
Object.defineProperty(container, 'clientWidth', { value: 200, configurable: true });
window.dispatchEvent(new Event('resize'));
await waitFor(() => {
expect(screen.queryByText('MRR')).not.toBeInTheDocument();
});
});
test('custom cell render displays Badge', () => {
render(<DataTableDemo />);
expect(screen.getAllByText('Active')[0].tagName).toBe('SPAN');
});
});
describe('Dialog demo', () => {
test('dialog opens when trigger is clicked', async () => {
const user = userEvent.setup();
render(<DialogDemo />);
await user.click(screen.getByRole('button', { name: 'Open Dialog' }));
expect(screen.getByRole('dialog')).toBeInTheDocument();
});
test('dialog closes when close button is clicked', async () => {
const user = userEvent.setup();
render(<DialogDemo />);
await user.click(screen.getByRole('button', { name: 'Open Dialog' }));
await user.click(screen.getByRole('button', { name: 'Cancel' }));
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
});
test('dialog displays title in header', async () => {
const user = userEvent.setup();
render(<DialogDemo />);
await user.click(screen.getByRole('button', { name: 'Open Dialog' }));
expect(screen.getByText('Invite teammates')).toBeInTheDocument();
});
});
describe('ConfirmDialog demo', () => {
test('confirm button triggers onConfirm callback', async () => {
const user = userEvent.setup();
render(<ConfirmDialogDemo />);
await user.click(screen.getByRole('button', { name: 'Open Confirm' }));
await user.click(screen.getByRole('button', { name: 'Confirm' }));
expect(screen.getByText(/Last action: Confirmed/)).toBeInTheDocument();
});
test('cancel button triggers onCancel callback', async () => {
const user = userEvent.setup();
render(<ConfirmDialogDemo />);
await user.click(screen.getByRole('button', { name: 'Open Confirm' }));
await user.click(screen.getByRole('button', { name: 'Cancel' }));
expect(screen.getByText(/Last action: Canceled/)).toBeInTheDocument();
});
test('danger variant shows destructive confirm button', async () => {
const user = userEvent.setup();
render(<ConfirmDialogDemo />);
await user.click(screen.getByRole('button', { name: 'Open Danger Confirm' }));
const deleteButton = screen.getByRole('button', { name: 'Delete' });
expect(deleteButton.style.background).toContain('var(--alga-accent)');
});
});
describe('Spinner demo', () => {
test('spinner animates', () => {
render(<SpinnerDemo />);
const spinner = screen.getAllByRole('status')[0] as HTMLElement;
expect(spinner.style.animation).toContain('alga-spinner-spin');
});
test('different sizes render correctly', () => {
render(<SpinnerDemo />);
const spinners = screen.getAllByRole('status');
expect(spinners.length).toBeGreaterThan(3);
});
});
describe('LoadingIndicator demo', () => {
test('text is displayed alongside spinner', () => {
render(<LoadingIndicatorDemo />);
expect(screen.getByText('Syncing updates')).toBeInTheDocument();
});
});
describe('Tooltip demo', () => {
test('tooltip appears on hover', async () => {
const user = userEvent.setup();
render(<TooltipDemo />);
await user.hover(screen.getByRole('button', { name: 'Top' }));
expect(await screen.findByText('Tooltip on top')).toBeInTheDocument();
});
test('tooltip positions correctly for each placement', async () => {
const user = userEvent.setup();
render(<TooltipDemo />);
await user.hover(screen.getByRole('button', { name: 'Left' }));
expect(await screen.findByText('Tooltip on left')).toBeInTheDocument();
await user.unhover(screen.getByRole('button', { name: 'Left' }));
await user.hover(screen.getByRole('button', { name: 'Right' }));
expect(await screen.findByText('Tooltip on right')).toBeInTheDocument();
});
});
describe('Progress demo', () => {
test('progress bar fills to specified value', () => {
render(<ProgressDemo />);
const progress = screen.getAllByRole('progressbar').find((el) => el.getAttribute('aria-valuenow') === '50');
expect(progress).toBeTruthy();
const track = progress?.querySelector('div') as HTMLElement | null;
const bar = track?.querySelector('div') as HTMLElement | null;
expect(bar?.style.width).toBe('50%');
});
test('striped variant shows stripe pattern', () => {
render(<ProgressDemo />);
const striped = document.querySelector('[aria-hidden="true"] div') as HTMLElement | null;
expect(striped?.style.backgroundImage).toContain('repeating-linear-gradient');
});
test('animated variant has moving animation', () => {
render(<ProgressDemo />);
const progress = screen.getAllByRole('progressbar').find((el) => el.getAttribute('aria-valuenow') === '70');
const track = progress?.querySelector('div') as HTMLElement | null;
const bar = track?.querySelector('div') as HTMLElement | null;
expect(bar?.style.transition).toContain('width');
});
test('size variants change bar height', () => {
render(<ProgressDemo />);
const sizeBars = screen.getAllByRole('progressbar').filter((el) => el.getAttribute('aria-valuenow') === '35');
const smTrack = sizeBars[0].querySelector('div') as HTMLElement;
const lgTrack = sizeBars[2].querySelector('div') as HTMLElement;
expect(Number(smTrack.style.height.replace('px', ''))).toBeLessThan(Number(lgTrack.style.height.replace('px', '')));
});
test('indeterminate mode shows continuous animation', () => {
render(<ProgressDemo />);
const indeterminate = screen.getAllByRole('progressbar').find((el) => el.getAttribute('aria-valuenow') === '25');
const track = indeterminate?.querySelector('div') as HTMLElement | null;
const bar = track?.querySelector('div') as HTMLElement | null;
expect(bar?.style.animation).toContain('progress-indeterminate');
});
});
describe('Skeleton demo', () => {
test('basic skeleton renders with pulse animation', () => {
render(<SkeletonDemo />);
const label = screen.getByText('Basic');
const skeleton = label.nextElementSibling?.querySelector('span') as HTMLElement;
expect(skeleton?.style.animation).toContain('skeleton-pulse');
});
test('SkeletonText renders multiple lines', () => {
render(<SkeletonDemo />);
const container = screen.getByText('Text Lines').parentElement as HTMLElement;
const lines = container.querySelectorAll('span');
expect(lines.length).toBeGreaterThan(1);
});
test('SkeletonCircle renders as circle shape', () => {
render(<SkeletonDemo />);
const label = screen.getByText('Circle');
const circle = label.nextElementSibling?.querySelector('span') as HTMLElement;
expect(circle?.style.borderRadius).toBe('50%');
});
test('SkeletonRectangle renders with specified dimensions', () => {
render(<SkeletonDemo />);
const label = screen.getByText('Rectangle');
const rect = label.nextElementSibling?.querySelector('span') as HTMLElement;
expect(rect?.style.height).toBe('120px');
});
});