# refactor: restructure .cursor directory for improved organization and clarity ## Description This PR refactors the `.cursor` directory to enhance organization, clarity, and maintainability. ### Problem The existing `.cursor` directory lacked clear organization, making it difficult to find specific files, understand their purpose, and add new components consistently. ### Solution A comprehensive restructuring: #### New Directory Structure ``` .cursor/ ├── settings.json # Main configuration file ├── docs/ # Documentation │ ├── guides/ # In-depth guides │ ├── references/ # Quick references │ └── practices/ # Best practices ├── rules/ # Rule definitions │ ├── commit/ # Commit-related rules │ ├── quality/ # Code quality rules │ ├── testing/ # Testing rules │ └── verification/ # Verification rules └── hooks/ # Git hooks and scripts ``` #### Key Changes 1. **Logical Categorization**: Organized files into clear categories based on purpose 2. **Improved Documentation**: Added comprehensive README files for each directory 3. **Standardized Naming**: Implemented consistent kebab-case naming convention 4. **Reference Updates**: Updated all internal references to point to new file locations ### Benefits - **Easier Navigation**: Clear categorization makes finding files intuitive - **Improved Understanding**: Comprehensive documentation explains purpose and usage - **Simplified Maintenance**: Logical structure makes updates and additions easier - **Better Onboarding**: New team members can quickly understand the system This refactoring sets a solid foundation for all Cursor AI-related configurations and rules, making it easier for the team to leverage Cursor's capabilities.
13 KiB
Appsmith Testing Guide
This guide outlines best practices for writing tests for the Appsmith codebase.
Frontend Testing
Unit Tests with Jest
Appsmith uses Jest for frontend unit tests. Unit tests should be written for individual components, utility functions, and Redux slices.
Test File Structure
Create test files with the .test.ts or .test.tsx extension in the same directory as the source file:
src/
components/
Button/
Button.tsx
Button.test.tsx
utils/
helpers.ts
helpers.test.ts
Writing React Component Tests
import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import Button from "./Button";
describe("Button component", () => {
it("renders correctly with default props", () => {
render(<Button>Click me</Button>);
expect(screen.getByText("Click me")).toBeInTheDocument();
});
it("calls onClick handler when clicked", () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click me</Button>);
fireEvent.click(screen.getByText("Click me"));
expect(handleClick).toHaveBeenCalledTimes(1);
});
});
Redux Testing
import { configureStore } from "@reduxjs/toolkit";
import reducer, {
setUserInfo,
fetchUserInfo
} from "./userSlice";
describe("User reducer", () => {
it("should handle initial state", () => {
expect(reducer(undefined, { type: "unknown" })).toEqual({
userInfo: null,
isLoading: false,
error: null
});
});
it("should handle setUserInfo", () => {
const userInfo = { name: "Test User", email: "test@example.com" };
expect(
reducer(
{ userInfo: null, isLoading: false, error: null },
setUserInfo(userInfo)
)
).toEqual({
userInfo,
isLoading: false,
error: null
});
});
});
Testing Redux/React Safety Patterns
Safety when accessing deeply nested properties in Redux state is critical for application reliability. Here are patterns for testing these safety mechanisms:
Testing Redux Selectors with Incomplete State
import { configureStore } from '@reduxjs/toolkit';
import reducer, { selectNestedData } from './dataSlice';
import { renderHook } from '@testing-library/react-hooks';
import { Provider } from 'react-redux';
import { useSelector } from 'react-redux';
describe("selectNestedData", () => {
it("returns default value when state is incomplete", () => {
// Set up store with incomplete state
const store = configureStore({
reducer: {
data: reducer,
},
preloadedState: {
data: {
// Missing expected nested properties
},
},
});
// Wrap the hook with the Redux provider
const wrapper = ({ children }) => (
<Provider store={store}>{children}</Provider>
);
// Render the hook with the selector
const { result } = renderHook(() => useSelector(selectNestedData), { wrapper });
// Verify the selector returns the fallback/default value
expect(result.current).toEqual(/* expected default value */);
});
it("returns actual data when state is complete", () => {
// Set up store with complete state
const expectedData = { value: "test" };
const store = configureStore({
reducer: {
data: reducer,
},
preloadedState: {
data: {
entities: {
items: {
123: {
details: expectedData,
},
},
},
},
},
});
const wrapper = ({ children }) => (
<Provider store={store}>{children}</Provider>
);
const { result } = renderHook(() => useSelector(state =>
selectNestedData(state, '123')
), { wrapper });
// Verify the selector returns the actual data
expect(result.current).toEqual(expectedData);
});
});
Testing Components with Error Boundaries
import React from 'react';
import { render, screen } from '@testing-library/react';
import { ErrorBoundary } from 'react-error-boundary';
import ComponentWithDeepAccess from './ComponentWithDeepAccess';
describe('ComponentWithDeepAccess with error boundary', () => {
it('renders fallback UI when data is invalid', () => {
// Define invalid data that would cause property access errors
const invalidData = {
// Missing required nested structure
};
const FallbackComponent = () => <div>Error occurred</div>;
render(
<ErrorBoundary FallbackComponent={FallbackComponent}>
<ComponentWithDeepAccess data={invalidData} />
</ErrorBoundary>
);
// Verify the fallback component is rendered
expect(screen.getByText('Error occurred')).toBeInTheDocument();
});
it('renders normally with valid data', () => {
// Define valid data with complete structure
const validData = {
user: {
profile: {
name: 'Test User'
}
}
};
const FallbackComponent = () => <div>Error occurred</div>;
render(
<ErrorBoundary FallbackComponent={FallbackComponent}>
<ComponentWithDeepAccess data={validData} />
</ErrorBoundary>
);
// Verify the component renders normally
expect(screen.getByText('Test User')).toBeInTheDocument();
});
});
Testing Safe Property Access Utilities
import { safeGet } from './propertyAccessUtils';
describe('safeGet utility', () => {
it('returns the value when the path exists', () => {
const obj = {
a: {
b: {
c: 'value'
}
}
};
expect(safeGet(obj, 'a.b.c')).toBe('value');
});
it('returns default value when path does not exist', () => {
const obj = {
a: {}
};
expect(safeGet(obj, 'a.b.c', 'default')).toBe('default');
});
it('handles array indices in path', () => {
const obj = {
users: [
{ id: 1, name: 'User 1' },
{ id: 2, name: 'User 2' }
]
};
expect(safeGet(obj, 'users.1.name')).toBe('User 2');
});
it('handles null and undefined input', () => {
expect(safeGet(null, 'a.b.c', 'default')).toBe('default');
expect(safeGet(undefined, 'a.b.c', 'default')).toBe('default');
});
});
Integration Tests with Cypress
Cypress is used for integration and end-to-end testing. These tests should verify the functionality of the application from a user's perspective.
Test File Structure
cypress/
integration/
Editor/
Canvas.spec.ts
PropertyPane.spec.ts
Workspace/
Applications.spec.ts
Writing Cypress Tests
describe("Application Canvas", () => {
before(() => {
cy.visit("/applications/my-app/pages/page-1/edit");
});
it("should allow adding a widget to the canvas", () => {
cy.get("[data-cy=entity-explorer]").should("be.visible");
cy.get("[data-cy=widget-button]").drag("[data-cy=canvas-drop-zone]");
cy.get("[data-cy=widget-card-button]").should("exist");
});
it("should open property pane when widget is selected", () => {
cy.get("[data-cy=widget-card-button]").click();
cy.get("[data-cy=property-pane]").should("be.visible");
cy.get("[data-cy=property-pane-title]").should("contain", "Button");
});
});
Backend Testing
Unit Tests with JUnit
Backend unit tests should validate individual components and services.
Test File Structure
src/test/java/com/appsmith/server/
services/
ApplicationServiceTest.java
UserServiceTest.java
controllers/
ApplicationControllerTest.java
Writing Java Unit Tests
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationServiceTest {
@Autowired
private ApplicationService applicationService;
@MockBean
private WorkspaceService workspaceService;
@Test
public void testCreateApplication() {
// Arrange
Application application = new Application();
application.setName("Test Application");
Workspace workspace = new Workspace();
workspace.setId("workspace-id");
Mono<Workspace> workspaceMono = Mono.just(workspace);
when(workspaceService.findById(any())).thenReturn(workspaceMono);
// Act
Mono<Application> result = applicationService.createApplication(application, "workspace-id");
// Assert
StepVerifier.create(result)
.assertNext(app -> {
assertThat(app.getId()).isNotNull();
assertThat(app.getName()).isEqualTo("Test Application");
assertThat(app.getWorkspaceId()).isEqualTo("workspace-id");
})
.verifyComplete();
}
}
Integration Tests
Backend integration tests should verify interactions between different components of the system.
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ApplicationControllerIntegrationTest {
@Autowired
private WebTestClient webTestClient;
@Autowired
private ApplicationRepository applicationRepository;
@Before
public void setUp() {
applicationRepository.deleteAll().block();
}
@Test
public void testGetAllApplications() {
// Test implementation
}
}
Best Practices
General Test Guidelines
- Test Isolation: Each test should be independent of others.
- Test Coverage: Aim for 80%+ coverage for critical code paths.
- Avoid Implementation Details: Test behavior, not implementation.
- Concise Tests: Keep tests focused on one behavior or functionality.
- Descriptive Names: Use clear test names that describe what is being tested.
Redux/React Safety Best Practices
- Always Check Property Existence: Test edge cases where properties might not exist.
- Use Defensive Programming: Design components and selectors to handle incomplete data gracefully.
- Test Error Boundaries: Verify that error boundaries correctly catch and handle errors from property access.
- Test Default Values: Ensure selectors return appropriate defaults when data is missing.
- Test Different State Permutations: Create tests with various combinations of missing or incomplete state to ensure robustness.
Performance Considerations
- Mock Heavy Dependencies: Use mocks for API calls, databases, etc.
- Optimize Test Speed: Keep tests fast to encourage frequent testing.
- Use Focused Tests: Test only what needs to be tested.
Troubleshooting Tests
Common Issues
-
Flaky Tests: Tests that sometimes pass and sometimes fail.
- Solution: Make tests more deterministic, avoid race conditions.
-
Memory Leaks: Tests that consume increasing memory.
- Solution: Clean up resources, avoid global state.
-
Slow Tests: Tests that take too long to run.
- Solution: Mock heavy dependencies, parallelize when possible.
React-Specific Issues
-
Component State Issues: Components not updating as expected.
- Solution: Use
act()for state updates, wait for async operations.
- Solution: Use
-
Redux State Access Errors: Errors when accessing nested properties.
- Solution: Use optional chaining, lodash/get, or default values in selectors.
-
Rendering Errors: Components not rendering as expected.
- Solution: Verify props, check for conditionals that might prevent rendering.
Advanced Testing Techniques
Property-Based Testing
Test with a wide range of automatically generated inputs to find edge cases.
Snapshot Testing
Useful for detecting unintended changes in UI components.
Visual Regression Testing
Compare screenshots of components to detect visual changes.
Load and Performance Testing
Test system behavior under high load or stress conditions.
A/B Testing
Compare different implementations to determine which performs better.
Test Data Best Practices
Creating Test Fixtures
- Create reusable fixtures for common test data
- Use descriptive names for test fixtures
- Keep test data minimal but sufficient
Mocking External Services
- Mock external API calls and dependencies
- Use realistic mock responses
- Consider edge cases and error conditions
Testing Standards
Frontend Testing Standards
- Aim for 80%+ test coverage for utility functions
- Test all Redux slices thoroughly
- Focus on critical user journeys in integration tests
- Test responsive behavior for key components
- Include accessibility tests for UI components
Backend Testing Standards
- Test all public service methods
- Test both successful and error cases
- Test database interactions with real repositories
- Test API endpoints with WebTestClient
- Mock external services to isolate tests
Running Tests
Frontend Tests
# Run all Jest tests
cd app/client
yarn run test:unit
# Run a specific test file
yarn jest src/path/to/test.ts
# Run Cypress tests
npx cypress run
Backend Tests
# Run all backend tests
cd app/server
./mvnw test
# Run a specific test class
./mvnw test -Dtest=ApplicationServiceTest
Best Practices for Test-Driven Development
- Write failing tests first
- Start with simple test cases
- Refactor after tests pass
- Use descriptive test names
- Keep tests independent
- Avoid test interdependence
- Test edge cases and error conditions
- Keep tests fast
- Avoid testing implementation details
- Review and update tests when requirements change