jam-cloud/.planning/codebase/TESTING.md

7.5 KiB

Testing Patterns

Analysis Date: 2026-01-11

Test Framework

Runner:

  • Playwright 1.40.0 - E2E testing for React app (jam-ui/package.json, jam-ui/playwright.config.ts)
  • Jest - Unit testing for React (inferred from test file patterns)
  • RSpec 2.11 - Ruby testing framework (ruby/Gemfile)
  • Jasmine - Legacy JavaScript tests with Karma runner (web/spec/javascripts/karma.conf.js)

Assertion Library:

  • Jest expect - Unit tests
  • Playwright assertions - E2E tests (expect(page).toHaveText(...))
  • RSpec expectations - Ruby tests (.should, expect { }.to)

Run Commands:

# React tests
cd jam-ui
npm test                              # Playwright E2E tests
npx cypress run                       # Cypress tests (alternative)

# Ruby tests
cd ruby
bundle exec rspec                     # Ruby gem tests

cd web
bundle exec rspec                     # Rails backend tests

cd websocket-gateway
bundle exec rspec                     # WebSocket gateway tests

Test File Organization

Location:

  • React unit tests: jam-ui/tests/helpers/rest.test.js (co-located pattern)
  • Playwright E2E: jam-ui/test/friends.spec.ts (TypeScript)
  • Legacy JavaScript: web/spec/javascripts/*.spec.js
  • Ruby tests: ruby/spec/jam_ruby/models/*.spec.rb, web/spec/controllers/*.spec.rb

Naming:

  • Unit tests: {module}.test.js (e.g., rest.test.js)
  • E2E tests: {feature}.spec.ts (e.g., friends.spec.ts)
  • Ruby tests: {class_name}_spec.rb (e.g., text_message_spec.rb)

Structure:

jam-ui/
  tests/helpers/rest.test.js          # Jest unit tests
  test/friends.spec.ts                # Playwright E2E
web/
  spec/
    controllers/                      # Controller specs
    javascripts/                      # Legacy Jasmine tests
      karma.conf.js                   # Karma configuration
ruby/
  spec/jam_ruby/models/              # Model specs

Test Structure

Jest Unit Tests:

// jam-ui/tests/helpers/rest.test.js
describe('rest helpers', () => {
  test('getPersonById calls correct URL', async () => {
    const payload = { id: 42, name: 'John' };
    mockResolved(payload);
    const result = await rest.getPersonById(42);
    expectCalledWith('/users/42/profile');
    expect(result).toEqual(payload);
  });
});

Playwright E2E Tests:

// jam-ui/test/friends.spec.ts
import { test, expect } from '@playwright/test';

test.describe.serial('Friends page', () => {
  test.use({ storageState: 'test/storageState/user1.json' });

  test('Homepage', async ({ page }) => {
    await page.goto('/');
    await expect(page.locator('h1').first()).toHaveText('Dashboard - Home');
  });
});

RSpec Ruby Tests:

# ruby/spec/jam_ruby/models/text_message_spec.rb
describe TextMessage do
  before do
    TextMessage.delete_all
    @target_user = FactoryGirl.create(:user)
    @msg = TextMessage.new(:target_user_id => @target_user.id)
  end

  it "should retrieve conversation for both users" do
    @msg.message = "Test message"
    @msg.save!
    messages = TextMessage.index(@target_user.id, @source_user.id)
    messages.count.should == 1
  end
end

Jasmine Legacy Tests:

// web/spec/javascripts/jamserver.spec.js
describe("JamServer", function() {
  beforeEach(function() {
    jamserver = context.JK.JamServer;
  });

  it("Subscribing to ping should call function", function() {
    var called = false;
    jamserver.registerMessageCallback(context.JK.MessageType.PING_ACK, function() {
      called = true;
    });
    expect(called).toBeTruthy();
  });
});

Patterns:

  • Jest: describe() blocks for grouping, test() or it() for individual tests
  • Playwright: test.describe() for grouping, test() for individual E2E scenarios
  • RSpec: describe blocks, before hooks, it blocks with expectations
  • Jasmine: describe() and it() blocks, beforeEach() hooks

Mocking

Framework:

  • Jest built-in mocking: jest.mock() for module mocking
  • RSpec: FactoryGirl for test data fixtures
  • Manual mocks in test helpers

Jest Patterns:

// jam-ui/tests/helpers/rest.test.js
// Mock apiFetch module
const mockResolved = (payload) => {
  apiFetch.mockResolvedValue(payload);
};

const expectCalledWith = (url) => {
  expect(apiFetch).toHaveBeenCalledWith(url);
};

RSpec Patterns:

# ruby/spec/jam_ruby/models/text_message_spec.rb
before do
  @target_user = FactoryGirl.create(:user)
  @source_user = FactoryGirl.create(:user)
end

What to Mock:

  • External API calls (via apiFetch() in frontend)
  • Database queries (via FactoryGirl fixtures in Ruby)
  • Time/dates (when testing time-sensitive logic)
  • WebSocket connections (in unit tests)

What NOT to Mock:

  • Pure functions and utilities
  • Internal business logic
  • Simple data transformations

Fixtures and Factories

Test Data:

  • FactoryGirl for Ruby models: FactoryGirl.create(:user) in ruby/spec/
  • Manual test helpers in JavaScript: mockResolved(), expectCalledWith() in jam-ui/tests/helpers/rest.test.js
  • Playwright storage state: test.use({ storageState: 'test/storageState/user1.json' }) for authenticated tests

Location:

  • JavaScript helpers: jam-ui/tests/helpers/ (if applicable)
  • Ruby factories: Defined in ruby/spec/ or web/spec/ (FactoryGirl configuration)
  • Playwright fixtures: jam-ui/test/storageState/ (session cookies for auth)

Coverage

Requirements:

  • No enforced coverage targets
  • Coverage tracked for awareness, not enforcement
  • Focus on critical paths (API endpoints, core models, business logic)

Configuration:

  • Jest: Built-in coverage via --coverage flag
  • RSpec: SimpleCov gem for Ruby code coverage
  • Playwright: No coverage (E2E tests focus on integration, not coverage)

View Coverage:

# Jest coverage (if configured)
cd jam-ui
npm test -- --coverage

# RSpec coverage
cd ruby
bundle exec rspec
# Check coverage/index.html

Test Types

Unit Tests:

  • Scope: Single function/module in isolation
  • Mocking: Mock all external dependencies (API, database)
  • Speed: Fast (<100ms per test)
  • Examples: jam-ui/tests/helpers/rest.test.js, ruby/spec/jam_ruby/models/*.spec.rb

Integration Tests:

  • Scope: Multiple modules working together
  • Mocking: Mock only external boundaries (network, file system)
  • Examples: Rails controller specs testing controller + model interaction

E2E Tests:

  • Framework: Playwright 1.40.0 (primary), Cypress (alternative mentioned in CLAUDE.md)
  • Scope: Full user flows through UI
  • Setup: Uses storage state for authenticated sessions
  • Location: jam-ui/test/*.spec.ts

Common Patterns

Async Testing:

// Jest async/await
test('handles async operation', async () => {
  const result = await asyncFunction();
  expect(result).toBe('expected');
});

// Playwright async
test('async page interaction', async ({ page }) => {
  await page.goto('/');
  await expect(page.locator('h1')).toHaveText('Title');
});

Error Testing:

// Jest error testing
test('throws on invalid input', () => {
  expect(() => functionCall()).toThrow('error message');
});

// Async error
test('rejects on failure', async () => {
  await expect(asyncCall()).rejects.toThrow('error message');
});

Before/After Hooks:

  • Jest: beforeEach(), afterEach() for setup/teardown
  • RSpec: before do ... end, after do ... end
  • Playwright: test.beforeAll(), test.afterAll() for global setup

Snapshot Testing:

  • Not used in this codebase
  • Prefer explicit assertions for clarity

Testing analysis: 2026-01-11 Update when test patterns change