npm install
npm run dev- Write TypeScript with strict mode enabled
- Follow the existing code style (use ESLint)
- All components must be exported with full JSDoc comments
- Test all public APIs
- Fork the repository
- Create a feature branch:
git checkout -b feat/your-feature - Commit changes:
git commit -m "feat: description" - Push to fork:
git push origin feat/your-feature - Open a Pull Request
Run the full test suite:
npm run testRun tests in watch mode:
npm run test:watchRun tests with coverage:
npm run test:coverage- Test files live alongside source files:
src/components/MyComponent.test.ts - Use
.test.tsor.test.tsxextension - Use Vitest for unit tests and Vitest's mocking utilities
- Use
@testing-library/reactfor component tests
Most components require SorokitProvider context. Use the renderWithProvider helper:
import { render } from '@testing-library/react';
import { renderWithProvider } from './test-utils';
import { MyComponent } from './MyComponent';
describe('MyComponent', () => {
it('should render with provider', () => {
const { getByText } = renderWithProvider(<MyComponent />);
expect(getByText('content')).toBeInTheDocument();
});
});Mock the Sorokit client for isolated component tests:
import { vi } from 'vitest';
import { getClient } from '../lib/client';
vi.mock('../lib/client', () => ({
getClient: vi.fn(() => ({
connect: vi.fn().mockResolvedValue('G...'),
invokeContract: vi.fn().mockResolvedValue({ data: null }),
getEvents: vi.fn().mockResolvedValue([]),
})),
}));
describe('MyComponent', () => {
it('should call client on mount', async () => {
renderWithProvider(<MyComponent />);
await waitFor(() => {
expect(getClient).toHaveBeenCalled();
});
});
});For full integration tests, use renderWithProvider:
import { renderWithProvider } from './test-utils';
const mockClient = {
connect: vi.fn().mockResolvedValue('GBRP...'),
invokeContract: vi.fn().mockResolvedValue({ data: { value: '123' } }),
};
describe('MyComponent', () => {
it('should display contract data', async () => {
const { getByText } = renderWithProvider(
<MyComponent />,
{ client: mockClient }
);
await waitFor(() => {
expect(getByText('123')).toBeInTheDocument();
});
});
});Every push and pull request runs .github/workflows/test.yml, which enforces
the following checks (all must pass before a PR can be merged):
| Step | Command | Purpose |
|---|---|---|
| Lint | npm run lint |
ESLint over .ts/.tsx sources. |
| Typecheck | npx tsc --noEmit -p tsconfig.app.json |
Catches type errors across the full source tree, not just the build entrypoint. |
| Build | npm run build |
Produces the library bundle in dist/. |
| Bundle size | npm run size |
Fails if the published bundle exceeds the 50 KB gzipped budget. |
| Verify exports | npm run test:exports |
Type-checks src/verify-exports.ts so the public API can't silently break. |
| Tests | npm test |
Runs the Vitest suite. |
Bundle size is tracked with size-limit.
The budget is configured in the size-limit field of package.json and is
currently 50 KB gzipped for each of the ES and CommonJS bundles. Run it
locally after a build:
npm run build
npm run sizeIf a change pushes the bundle over budget, the check fails with a clear message
showing the current size versus the limit. Avoid this by keeping runtime
dependencies in devDependencies where possible and lazy-loading heavy assets.
- All PRs must have at least one approval
- CI checks must pass (lint, typecheck, build, bundle size, exports, tests)
- New components must include JSDoc comments
- Breaking changes need documentation updates
Open an issue or ask in discussions. We're here to help!