From 12147864780839df63c2365b4933fef6a4b029de Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 14:02:20 +0000 Subject: [PATCH 1/2] Initial plan From 41ba4f4f0de6931c6dd41a89f53bfb9b5d511378 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:01:15 +0000 Subject: [PATCH 2/2] Remove unnecessary CI tests to reduce test suite overhead Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- .../src/__tests__/ConsoleFeatures.test.tsx | 114 ----- .../src/__tests__/ServerDefinitions.test.tsx | 402 ------------------ .../src/__tests__/SkeletonComponents.test.tsx | 61 --- .../src/__tests__/SpecCompliance.test.tsx | 346 --------------- .../__tests__/console-accessibility.test.tsx | 350 --------------- .../console-load-performance.test.tsx | 366 ---------------- .../components/src/__tests__/Registry.test.ts | 21 - .../plugin-charts/src/registration.test.tsx | 22 - .../plugin-detail/src/registration.test.tsx | 18 - .../plugin-list/src/registration.test.tsx | 8 - .../src/__tests__/registration.test.tsx | 32 -- 11 files changed, 1740 deletions(-) delete mode 100644 apps/console/src/__tests__/ConsoleFeatures.test.tsx delete mode 100644 apps/console/src/__tests__/ServerDefinitions.test.tsx delete mode 100644 apps/console/src/__tests__/SkeletonComponents.test.tsx delete mode 100644 apps/console/src/__tests__/SpecCompliance.test.tsx delete mode 100644 apps/console/src/__tests__/console-accessibility.test.tsx delete mode 100644 apps/console/src/__tests__/console-load-performance.test.tsx delete mode 100644 packages/components/src/__tests__/Registry.test.ts delete mode 100644 packages/plugin-charts/src/registration.test.tsx delete mode 100644 packages/plugin-detail/src/registration.test.tsx delete mode 100644 packages/plugin-list/src/registration.test.tsx delete mode 100644 packages/plugin-view/src/__tests__/registration.test.tsx diff --git a/apps/console/src/__tests__/ConsoleFeatures.test.tsx b/apps/console/src/__tests__/ConsoleFeatures.test.tsx deleted file mode 100644 index a3eed8805..000000000 --- a/apps/console/src/__tests__/ConsoleFeatures.test.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { render, screen } from '@testing-library/react'; -import '@testing-library/jest-dom'; -import { ObjectView } from '../components/ObjectView'; -import { MemoryRouter, Route, Routes } from 'react-router-dom'; - -// Mock child plugins to focus on ObjectView logic -vi.mock('@object-ui/plugin-grid', () => ({ - ObjectGrid: (props: any) =>
Grid View: {props.schema.objectName} (Filter: {JSON.stringify(props.schema.filter)})
-})); - -vi.mock('@object-ui/plugin-kanban', () => ({ - ObjectKanban: () =>
Kanban View
-})); - -vi.mock('@object-ui/plugin-calendar', () => ({ - ObjectCalendar: () =>
Calendar View
-})); - -vi.mock('@object-ui/plugin-list', () => ({ - ListView: (props: any) => ( -
- ListView for {props.schema.objectName} - -
- ) -})); - -// Mock UI Components -vi.mock('@object-ui/components', async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - Button: ({ children, onClick, className }: any) => ( - - ), - Empty: ({ children }: any) =>
{children}
, - EmptyTitle: ({ children }: any) =>
{children}
, - EmptyDescription: ({ children }: any) =>
{children}
, - Sheet: ({ children }: any) =>
{children}
, - SheetContent: ({ children }: any) =>
{children}
, - SheetHeader: ({ children }: any) =>
{children}
, - SheetTitle: ({ children }: any) =>
{children}
, - }; -}); - -// Mock Icons -vi.mock('lucide-react', async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - Plus: () => +, - Calendar: () => Cal, - Kanban: () => Kan, - Table: () => Tab, - AlignLeft: () => Gantt, - Filter: () => FilterIcon, - X: () => X, - Code2: () => Code, - }; -}); - -describe('ObjectView Console Features', () => { - - const mockObjects = [ - { - name: 'todo_task', - label: 'Todo Task', - fields: { name: { type: 'text' }, status: { type: 'select' } }, - list_views: { - all: { label: 'All Tasks', type: 'grid' } - } - } - ]; - - const mockDataSource = { - find: vi.fn().mockResolvedValue([]), - findOne: vi.fn(), - create: vi.fn(), - update: vi.fn(), - delete: vi.fn() - }; - - const renderObjectView = (objectName = 'todo_task') => { - return render( - - - - } /> - - - ); - }; - - it('renders ListView', () => { - renderObjectView(); - - // "Todo Task" appears in breadcrumb and h1 - const headers = screen.getAllByText('Todo Task'); - expect(headers.length).toBeGreaterThanOrEqual(1); - // Verify ListView is rendered instead of direct Filter button - expect(screen.getByTestId('list-view')).toBeInTheDocument(); - expect(screen.getByText('ListView for todo_task')).toBeInTheDocument(); - }); - - // Removed Filter button test as functionality moved to ListView plugin -}); diff --git a/apps/console/src/__tests__/ServerDefinitions.test.tsx b/apps/console/src/__tests__/ServerDefinitions.test.tsx deleted file mode 100644 index f827a1429..000000000 --- a/apps/console/src/__tests__/ServerDefinitions.test.tsx +++ /dev/null @@ -1,402 +0,0 @@ -/** - * Server-Driven Layout Tests - * - * Tests for server-driven app layout, page definitions, and component composition - */ - -import { describe, it, expect, beforeAll, afterAll } from 'vitest'; -import { render, screen } from '@testing-library/react'; -import '@testing-library/jest-dom'; -import { SchemaRenderer, SchemaRendererProvider } from '@object-ui/react'; -import type { AppSchema } from '@object-ui/types'; -import { startMockServer, stopMockServer } from '../mocks/server'; - -describe('Server-Driven Definitions', () => { - beforeAll(async () => { - await startMockServer(); - }); - - afterAll(() => { - stopMockServer(); - }); - - describe('App Layout Definition', () => { - it('should render app layout with sidebar', () => { - const appSchema: AppSchema = { - type: 'app', - name: 'test-app', - title: 'Test Application', - description: 'Test app for MSW integration', - layout: 'sidebar', - menu: [ - { - type: 'item', - label: 'Dashboard', - icon: 'layout-dashboard', - href: '/dashboard', - }, - { - type: 'item', - label: 'Contacts', - icon: 'users', - href: '/contacts', - }, - ], - }; - - // Note: AppSchema would typically be rendered by a higher-level component - // This is a simplified test for the schema structure - expect(appSchema.type).toBe('app'); - expect(appSchema.layout).toBe('sidebar'); - expect(appSchema.menu).toHaveLength(2); - expect(appSchema.menu![0].label).toBe('Dashboard'); - }); - - it('should render app layout with header navigation', () => { - const appSchema: AppSchema = { - type: 'app', - name: 'header-app', - title: 'Header App', - layout: 'header', - menu: [ - { - type: 'item', - label: 'Home', - href: '/', - }, - { - type: 'item', - label: 'About', - href: '/about', - }, - ], - }; - - expect(appSchema.layout).toBe('header'); - expect(appSchema.menu).toHaveLength(2); - }); - - it('should define app with menu groups', () => { - const appSchema: AppSchema = { - type: 'app', - name: 'grouped-app', - title: 'Grouped App', - layout: 'sidebar', - menu: [ - { - type: 'group', - label: 'Main', - children: [ - { type: 'item', label: 'Dashboard', href: '/dashboard' }, - { type: 'item', label: 'Overview', href: '/overview' }, - ], - }, - { - type: 'separator', - }, - { - type: 'group', - label: 'Settings', - children: [ - { type: 'item', label: 'Profile', href: '/profile' }, - { type: 'item', label: 'Preferences', href: '/preferences' }, - ], - }, - ], - }; - - expect(appSchema.menu).toHaveLength(3); - expect(appSchema.menu![0].type).toBe('group'); - expect(appSchema.menu![1].type).toBe('separator'); - }); - }); - - describe('Page Definition', () => { - it('should define a page with layout components', () => { - const pageSchema = { - type: 'div', - className: 'container mx-auto p-4', - children: [ - { - type: 'text', - value: 'Contact List', - variant: 'h1', - }, - { - type: 'object-grid', - objectName: 'contact', - columns: ['name', 'email', 'company'], - }, - ], - }; - - // Test that the schema can be rendered - render( - - - - ); - - expect(screen.getByText('Contact List')).toBeInTheDocument(); - }); - - it('should define a dashboard page', () => { - const dashboardPage = { - type: 'div', - className: 'p-6', - children: [ - { - type: 'text', - value: 'Dashboard', - variant: 'h1', - }, - { - type: 'dashboard', - columns: 3, - widgets: [ - { - id: 'metric-1', - title: 'Total Users', - component: { - type: 'metric', - label: 'Users', - value: '150', - }, - layout: { x: 0, y: 0, w: 1, h: 1 }, - }, - { - id: 'metric-2', - title: 'Active Sessions', - component: { - type: 'metric', - label: 'Sessions', - value: '42', - }, - layout: { x: 1, y: 0, w: 1, h: 1 }, - }, - ], - }, - ], - }; - - render(); - - expect(screen.getByText('Dashboard')).toBeInTheDocument(); - expect(screen.getByText('Total Users')).toBeInTheDocument(); - expect(screen.getByText('Active Sessions')).toBeInTheDocument(); - }); - - it('should define a form page', () => { - const formPage = { - type: 'div', - className: 'max-w-2xl mx-auto p-6', - children: [ - { - type: 'text', - value: 'Create Contact', - variant: 'h1', - }, - { - type: 'object-form', - objectName: 'contact', - mode: 'create', - fields: ['name', 'email', 'company'], - }, - ], - }; - - expect(formPage.children[0].value).toBe('Create Contact'); - expect(formPage.children[1].type).toBe('object-form'); - expect(formPage.children[1].mode).toBe('create'); - }); - }); - - describe('Component Composition from Server', () => { - it('should render nested layout components', () => { - const nestedLayout = { - type: 'div', - className: 'flex flex-col gap-4', - children: [ - { - type: 'div', - className: 'header', - children: { - type: 'text', - value: 'Header Section', - variant: 'h2', - }, - }, - { - type: 'div', - className: 'content flex gap-4', - children: [ - { - type: 'div', - className: 'sidebar w-64', - children: { - type: 'text', - value: 'Sidebar', - }, - }, - { - type: 'div', - className: 'main flex-1', - children: { - type: 'text', - value: 'Main Content', - }, - }, - ], - }, - ], - }; - - render(); - - expect(screen.getByText('Header Section')).toBeInTheDocument(); - expect(screen.getByText('Sidebar')).toBeInTheDocument(); - expect(screen.getByText('Main Content')).toBeInTheDocument(); - }); - - it('should render complex page composition', () => { - const complexPage = { - type: 'div', - className: 'min-h-screen bg-gray-50', - children: [ - { - type: 'div', - className: 'header bg-white shadow', - children: { - type: 'div', - className: 'container mx-auto p-4', - children: { - type: 'text', - value: 'CRM Dashboard', - variant: 'h1', - }, - }, - }, - { - type: 'div', - className: 'container mx-auto p-6', - children: [ - { - type: 'dashboard', - columns: 4, - gap: 4, - widgets: [ - { - id: 'contacts-metric', - title: 'Total Contacts', - component: { type: 'metric', label: 'Contacts', value: '3' }, - layout: { x: 0, y: 0, w: 1, h: 1 }, - }, - { - id: 'active-metric', - title: 'Active', - component: { type: 'metric', label: 'Active', value: '2' }, - layout: { x: 1, y: 0, w: 1, h: 1 }, - }, - ], - }, - ], - }, - ], - }; - - render(); - - expect(screen.getByText('CRM Dashboard')).toBeInTheDocument(); - expect(screen.getAllByText('Total Contacts')).toHaveLength(1); - expect(screen.getAllByText('Active')).toHaveLength(2); - }); - }); - - describe('Dynamic Server-Driven Content', () => { - it('should support data binding in server definitions', async () => { - // This test demonstrates how server-driven schemas can include dynamic data - const dynamicSchema = { - type: 'div', - children: [ - { - type: 'text', - value: 'Contact Directory', - variant: 'h1', - }, - { - type: 'object-grid', - objectName: 'contact', - columns: ['name', 'email'], - data: { - provider: 'object', - object: 'contact', - }, - }, - ], - }; - - expect(dynamicSchema.children[1].objectName).toBe('contact'); - expect(dynamicSchema.children[1].data?.provider).toBe('object'); - }); - - it('should support conditional rendering based on server data', () => { - const conditionalSchema = { - type: 'div', - children: [ - { - type: 'text', - value: 'Dashboard', - variant: 'h1', - }, - { - type: 'div', - visible: true, // Would be dynamic from server - children: { - type: 'metric', - label: 'Visible Metric', - value: '100', - }, - }, - ], - }; - - expect(conditionalSchema.children[1].visible).toBe(true); - }); - }); - - describe('Layout Schema Validation', () => { - it('should validate basic layout structure', () => { - const layout = { - type: 'div', - className: 'container', - children: [ - { type: 'text', value: 'Hello' }, - { type: 'text', value: 'World' }, - ], - }; - - expect(layout.type).toBe('div'); - expect(layout.children).toHaveLength(2); - expect(Array.isArray(layout.children)).toBe(true); - }); - - it('should support single child or array of children', () => { - const singleChild = { - type: 'div', - children: { type: 'text', value: 'Single' }, - }; - - const multipleChildren = { - type: 'div', - children: [ - { type: 'text', value: 'First' }, - { type: 'text', value: 'Second' }, - ], - }; - - expect(singleChild.children.type).toBe('text'); - expect(Array.isArray(multipleChildren.children)).toBe(true); - expect(multipleChildren.children).toHaveLength(2); - }); - }); -}); diff --git a/apps/console/src/__tests__/SkeletonComponents.test.tsx b/apps/console/src/__tests__/SkeletonComponents.test.tsx deleted file mode 100644 index 37c9c2225..000000000 --- a/apps/console/src/__tests__/SkeletonComponents.test.tsx +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Tests for skeleton loading components - */ -import { describe, it, expect } from 'vitest'; -import { render } from '@testing-library/react'; -import '@testing-library/jest-dom'; -import { SkeletonGrid } from '../components/skeletons/SkeletonGrid'; -import { SkeletonDashboard } from '../components/skeletons/SkeletonDashboard'; -import { SkeletonDetail } from '../components/skeletons/SkeletonDetail'; - -// Mock @object-ui/components Skeleton -vi.mock('@object-ui/components', () => ({ - Skeleton: ({ className, ...props }: any) => ( -
- ), -})); - -describe('SkeletonGrid', () => { - it('renders with default props', () => { - const { container } = render(); - const skeletons = container.querySelectorAll('[data-testid="skeleton"]'); - // Header (5) + 8 rows x 5 cols (40) + toolbar (4) + pagination (4) = 53 - expect(skeletons.length).toBeGreaterThan(0); - }); - - it('renders correct number of rows', () => { - const { container } = render(); - // Should have skeletons for 3 rows x 2 columns in the table body - const rowContainers = container.querySelectorAll('.border-b'); - expect(rowContainers.length).toBeGreaterThanOrEqual(3); - }); -}); - -describe('SkeletonDashboard', () => { - it('renders with default props', () => { - const { container } = render(); - const skeletons = container.querySelectorAll('[data-testid="skeleton"]'); - expect(skeletons.length).toBeGreaterThan(0); - }); - - it('renders correct number of widget cards', () => { - const { container } = render(); - // 3 widget cards, each with 3 skeletons + stats row (4 cards x 3 each) + header (2) - const skeletons = container.querySelectorAll('[data-testid="skeleton"]'); - expect(skeletons.length).toBeGreaterThan(0); - }); -}); - -describe('SkeletonDetail', () => { - it('renders with default props', () => { - const { container } = render(); - const skeletons = container.querySelectorAll('[data-testid="skeleton"]'); - expect(skeletons.length).toBeGreaterThan(0); - }); - - it('renders correct number of field rows', () => { - const { container } = render(); - const skeletons = container.querySelectorAll('[data-testid="skeleton"]'); - expect(skeletons.length).toBeGreaterThan(0); - }); -}); diff --git a/apps/console/src/__tests__/SpecCompliance.test.tsx b/apps/console/src/__tests__/SpecCompliance.test.tsx deleted file mode 100644 index feed99dc1..000000000 --- a/apps/console/src/__tests__/SpecCompliance.test.tsx +++ /dev/null @@ -1,346 +0,0 @@ - -import { describe, it, expect } from 'vitest'; -import appConfig from '../../objectstack.config'; - -/** - * Spec Compliance Tests - * - * These tests verify that the console properly implements the ObjectStack Spec v0.9.0 - * See: apps/console/SPEC_ALIGNMENT.md for full compliance details - */ - -describe('ObjectStack Spec v0.9.0 Compliance', () => { - - describe('AppSchema Validation', () => { - it('should have at least one app defined', () => { - expect(appConfig.apps).toBeDefined(); - expect(Array.isArray(appConfig.apps)).toBe(true); - expect(appConfig.apps!.length).toBeGreaterThan(0); - }); - - it('should have valid app structure', () => { - const app = appConfig.apps![0]; - - // Required fields per spec - expect(app.name).toBeDefined(); - expect(typeof app.name).toBe('string'); - expect(app.label).toBeDefined(); - expect(['string', 'object']).toContain(typeof app.label); - if (typeof app.label === 'object') { - expect(app.label).toHaveProperty('key'); - } - - // Name convention: lowercase snake_case - expect(app.name).toMatch(/^[a-z][a-z0-9_]*$/); - }); - - it('should support optional app metadata', () => { - const app = appConfig.apps![0]; - - // Optional fields that should be defined if present - if (app.description) { - expect(['string', 'object']).toContain(typeof app.description); - if (typeof app.description === 'object') { - expect(app.description).toHaveProperty('key'); - } - } - if (app.version) { - expect(typeof app.version).toBe('string'); - } - if (app.icon) { - expect(typeof app.icon).toBe('string'); - } - }); - - it('should support app branding configuration', () => { - const appsWithBranding = appConfig.apps!.filter((a: any) => a.branding); - - if (appsWithBranding.length > 0) { - const app = appsWithBranding[0]; - - if (app.branding.primaryColor) { - // Should be a valid CSS color - expect(app.branding.primaryColor).toMatch(/^#[0-9A-Fa-f]{6}$/); - } - - if (app.branding.logo) { - expect(typeof app.branding.logo).toBe('string'); - } - - if (app.branding.favicon) { - expect(typeof app.branding.favicon).toBe('string'); - } - } - }); - - it('should support homePageId for custom landing pages', () => { - // homePageId is optional but should be a string if defined - appConfig.apps!.forEach((app: any) => { - if (app.homePageId) { - expect(typeof app.homePageId).toBe('string'); - expect(app.homePageId.length).toBeGreaterThan(0); - } - }); - }); - - it('should support permission requirements', () => { - // requiredPermissions is optional but should be an array if defined - appConfig.apps!.forEach((app: any) => { - if (app.requiredPermissions) { - expect(Array.isArray(app.requiredPermissions)).toBe(true); - app.requiredPermissions.forEach((perm: any) => { - expect(typeof perm).toBe('string'); - }); - } - }); - }); - }); - - describe('NavigationItem Validation', () => { - it('should have navigation items defined', () => { - const app = appConfig.apps![0]; - expect(app.navigation).toBeDefined(); - expect(Array.isArray(app.navigation)).toBe(true); - }); - - it('should support object navigation items', () => { - const objectNavItems = getAllNavItems(appConfig.apps![0].navigation) - .filter((item: any) => item.type === 'object'); - - if (objectNavItems.length > 0) { - const item = objectNavItems[0]; - expect(item.id).toBeDefined(); - expect(item.label).toBeDefined(); - expect(item.objectName).toBeDefined(); - expect(typeof item.objectName).toBe('string'); - } - }); - - it('should support group navigation items', () => { - const groupNavItems = getAllNavItems(appConfig.apps![0].navigation) - .filter((item: any) => item.type === 'group'); - - if (groupNavItems.length > 0) { - const item = groupNavItems[0]; - expect(item.id).toBeDefined(); - expect(item.label).toBeDefined(); - expect(item.children).toBeDefined(); - expect(Array.isArray(item.children)).toBe(true); - } - }); - - it('should have valid navigation item structure', () => { - const allNavItems = getAllNavItems(appConfig.apps![0].navigation); - - allNavItems.forEach((item: any) => { - // All items must have id, label, and type - expect(item.id).toBeDefined(); - expect(item.label).toBeDefined(); - expect(item.type).toBeDefined(); - - // Type must be one of the valid types - expect(['object', 'dashboard', 'page', 'url', 'group', 'report', 'separator', 'action']).toContain(item.type); - - // Type-specific validation - if (item.type === 'object') { - expect(item.objectName).toBeDefined(); - } - if (item.type === 'dashboard') { - expect(item.dashboardName).toBeDefined(); - } - if (item.type === 'page') { - expect(item.pageName).toBeDefined(); - } - if (item.type === 'url') { - expect(item.url).toBeDefined(); - } - if (item.type === 'group') { - expect(item.children).toBeDefined(); - expect(Array.isArray(item.children)).toBe(true); - } - }); - }); - - it('should support navigation item visibility', () => { - const allNavItems = getAllNavItems(appConfig.apps![0].navigation); - - // visible field is optional but should be string or boolean if present - allNavItems.forEach((item: any) => { - if (item.visible !== undefined) { - const validTypes = ['string', 'boolean']; - expect(validTypes).toContain(typeof item.visible); - } - }); - }); - - it('should support extended NavigationItem types (separator, action)', () => { - // These types are valid per the unified NavigationItem model - const validTypes = ['object', 'dashboard', 'page', 'url', 'group', 'report', 'separator', 'action']; - const allNavItems = getAllNavItems(appConfig.apps![0].navigation); - - allNavItems.forEach((item: any) => { - expect(validTypes).toContain(item.type); - }); - }); - - it('should support optional UX enhancement fields on navigation items', () => { - const allNavItems = getAllNavItems(appConfig.apps![0].navigation); - - allNavItems.forEach((item: any) => { - // badge is optional - if (item.badge !== undefined) { - expect(['string', 'number']).toContain(typeof item.badge); - } - // requiredPermissions is optional - if (item.requiredPermissions) { - expect(Array.isArray(item.requiredPermissions)).toBe(true); - } - // order is optional - if (item.order !== undefined) { - expect(typeof item.order).toBe('number'); - } - }); - }); - }); - - describe('NavigationArea Validation', () => { - it('should support areas configuration on apps', () => { - // areas is optional per unified model - appConfig.apps!.forEach((app: any) => { - if (app.areas) { - expect(Array.isArray(app.areas)).toBe(true); - - app.areas.forEach((area: any) => { - expect(area.id).toBeDefined(); - expect(typeof area.id).toBe('string'); - expect(area.label).toBeDefined(); - expect(typeof area.label).toBe('string'); - expect(area.navigation).toBeDefined(); - expect(Array.isArray(area.navigation)).toBe(true); - }); - } - }); - }); - - it('should have valid navigation items inside areas', () => { - appConfig.apps!.forEach((app: any) => { - if (app.areas) { - app.areas.forEach((area: any) => { - const allNavItems = getAllNavItems(area.navigation); - allNavItems.forEach((item: any) => { - expect(item.id).toBeDefined(); - expect(item.label).toBeDefined(); - expect(item.type).toBeDefined(); - }); - }); - } - }); - }); - }); - - describe('ObjectSchema Validation', () => { - it('should have objects defined', () => { - expect(appConfig.objects).toBeDefined(); - expect(Array.isArray(appConfig.objects)).toBe(true); - expect(appConfig.objects!.length).toBeGreaterThan(0); - }); - - it('should have valid object structure', () => { - const object = appConfig.objects![0]; - - // Required fields - expect(object.name).toBeDefined(); - expect(typeof object.name).toBe('string'); - expect(object.label).toBeDefined(); - expect(typeof object.label).toBe('string'); - expect(object.fields).toBeDefined(); - }); - - it('should have valid field definitions', () => { - const object = appConfig.objects![0]; - const fields = Array.isArray(object.fields) - ? object.fields - : Object.values(object.fields); - - expect(fields.length).toBeGreaterThan(0); - - fields.forEach((field: any) => { - // Each field should have a type - expect(field.type).toBeDefined(); - }); - }); - - it('should reference only defined objects in navigation', () => { - const objectNames = new Set(appConfig.objects!.map((o: any) => o.name)); - const navItems = getAllNavItems(appConfig.apps![0].navigation); - const objectNavItems = navItems.filter((item: any) => item.type === 'object'); - - objectNavItems.forEach((item: any) => { - expect(objectNames.has(item.objectName)).toBe(true); - }); - }); - }); - - describe('Manifest Validation', () => { - it('should have manifest defined', () => { - expect(appConfig.manifest).toBeDefined(); - }); - - it('should have valid data seeds', () => { - if (appConfig.manifest?.data) { - expect(Array.isArray(appConfig.manifest.data)).toBe(true); - - appConfig.manifest.data.forEach((seed: any) => { - expect(seed.object).toBeDefined(); - expect(typeof seed.object).toBe('string'); - expect(seed.mode).toBeDefined(); - expect(['upsert', 'insert']).toContain(seed.mode); - expect(seed.records).toBeDefined(); - expect(Array.isArray(seed.records)).toBe(true); - }); - } - }); - - it('should reference only defined objects in manifest', () => { - if (appConfig.manifest?.data) { - const objectNames = new Set(appConfig.objects!.map((o: any) => o.name)); - - appConfig.manifest.data.forEach((seed: any) => { - expect(objectNames.has(seed.object)).toBe(true); - }); - } - }); - }); - - describe('Plugin Configuration', () => { - it('should have plugins defined', () => { - expect(appConfig.plugins).toBeDefined(); - expect(Array.isArray(appConfig.plugins)).toBe(true); - }); - - it('should have datasource configuration', () => { - expect(appConfig.datasources).toBeDefined(); - expect(appConfig.datasources!.default).toBeDefined(); - expect(appConfig.datasources!.default.driver).toBeDefined(); - }); - }); -}); - -/** - * Helper function to recursively get all navigation items including nested ones - */ -function getAllNavItems(navItems: any[]): any[] { - if (!navItems) return []; - - const result: any[] = []; - - for (const item of navItems) { - result.push(item); - - if (item.type === 'group' && item.children) { - result.push(...getAllNavItems(item.children)); - } - } - - return result; -} diff --git a/apps/console/src/__tests__/console-accessibility.test.tsx b/apps/console/src/__tests__/console-accessibility.test.tsx deleted file mode 100644 index 66d08daf9..000000000 --- a/apps/console/src/__tests__/console-accessibility.test.tsx +++ /dev/null @@ -1,350 +0,0 @@ -/** - * ObjectUI - * Copyright (c) 2024-present ObjectStack Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -/** - * Console page-level axe-core accessibility audit. - * - * Tests full Console page assemblies (layout, sidebar, header, key views) - * against WCAG 2.1 AA standards using axe-core. - * Part of P2.3 Accessibility & Inclusive Design roadmap. - */ - -import { describe, it, vi } from 'vitest'; -import { render } from '@testing-library/react'; -import { axe } from 'vitest-axe'; -import React from 'react'; -import { MemoryRouter } from 'react-router-dom'; - -// Mock heavy dependencies to isolate layout accessibility testing -vi.mock('@objectstack/client', () => ({ - ObjectStackClient: class { - connect = vi.fn().mockResolvedValue(true); - }, -})); - -vi.mock('../../objectstack.shared', () => ({ - default: { - apps: [ - { - name: 'sales', - label: 'Sales App', - active: true, - icon: 'briefcase', - navigation: [ - { id: 'nav_opp', label: 'Opportunities', type: 'object', objectName: 'opportunity' }, - { id: 'nav_contact', label: 'Contacts', type: 'object', objectName: 'contact' }, - ], - }, - ], - objects: [ - { name: 'opportunity', label: 'Opportunity', fields: {} }, - { name: 'contact', label: 'Contact', fields: {} }, - ], - }, -})); - -vi.mock('../dataSource', () => { - const MockAdapter = class { - find = vi.fn().mockResolvedValue([]); - findOne = vi.fn(); - create = vi.fn(); - update = vi.fn(); - delete = vi.fn(); - connect = vi.fn().mockResolvedValue(true); - onConnectionStateChange = vi.fn(); - discovery = {}; - }; - return { - ObjectStackAdapter: MockAdapter, - ObjectStackDataSource: MockAdapter, - }; -}); - -// Mock lucide-react icons -vi.mock('lucide-react', async (importOriginal) => { - const actual = await importOriginal(); - const createIcon = (name: string) => (props: any) => - React.createElement('span', { 'data-testid': `icon-${name}`, 'aria-hidden': 'true', ...props }); - return { - ...actual, - Database: createIcon('database'), - LayoutDashboard: createIcon('dashboard'), - Briefcase: createIcon('briefcase'), - ChevronRight: createIcon('chevron-right'), - ChevronsUpDown: createIcon('chevrons-up-down'), - Settings: createIcon('settings'), - LogOut: createIcon('logout'), - Plus: createIcon('plus'), - Menu: createIcon('menu'), - Search: createIcon('search'), - Bell: createIcon('bell'), - User: createIcon('user'), - Users: createIcon('users'), - Sun: createIcon('sun'), - Moon: createIcon('moon'), - Home: createIcon('home'), - X: createIcon('x'), - }; -}); - -async function expectNoViolations(container: HTMLElement) { - const results = await axe(container); - const violations = (results as any).violations || []; - if (violations.length > 0) { - const messages = violations.map( - (v: any) => `[${v.impact}] ${v.id}: ${v.description} (${v.nodes.length} instance(s))` - ); - throw new Error( - `Expected no accessibility violations, but found ${violations.length}:\n${messages.join('\n')}` - ); - } -} - -describe('Console Page-Level Accessibility Audit', () => { - it('main layout structure has proper landmarks', async () => { - const { container } = render( - -
- -
-
-

Sales App

- - -
-
-

Welcome to the Console

-
-
-
-
- ); - await expectNoViolations(container); - }); - - it('sidebar navigation with links has no violations', async () => { - const { container } = render( - - - - ); - await expectNoViolations(container); - }); - - it('header with search and actions has no violations', async () => { - const { container } = render( -
-
-

Sales App

-
- - -
-
- - - -
-
-
- ); - await expectNoViolations(container); - }); - - it('dashboard view with cards and widgets has no violations', async () => { - const { container } = render( -
-

Dashboard

-
-
-
-

Total Revenue

-

$45,231.89

-
-
-

Active Users

-

2,350

-
-
-
-
-

Recent Activity

- - - - - - - - - - - - - - - -
NameStatusDate
Task 1Complete2024-01-15
-
-
- ); - await expectNoViolations(container); - }); - - it('object list view with table has no violations', async () => { - const { container } = render( -
-
-

Opportunities

-
- - - -
-
- - - - - - - - - - - - - - - - - - - - -
NameAmountStage
Acme Corp Deal$50,000Negotiation
Beta Inc Deal$25,000Proposal
- -
- ); - await expectNoViolations(container); - }); - - it('form view with proper labels has no violations', async () => { - const { container } = render( -
-

New Opportunity

-
-
- - -
-
- - -
-
- - -
-
- - -
-
-
- ); - await expectNoViolations(container); - }); - - it('loading state has appropriate ARIA attributes', async () => { - const { container } = render( -
- - ); - await expectNoViolations(container); - }); - - it('error boundary state has proper structure', async () => { - const { container } = render( -
-
-

Something went wrong

-

An unexpected error occurred. Please try refreshing the page.

- -
-
- ); - await expectNoViolations(container); - }); - - it('empty state view has no violations', async () => { - const { container } = render( -
-

Opportunities

-
-

No opportunities found

-

Create your first opportunity to get started.

- -
-
- ); - await expectNoViolations(container); - }); - - it('keyboard shortcuts dialog has no violations', async () => { - const { container } = render( -
-

Keyboard Shortcuts

-
-
Ctrl + K
-
Open command palette
-
Ctrl + N
-
Create new record
-
- -
- ); - await expectNoViolations(container); - }); -}); diff --git a/apps/console/src/__tests__/console-load-performance.test.tsx b/apps/console/src/__tests__/console-load-performance.test.tsx deleted file mode 100644 index b571d1f06..000000000 --- a/apps/console/src/__tests__/console-load-performance.test.tsx +++ /dev/null @@ -1,366 +0,0 @@ -/** - * ObjectUI - * Copyright (c) 2024-present ObjectStack Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -/** - * Console load-performance smoke tests. - * - * Validates code-splitting patterns, render-time budgets, loading-skeleton - * immediacy, and production-bundle hygiene without requiring a real browser. - * Since JSDOM cannot measure real network or bundle sizes we verify the - * structural invariants that guarantee good performance: - * - lazy() / dynamic import() route splitting - * - shallow component-tree depth - * - fast initial render via performance.now() - * - skeleton placeholders before async data - * - MSW gated behind a runtime flag - */ - -import { describe, it, expect } from 'vitest'; -import { render, screen } from '@testing-library/react'; -import '@testing-library/jest-dom'; -import React, { Suspense } from 'react'; -import * as fs from 'node:fs'; -import * as path from 'node:path'; - -// --------------------------------------------------------------------------- -// Performance budget constants (keep in sync with ROADMAP / docs) -// --------------------------------------------------------------------------- - -const PERF = { - /** Max number of JS chunks a production build may emit */ - MAX_CHUNK_COUNT: 22, - /** Main entry gzip budget reference (KB) */ - MAIN_ENTRY_GZIP_KB: 50, - /** Maximum React component nesting depth */ - MAX_TREE_DEPTH: 15, - /** Initial render must complete within this window (ms) */ - RENDER_TIME_BUDGET_MS: 100, -} as const; - -// --------------------------------------------------------------------------- -// 1. Bundle chunk count — verify route-level code-splitting exists -// --------------------------------------------------------------------------- - -describe('Bundle chunk verification', () => { - it('App.tsx declares lazy-loaded route chunks', () => { - const appSource = fs.readFileSync( - path.resolve(__dirname, '..', 'App.tsx'), - 'utf-8', - ); - - const lazyImports = appSource.match(/\blazy\s*\(\s*\(\)\s*=>\s*import\(/g) || []; - - // We expect at least the route-level splits declared in App.tsx - expect(lazyImports.length).toBeGreaterThanOrEqual(5); - expect(lazyImports.length).toBeLessThanOrEqual(PERF.MAX_CHUNK_COUNT); - }); - - it('lazy routes cover all three split categories', () => { - const appSource = fs.readFileSync( - path.resolve(__dirname, '..', 'App.tsx'), - 'utf-8', - ); - - // Route views - expect(appSource).toContain("import('./components/RecordDetailView')"); - expect(appSource).toContain("import('./components/DashboardView')"); - // Auth pages - expect(appSource).toContain("import('./pages/LoginPage')"); - // System admin pages - expect(appSource).toContain("import('./pages/system/UserManagementPage')"); - }); -}); - -// --------------------------------------------------------------------------- -// 2. Main entry size budget reference -// --------------------------------------------------------------------------- - -describe('Entry-size budget constants', () => { - it('gzip budget reference is below 50 KB', () => { - expect(PERF.MAIN_ENTRY_GZIP_KB).toBeLessThanOrEqual(50); - }); - - it('eagerly loaded imports in App.tsx are limited', () => { - const appSource = fs.readFileSync( - path.resolve(__dirname, '..', 'App.tsx'), - 'utf-8', - ); - - // Count non-lazy static imports (lines starting with "import " that are - // NOT type-only imports and come before the first lazy() declaration) - const beforeLazy = appSource.split(/\blazy\s*\(/)[0]; - const staticImports = - beforeLazy.match(/^import\s+(?!type\b)/gm) || []; - - // Eagerly loaded modules should stay small; flag if this grows too large - expect(staticImports.length).toBeLessThanOrEqual(21); - }); -}); - -// --------------------------------------------------------------------------- -// 3. Component tree depth verification -// --------------------------------------------------------------------------- - -describe('Component tree depth', () => { - function NestedTree({ depth }: { depth: number }): React.ReactElement { - if (depth <= 0) return leaf; - return ( -
- -
- ); - } - - it(`renders a tree of depth ${PERF.MAX_TREE_DEPTH} without issue`, () => { - const { container } = render(); - expect(screen.getByTestId('leaf')).toBeInTheDocument(); - - // Measure actual DOM nesting depth - let node: Element | null = container.querySelector('[data-testid="leaf"]'); - let levels = 0; - while (node && node !== container) { - node = node.parentElement; - levels++; - } - expect(levels).toBeLessThanOrEqual(PERF.MAX_TREE_DEPTH + 2); // +2 for root wrappers - }); - - it('typical console layout nests fewer than MAX_TREE_DEPTH levels', () => { - const { container } = render( -
-
- -
-
-

Title

-
-
-
-
Card
-
-
-
-
-
, - ); - - // Walk from deepest text node up to the shell - const deepest = container.querySelector('.grid div')!; - let el: Element | null = deepest; - let depth = 0; - while (el && el !== container) { - el = el.parentElement; - depth++; - } - expect(depth).toBeLessThan(PERF.MAX_TREE_DEPTH); - }); -}); - -// --------------------------------------------------------------------------- -// 4. Render-time verification (performance.now() timing) -// --------------------------------------------------------------------------- - -describe('Render-time budget', () => { - it(`core layout renders in under ${PERF.RENDER_TIME_BUDGET_MS}ms`, () => { - const start = performance.now(); - - render( -
- -
-
-

Console

-
-
-
- {Array.from({ length: 9 }, (_, i) => ( -
Card {i}
- ))} -
-
-
-
, - ); - - const elapsed = performance.now() - start; - expect(elapsed).toBeLessThan(PERF.RENDER_TIME_BUDGET_MS); - }); - - it('renders a 100-row data table within budget', () => { - const rows = Array.from({ length: 100 }, (_, i) => ({ - id: i, - name: `Row ${i}`, - status: i % 2 === 0 ? 'active' : 'inactive', - })); - - const start = performance.now(); - render( - - - - - - {rows.map((r) => ( - - ))} - -
IDNameStatus
{r.id}{r.name}{r.status}
, - ); - const elapsed = performance.now() - start; - - expect(elapsed).toBeLessThan(PERF.RENDER_TIME_BUDGET_MS); - }); -}); - -// --------------------------------------------------------------------------- -// 5. Loading skeleton renders immediately (before async data) -// --------------------------------------------------------------------------- - -describe('Loading skeleton immediacy', () => { - it('Suspense fallback renders before lazy component resolves', () => { - // Simulate a lazy component that never resolves during the test - const NeverResolves = React.lazy( - () => new Promise<{ default: React.ComponentType }>(() => {}), - ); - - render( - Loading...
}> - - , - ); - - // The skeleton must appear synchronously - expect(screen.getByTestId('skeleton')).toBeInTheDocument(); - expect(screen.getByTestId('skeleton')).toHaveTextContent('Loading...'); - }); - - it('loading placeholder renders while data is pending', () => { - function DataView({ data }: { data: string[] | null }) { - if (!data) { - return ( -
-
-
-
- ); - } - return ( -
    - {data.map((d, i) =>
  • {d}
  • )} -
- ); - } - - // Render with null data → skeleton should be visible - const { rerender } = render(); - expect(screen.getByTestId('loading-state')).toBeInTheDocument(); - expect(screen.queryByTestId('data-list')).not.toBeInTheDocument(); - - // Provide data → skeleton disappears - rerender(); - expect(screen.queryByTestId('loading-state')).not.toBeInTheDocument(); - expect(screen.getByTestId('data-list')).toBeInTheDocument(); - }); -}); - -// --------------------------------------------------------------------------- -// 6. Lazy-loaded routes use dynamic import() -// --------------------------------------------------------------------------- - -describe('Lazy-loaded routes use dynamic import()', () => { - it('every lazy() call wraps a dynamic import()', () => { - const appSource = fs.readFileSync( - path.resolve(__dirname, '..', 'App.tsx'), - 'utf-8', - ); - - // Extract all lazy() declarations - const lazyDeclarations = appSource.match(/const \w+ = lazy\(.+?\);/g) || []; - expect(lazyDeclarations.length).toBeGreaterThan(0); - - for (const decl of lazyDeclarations) { - // Each must contain a dynamic import() call - expect(decl).toMatch(/import\(/); - } - }); - - it('no route component is eagerly imported AND lazy-loaded', () => { - const appSource = fs.readFileSync( - path.resolve(__dirname, '..', 'App.tsx'), - 'utf-8', - ); - - // Collect names of lazy-loaded components - const lazyNames = - (appSource.match(/const (\w+) = lazy\(/g) || []).map((m) => - m.replace(/^const /, '').replace(/ = lazy\($/, ''), - ); - - // Collect eagerly imported identifiers (non-type, non-lazy) - const eagerImports = appSource.match(/^import \{[^}]+\} from/gm) || []; - const eagerNames = eagerImports.flatMap((line) => { - const match = line.match(/\{([^}]+)\}/); - return match ? match[1].split(',').map((n) => n.trim()) : []; - }); - - // No component should appear in both lists - for (const name of lazyNames) { - expect(eagerNames).not.toContain(name); - } - }); -}); - -// --------------------------------------------------------------------------- -// 7. MSW is not bundled in production mode -// --------------------------------------------------------------------------- - -describe('MSW production-bundle hygiene', () => { - it('MSW import is gated behind a runtime env flag', () => { - const mainSource = fs.readFileSync( - path.resolve(__dirname, '..', 'main.tsx'), - 'utf-8', - ); - - // MSW should only be imported dynamically, never at the top level - const topLevelMswImport = mainSource.match(/^import .+msw/m); - expect(topLevelMswImport).toBeNull(); - - // The dynamic import must be guarded by an env check - expect(mainSource).toMatch(/import\.meta\.env\./); - expect(mainSource).toMatch(/import\(.+mocks/); - }); - - it('no static MSW import exists in main.tsx', () => { - const mainSource = fs.readFileSync( - path.resolve(__dirname, '..', 'main.tsx'), - 'utf-8', - ); - - // Ensure no top-level import from 'msw' or './mocks' - const staticMswLines = mainSource - .split('\n') - .filter((line) => /^import\s/.test(line)) - .filter((line) => /msw|mocks/.test(line)); - - expect(staticMswLines).toHaveLength(0); - }); -}); diff --git a/packages/components/src/__tests__/Registry.test.ts b/packages/components/src/__tests__/Registry.test.ts deleted file mode 100644 index a1a12e594..000000000 --- a/packages/components/src/__tests__/Registry.test.ts +++ /dev/null @@ -1,21 +0,0 @@ - -import { describe, it, expect } from 'vitest'; -import { ComponentRegistry } from '@object-ui/core'; -import '../renderers'; // Ensure side-effects are run - -describe('Component Registry', () => { - it('should register standard page types', () => { - expect(ComponentRegistry.get('page')).toBeDefined(); - expect(ComponentRegistry.get('app')).toBeDefined(); - expect(ComponentRegistry.get('utility')).toBeDefined(); - expect(ComponentRegistry.get('home')).toBeDefined(); - expect(ComponentRegistry.get('record')).toBeDefined(); - }); - - it('should register standard layout components', () => { - expect(ComponentRegistry.get('container')).toBeDefined(); - expect(ComponentRegistry.get('grid')).toBeDefined(); - expect(ComponentRegistry.get('stack')).toBeDefined(); - expect(ComponentRegistry.get('card')).toBeDefined(); - }); -}); diff --git a/packages/plugin-charts/src/registration.test.tsx b/packages/plugin-charts/src/registration.test.tsx deleted file mode 100644 index a63b0bffa..000000000 --- a/packages/plugin-charts/src/registration.test.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; -import { describe, it, expect, vi, beforeAll } from 'vitest'; -import { ComponentRegistry } from '@object-ui/core'; - -describe('Plugin Charts Registration', () => { - beforeAll(async () => { - await import('./index'); - }, 30000); - - it('registers bar-chart component', () => { - const config = ComponentRegistry.get('bar-chart'); - expect(config).toBeDefined(); - }); - - it('registers chart component types', () => { - expect(ComponentRegistry.get('chart')).toBeDefined(); // Assuming base - // Verify aliases if they exist - expect(ComponentRegistry.get('pie-chart')).toBeDefined(); - expect(ComponentRegistry.get('donut-chart')).toBeDefined(); - }); -}); diff --git a/packages/plugin-detail/src/registration.test.tsx b/packages/plugin-detail/src/registration.test.tsx deleted file mode 100644 index 30ec05b69..000000000 --- a/packages/plugin-detail/src/registration.test.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { describe, it, expect, beforeAll } from 'vitest'; -import { ComponentRegistry } from '@object-ui/core'; - -describe('Plugin Detail Registration', () => { - beforeAll(async () => { - await import('./index'); - }, 15000); // Increase timeout to 15 seconds for async import - - it('registers detail-view component', () => { - // We must use getConfig to retrieve the metadata (label, category, etc.) - // .get() only returns the React Component. - const config = ComponentRegistry.getConfig('detail-view'); - - expect(config).toBeDefined(); - expect(config?.label).toBe('Detail View'); - expect(config?.category).toBe('Views'); - }); -}); diff --git a/packages/plugin-list/src/registration.test.tsx b/packages/plugin-list/src/registration.test.tsx deleted file mode 100644 index dcd5567a4..000000000 --- a/packages/plugin-list/src/registration.test.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { ListView } from './index'; - -describe('Plugin List Registration', () => { - it('exports ListView component', () => { - expect(ListView).toBeDefined(); - }); -}); diff --git a/packages/plugin-view/src/__tests__/registration.test.tsx b/packages/plugin-view/src/__tests__/registration.test.tsx deleted file mode 100644 index 2620689f2..000000000 --- a/packages/plugin-view/src/__tests__/registration.test.tsx +++ /dev/null @@ -1,32 +0,0 @@ -/** - * ObjectUI - * Copyright (c) 2024-present ObjectStack Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import { describe, it, expect } from 'vitest'; -import { ObjectView, ViewSwitcher, FilterUI, SortUI } from '../index'; - -describe('Plugin View Registration', () => { - it('exports ObjectView component', () => { - expect(ObjectView).toBeDefined(); - expect(typeof ObjectView).toBe('function'); - }); - - it('exports ViewSwitcher component', () => { - expect(ViewSwitcher).toBeDefined(); - expect(typeof ViewSwitcher).toBe('function'); - }); - - it('exports FilterUI component', () => { - expect(FilterUI).toBeDefined(); - expect(typeof FilterUI).toBe('function'); - }); - - it('exports SortUI component', () => { - expect(SortUI).toBeDefined(); - expect(typeof SortUI).toBe('function'); - }); -});