Testing a Studio app

A Studio screen is plain SvelteKit code, so you test it with the tools you already use. Three layers, cheapest first: the data source (no DOM), the screen (a component test), and the flow (end to end).

1. The data source (fastest)

Every backend is a plain object implementing ServerDataSource, so you can exercise read, filter, sort, and CRUD directly - no DOM, no database. Use the in-memory source for a deterministic fixture:

import { describe, it, expect } from 'vitest'
import { createInMemoryDataSource } from '@svgrid/enterprise'
import { customersSchema, type CustomersRow } from '$lib/customers.schema'

describe('customers data', () => {
  const source = () => createInMemoryDataSource<CustomersRow>([
    { id: '1', name: 'Ada', email: '[email protected]', tier: 'enterprise', mrr: 1200, active: true },
    { id: '2', name: 'Alan', email: '[email protected]', tier: 'pro', mrr: 240, active: false },
  ], customersSchema)

  it('filters + sorts like the grid asks', async () => {
    const page = await source().getRows({
      startRow: 0, endRow: 25, pageIndex: 0, pageSize: 25,
      sortModel: [{ id: 'mrr', desc: true }],
      filterModel: { columns: { active: { operator: 'equals', value: 'true' } } },
    })
    expect(page.rowCount).toBe(1)
    expect(page.rows[0]!.name).toBe('Ada')
  })

  it('creates and deletes', async () => {
    const s = source()
    const created = await s.createRow!({ name: 'Grace', email: '[email protected]', tier: 'pro', mrr: 900, active: true })
    await s.deleteRow!(created.id)
  })
})

It is the same ServerRequest shape the grid sends at runtime, so a passing test is a screen that behaves.

Business logic

If your schema has computed fields or hooks, wrap the source in withEntityRules and assert them the same way - computed values appear on read, and a validate hook or beforeCreate runs on write:

import { withEntityRules } from '@svgrid/enterprise'

const source = withEntityRules(createInMemoryDataSource(seed, orderSchema), orderSchema)
const [row] = (await source.getRows(fullPageRequest)).rows
expect(row.total).toBe(row.qty * row.price)   // computed on read

2. The screen (component test)

Mount the page and assert the rendered rows with @testing-library/svelte. Point it at an in-memory source so there is no network:

import { render, screen } from '@testing-library/svelte'
import { expect, it } from 'vitest'
import CustomersPage from '../routes/customers/+page.svelte'

it('renders the seeded customers', async () => {
  render(CustomersPage)
  expect(await screen.findByText('Ada Lovelace')).toBeInTheDocument()
})

Run component tests under Vitest's browser mode (or jsdom) - the grid virtualizes, so browser mode gives the most faithful layout.

3. The flow (end to end)

Drive the real app with Playwright: create a row, edit it, delete it, and assert the grid.

import { test, expect } from '@playwright/test'

test('create, edit, delete a customer', async ({ page }) => {
  await page.goto('/customers')
  await expect(page.getByText('Ada Lovelace')).toBeVisible()

  await page.getByRole('button', { name: '+ New customer' }).click()
  await page.getByLabel('Name').fill('Katherine Johnson')
  await page.getByLabel('Email').fill('[email protected]')
  await page.getByRole('button', { name: 'Save' }).click()
  await expect(page.getByText('Katherine Johnson')).toBeVisible()
})

For CRUD against a real database, point the run at a disposable schema (a test database, or a transaction rolled back in afterEach) so runs stay isolated.

What to test where

Layer Tool Good for
Data source Vitest filter / sort / paging logic, CRUD, computed fields, hooks
Screen Testing Library + Vitest the page renders, the form validates, the edit flow wires up
Flow Playwright the whole thing against a running app + real backend

Most of the value is in layer 1 - it is milliseconds per test and covers the logic that actually breaks. Add a few layer-3 smoke tests for confidence.

See also