-
Notifications
You must be signed in to change notification settings - Fork 2
Add black-box integration tests for core ObjectUI components #363
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -217,6 +217,79 @@ describe('Console Application Simulation', () => { | |||||||||||||||||||||||||||||||||||||||||||||
| expect(screen.getAllByText('5000').length).toBeGreaterThan(0); | ||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // ----------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||
| // SIMPLIFIED FORM INTEGRATION TESTS | ||||||||||||||||||||||||||||||||||||||||||||||
| // ----------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||
| it('Form Scenario B: Metadata-Driven Form Generation', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||
| vi.spyOn(mocks.MockDataSource.prototype, 'getObjectSchema').mockResolvedValue({ | ||||||||||||||||||||||||||||||||||||||||||||||
| name: 'kitchen_sink', | ||||||||||||||||||||||||||||||||||||||||||||||
| fields: { | ||||||||||||||||||||||||||||||||||||||||||||||
| name: { type: 'text', label: 'Name Field' }, | ||||||||||||||||||||||||||||||||||||||||||||||
| amount: { type: 'number', label: 'Amount Field' } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+224
to
+230
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| renderApp('/kitchen_sink'); | ||||||||||||||||||||||||||||||||||||||||||||||
| await waitFor(() => { | ||||||||||||||||||||||||||||||||||||||||||||||
| expect(screen.getByRole('heading', { name: /Kitchen Sink/i })).toBeInTheDocument(); | ||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Verify the form can be opened (showing metadata was loaded) | ||||||||||||||||||||||||||||||||||||||||||||||
| const newButton = screen.getByRole('button', { name: /New Kitchen Sink/i }); | ||||||||||||||||||||||||||||||||||||||||||||||
| fireEvent.click(newButton); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Verify form loaded with schema-based fields | ||||||||||||||||||||||||||||||||||||||||||||||
| await waitFor(() => { | ||||||||||||||||||||||||||||||||||||||||||||||
| expect(screen.getByRole('dialog')).toBeInTheDocument(); | ||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Form should render based on mocked schema | ||||||||||||||||||||||||||||||||||||||||||||||
| // The actual field labels might differ based on implementation | ||||||||||||||||||||||||||||||||||||||||||||||
| // but the form should render without errors | ||||||||||||||||||||||||||||||||||||||||||||||
| expect(screen.queryByText(/error/i)).not.toBeInTheDocument(); | ||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+223
to
+250
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // ----------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||
| // SIMPLIFIED GRID INTEGRATION TESTS | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+220
to
+253
|
||||||||||||||||||||||||||||||||||||||||||||||
| // ----------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||
| it('Grid Scenario A: Grid Rendering and Actions', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+223
to
+255
|
||||||||||||||||||||||||||||||||||||||||||||||
| renderApp('/kitchen_sink'); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| await waitFor(() => { | ||||||||||||||||||||||||||||||||||||||||||||||
| expect(screen.getByRole('heading', { name: /Kitchen Sink/i })).toBeInTheDocument(); | ||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const newButton = screen.getByRole('button', { name: /New Kitchen Sink/i }); | ||||||||||||||||||||||||||||||||||||||||||||||
| expect(newButton).toBeInTheDocument(); | ||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+255
to
+264
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| it('Grid Scenario B: Grid Data Loading', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||
| const seedData = [ | ||||||||||||||||||||||||||||||||||||||||||||||
| { id: '1', name: 'Item 1', amount: 100 }, | ||||||||||||||||||||||||||||||||||||||||||||||
| { id: '2', name: 'Item 2', amount: 200 } | ||||||||||||||||||||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const findSpy = vi.spyOn(mocks.MockDataSource.prototype, 'find') | ||||||||||||||||||||||||||||||||||||||||||||||
| .mockResolvedValue({ data: seedData }); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| renderApp('/kitchen_sink'); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| await waitFor(() => { | ||||||||||||||||||||||||||||||||||||||||||||||
| expect(screen.getByRole('heading', { name: /Kitchen Sink/i })).toBeInTheDocument(); | ||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Verify data source was called to load grid data | ||||||||||||||||||||||||||||||||||||||||||||||
| await waitFor(() => { | ||||||||||||||||||||||||||||||||||||||||||||||
| expect(findSpy).toHaveBeenCalledWith('kitchen_sink', expect.any(Object)); | ||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Verify grid displays the loaded data | ||||||||||||||||||||||||||||||||||||||||||||||
| await waitFor(() => { | ||||||||||||||||||||||||||||||||||||||||||||||
| expect(screen.getByText('Item 1')).toBeInTheDocument(); | ||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||
| expect(screen.getByText('Item 2')).toBeInTheDocument(); | ||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+220
to
+291
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // ----------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -664,3 +737,87 @@ describe('Kanban Integration', () => { | |||||||||||||||||||||||||||||||||||||||||||||
| expect(findSpy).toHaveBeenCalledWith('project_task', expect.any(Object)); | ||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // ----------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||
| // FIELDS INTEGRATION TESTS | ||||||||||||||||||||||||||||||||||||||||||||||
| // ----------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||
| describe('Fields Integration', () => { | ||||||||||||||||||||||||||||||||||||||||||||||
| it('Scenario A: Field Type Mapping', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||
| const { mapFieldTypeToFormType } = await import('@object-ui/fields'); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| expect(mapFieldTypeToFormType('text')).toBe('field:text'); | ||||||||||||||||||||||||||||||||||||||||||||||
| expect(mapFieldTypeToFormType('email')).toBe('field:email'); | ||||||||||||||||||||||||||||||||||||||||||||||
| expect(mapFieldTypeToFormType('number')).toBe('field:number'); | ||||||||||||||||||||||||||||||||||||||||||||||
| expect(mapFieldTypeToFormType('boolean')).toBe('field:boolean'); | ||||||||||||||||||||||||||||||||||||||||||||||
| expect(mapFieldTypeToFormType('select')).toBe('field:select'); | ||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+746
to
+754
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| it('Scenario A.2: Unknown Field Type Fallback in Form', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||
| const { mapFieldTypeToFormType } = await import('@object-ui/fields'); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Verify unknown types fallback to text | ||||||||||||||||||||||||||||||||||||||||||||||
| expect(mapFieldTypeToFormType('unknown_type')).toBe('field:text'); | ||||||||||||||||||||||||||||||||||||||||||||||
| expect(mapFieldTypeToFormType('custom_widget')).toBe('field:text'); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // This ensures forms don't break when encountering unknown field types | ||||||||||||||||||||||||||||||||||||||||||||||
| // The actual rendering is tested via the full form integration tests | ||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+756
to
+765
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| it('Scenario B: Field Formatting Utilities', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||
| const { formatCurrency, formatDate, formatPercent } = await import('@object-ui/fields'); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const formatted = formatCurrency(1234.56); | ||||||||||||||||||||||||||||||||||||||||||||||
| expect(formatted).toContain('1,234.56'); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const dateStr = formatDate(new Date('2024-01-15')); | ||||||||||||||||||||||||||||||||||||||||||||||
| expect(dateStr).toContain('2024'); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const percent = formatPercent(0.1234); | ||||||||||||||||||||||||||||||||||||||||||||||
| expect(percent).toBe('12.34%'); | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+771
to
+777
|
||||||||||||||||||||||||||||||||||||||||||||||
| expect(formatted).toContain('1,234.56'); | |
| const dateStr = formatDate(new Date('2024-01-15')); | |
| expect(dateStr).toContain('2024'); | |
| const percent = formatPercent(0.1234); | |
| expect(percent).toBe('12.34%'); | |
| expect(typeof formatted).toBe('string'); | |
| // Strip all non-digit characters and verify the numeric content is correct, | |
| // regardless of locale-specific separators or currency symbols. | |
| expect(formatted.replace(/[^\d]/g, '')).toBe('123456'); | |
| const dateStr = formatDate(new Date('2024-01-15T00:00:00Z')); | |
| expect(typeof dateStr).toBe('string'); | |
| // Ensure a 4-digit year is present without assuming a specific locale format. | |
| expect(dateStr).toMatch(/\d{4}/); | |
| const percent = formatPercent(0.1234); | |
| expect(typeof percent).toBe('string'); | |
| // Accept both "12.34%" and "12,34 %" style outputs (different locales may use | |
| // different decimal separators and optional spaces before the percent sign). | |
| expect(percent).toMatch(/^12([.,]\d+)? ?%$/); |
Copilot
AI
Feb 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Fields Integration tests use dynamic imports (await import) for testing pure utility functions like mapFieldTypeToFormType and formatCurrency. This is inconsistent with how these functions should be tested - they're pure functions with no side effects and should be imported statically at the top of the file like other test dependencies. The dynamic imports add unnecessary complexity and async overhead for synchronous functions. Reference: lines 2-5 show static imports for testing utilities.
Copilot
AI
Feb 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The renderApp helper function is defined locally within the Dashboard Integration test suite, but the same function already exists as a helper in the parent "Console Application Simulation" test suite (lines 99-105). This creates unnecessary duplication and potential confusion. Consider removing this duplicate definition and using the parent suite's helper, or if this suite needs to be independent, ensure it's clearly scoped.
| const renderApp = (initialRoute: string) => { | |
| return render( | |
| <MemoryRouter initialEntries={[initialRoute]}> | |
| <AppContent /> | |
| </MemoryRouter> | |
| ); | |
| }; |
Copilot
AI
Feb 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Dashboard Integration tests duplicate existing functionality from the "Console Application Simulation" test suite. Specifically, "Scenario 2: Dashboard Rendering (Sales Dashboard)" (lines 117-132) already tests dashboard page rendering, and "Scenario 5: Report Rendering (Report Page)" (lines 199-218) already tests report page rendering. The new tests (lines 793-812) are essentially duplicates with slightly different assertions. This violates DRY principles and creates maintenance overhead.
| it('Scenario A: Dashboard Page Rendering', async () => { | |
| renderApp('/dashboard/sales_dashboard'); | |
| await waitFor(() => { | |
| expect(screen.getByText(/Sales Overview/i)).toBeInTheDocument(); | |
| }); | |
| expect(screen.getByText(/Sales by Region/i)).toBeInTheDocument(); | |
| }); | |
| it('Scenario B: Report Page Rendering', async () => { | |
| renderApp('/page/report_page'); | |
| await waitFor(() => { | |
| expect(screen.getByText(/Sales Performance Report/i)).toBeInTheDocument(); | |
| }); | |
| expect(screen.getByText('Region')).toBeInTheDocument(); | |
| expect(screen.getByText('North')).toBeInTheDocument(); | |
| }); |
Copilot
AI
Feb 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Component Registry Check test uses dynamic imports but doesn't follow the established pattern for integration tests. According to the PR description's test philosophy, integration tests should verify "Black Box" behavior - how components render from JSON schemas and interact with data. Testing the ComponentRegistry directly is a unit test concern, not an integration test. This test belongs in a separate unit test file for the @object-ui/core package, not in the integration test suite.
| it('Scenario C: Component Registry Check', async () => { | |
| const { ComponentRegistry } = await import('@object-ui/core'); | |
| const dashboardRenderer = ComponentRegistry.get('dashboard'); | |
| expect(dashboardRenderer).toBeDefined(); | |
| const reportRenderer = ComponentRegistry.get('report'); | |
| expect(reportRenderer).toBeDefined(); | |
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Form and Grid integration test sections lack the header comment block that documents the test scenarios covered, unlike the Kanban Integration section (lines 295-303). For consistency with the established pattern, these sections should have similar header comments describing what scenarios they cover. This improves code documentation and makes the test structure clearer.