Skip to content

Development Guide

This guide covers the development workflow, tools, and best practices for contributing to GridSheet.

  • Node.js: Version 22.x or later
  • pnpm: For package management
  • Git: For version control
Terminal window
# Clone the repository
git clone https://github.com/your-org/gridsheet.git
cd gridsheet
# Install dependencies
pnpm install
Terminal window
# Start Storybook development server
pnpm dev
# Start documentation development server
pnpm doc
# Run unit tests
pnpm test
# Run e2e tests (uses server)
pnpm e2e
# Run linting and apply prettier
pnpm lint:fix

GridSheet follows this testing strategy:

  • E2E tests (Playwright): The default for all features — component behavior, user interactions, and integration between components.
  • Unit tests (Jest): Used only for packages/functions. Every formula function in that package must have a corresponding Jest unit test.
import { test, expect } from '@playwright/test';
import { ctrl, drag, paste } from './utils';
test('time + delta, time + number(days)', async ({ page }) => {
await page.goto('http://localhost:5233/iframe.html?id=basic-simple--sheet&viewMode=story');
const a4 = page.locator("[data-address='A4']");
const b4 = page.locator("[data-address='B4']");
const c4 = page.locator("[data-address='C4']");
const a5 = page.locator("[data-address='A5']");
expect(await a4.locator('.gs-cell-rendered').textContent()).toBe('2022-03-05 12:34:56');
expect(await b4.locator('.gs-cell-rendered').textContent()).toBe('11:11:11');
expect(await c4.locator('.gs-cell-rendered').textContent()).toBe('2022-03-05 23:46:07');
expect(await a5.locator('.gs-cell-rendered').textContent()).toBe('2022-03-04 23:34:56');
});

Available Utilities:

The E2E test suite provides utility functions for common operations:

  • ctrl: Helper for Ctrl key combinations
  • drag: Helper for drag and drop operations
  • paste: Helper for paste operations

These utilities are available in e2e/utils.ts and can be imported to simplify test writing.

When adding new formula functions to GridSheet, follow these guidelines:

To keep the distribution size of @gridsheet/react-core as compact as possible, it only includes essential, minimum functions. All future function additions must be made to the @gridsheet/functions package (packages/functions).

When adding new functions, prioritize those that are also supported by Google Spreadsheet:

  • Target Functions: Focus on functions that exist in Google Sheets
  • Specification Matching: Follow Google Sheets’ function specifications and behavior
  • Parameter Compatibility: Match argument types, order, and validation rules
  • Return Value Consistency: Ensure return values match Google Sheets’ behavior
  • Error Handling: Implement the same error conditions and messages

All custom formula functions must extend the BaseFunction class:

import { BaseFunction, ensureString } from '@gridsheet/react-core';
import type { FunctionArgumentDefinition } from '@gridsheet/react-core';
class CustomFunction extends BaseFunction {
// Required properties for documentation and the function guide UI
example = 'CUSTOM_FUNCTION("value", 123)';
description = 'Performs a custom operation on the input values';
category = 'text' as const; // math | statistics | text | time | lookup | information | logical | other
defs: FunctionArgumentDefinition[] = [
{ name: 'value', description: 'The input value to process', acceptedTypes: ['string'] },
{ name: 'number', description: 'A numeric parameter', acceptedTypes: ['number'] },
];
// Required main implementation
protected main(value: string, number: number) {
// Your custom logic here
return `Processed: ${value} with ${number}`;
}
}

Note: The built-in validate() method automatically checks argument count and types based on defs. Override it only when you need custom coercion logic beyond what defs provides.

Every formula function must include these properties:

  • example: Shows how to use the function in a formula
  • description: A string describing what the function does (used in the FunctionGuide UI)
  • category: Classification (math, statistics, text, time, lookup, information, logical, other)
  • defs: Array of FunctionArgumentDefinition objects with name, description, and optional type constraints

Register custom functions via the additionalFunctions option in useBook (or createBook):

import { useBook } from '@gridsheet/react-core';
const book = useBook({
additionalFunctions: {
CUSTOM_FUNCTION: CustomFunction,
},
});

For apps that want all extended functions from the @gridsheet/functions package:

import { useSpellbook } from '@gridsheet/react-core/spellbook';
// useSpellbook pre-loads all extended functions and accepts extra ones
const book = useSpellbook({
additionalFunctions: { CUSTOM_FUNCTION: CustomFunction },
});
Terminal window
# Start documentation development server
pnpm doc
# Build documentation
pnpm build:docs
Terminal window
# Start Storybook development server
pnpm dev
# Build Storybook
pnpm build:storybook
  1. Create Feature Branch: git checkout -b feature/new-feature
  2. Make Changes: Implement your feature
  3. Run Tests: Ensure all tests pass (pnpm test)
  4. Update Documentation: Update relevant docs
  5. Submit PR: Create pull request with description
  • Code follows style guidelines
  • Tests are included and passing
  • Documentation is updated
  • No breaking changes (or documented)
  • Performance impact considered
  1. Version Bump: pnpm set-version <version> (updates all packages, commits, and tags)
  2. Push to master: git push origin master --tags
  3. GitHub Actions (release.yaml) will automatically build and publish all packages to npm

Recommended: Release via GitHub Actions rather than manually. The CI pipeline handles build order and dependency resolution correctly.

If you must publish manually, always use pnpm publish (not npm publish). pnpm publish automatically converts workspace:* references to actual version numbers. npm publish will leave workspace:* as-is, causing installation failures for users.