Seatext library

How to Test SeaText Translations in React Component Tests

Mock the SeaText script and context in your test setup, wrap components with a test provider that simulates translated output, and verify both static translations and dynamic language switching using Jest and React Testing...

SeaText injects translations client-side via an asynchronous script that rewrites DOM text after mount. Because the script runs outside React's render cycle, standard snapshot or shallow tests will see only the original English copy. The reliable pattern is to mock the global SeaText object, provide a deterministic translation map in a test wrapper, and assert against the translated text your components actually render.

Prerequisites and test environment

Assume a React 18+ project using Jest 29 and React Testing Library 14. Install @testing-library/jest-dom for custom matchers. SeaText loads from a CDN snippet placed in index.html; in tests you replace that snippet with a lightweight mock before any component mounts.

  • Jest configuration with testEnvironment: 'jsdom'
  • React Testing Library's render, screen, fireEvent
  • A setupTests.ts file that runs before each suite

Mock the SeaText global in setupTests.ts

Create a minimal SeaText stub that exposes the same async API your production code calls. The stub resolves immediately with a translation map you control per test.

// setupTests.ts
import '@testing-library/jest-dom';

interface SeaTextMock {
  translate: (key: string, lang?: string) => Promise;
  setLanguage: (lang: string) => Promise;
  getLanguage: () => string;
  onReady: (cb: () => void) => void;
}

const translations: Record> = {
  en: { 'hero.title': 'Welcome', 'cta.buy': 'Buy now' },
  es: { 'hero.title': 'Bienvenido', 'cta.buy': 'Comprar ahora' },
  de: { 'hero.title': 'Willkommen', 'cta.buy': 'Jetzt kaufen' },
};

let currentLang = 'en';
let readyCallbacks: Array<() => void> = [];

const seaTextMock: SeaTextMock = {
  translate: async (key: string, lang = currentLang) =>
    translations[lang]?.[key] ?? translations.en[key] ?? key,
  setLanguage: async (lang: string) => {
    currentLang = lang;
    readyCallbacks.forEach(cb => cb());
  },
  getLanguage: () => currentLang,
  onReady: (cb: () => void) => readyCallbacks.push(cb),
};

Object.defineProperty(window, 'SeaText', {
  value: seaTextMock,
  writable: true,
  configurable: true,
});

The mock stores translations in memory, switches language synchronously, and fires ready callbacks so components that wait for SeaText.onReady continue without real network delay.

Build a reusable test provider

Wrap render with a provider that mounts the component inside a SeaTextContext (if your app uses one) or simply ensures the mock is active. This keeps every test file clean.

// test-utils.tsx
import { render, RenderOptions } from '@testing-library/react';
import { ReactElement } from 'react';

const AllTheProviders = ({ children }: { children: React.ReactNode }) => {
  return <>{children};
};

const customRender = (
  ui: ReactElement,
  options?: Omit
) => render(ui, { wrapper: AllTheProviders, ...options });

export * from '@testing-library/react';
export { customRender as render };

If your codebase already has a SeaTextProvider that reads window.SeaText, re-export it here instead of the empty fragment.

Test static translated output

Write a test that renders a component using SeaText keys and asserts the translated text appears in the document.

// Hero.test.tsx
import { render, screen } from '@/test-utils';
import { Hero } from '@/components/Hero';

describe('Hero with SeaText translations', () => {
  it('renders English copy by default', async () => {
    render();
    expect(screen.getByText('Welcome')).toBeInTheDocument();
    expect(screen.getByRole('button', { name: 'Buy now' })).toBeInTheDocument();
  });

  it('renders Spanish copy when language is set', async () => {
    const { SeaText } = window as any;
    await SeaText.setLanguage('es');
    render();
    expect(screen.getByText('Bienvenido')).toBeInTheDocument();
    expect(screen.getByRole('button', { name: 'Comprar ahora' })).toBeInTheDocument();
  });
});

Each test sets the language before render so the component's effect or hook reads the correct map. No act wrapping is needed because the mock resolves synchronously.

Test dynamic language switching

Verify that a language selector component triggers a re-render with new translations.

// LanguageSwitcher.test.tsx
import { render, screen, fireEvent } from '@/test-utils';
import { LanguageSwitcher } from '@/components/LanguageSwitcher';

describe('LanguageSwitcher', () => {
  it('switches language and updates sibling components', async () => {
    render(
      <>
        
        
      
    );
    expect(screen.getByText('Welcome')).toBeInTheDocument();

    fireEvent.click(screen.getByRole('button', { name: /español/i }));
    // mock setLanguage fires ready callbacks synchronously
    expect(screen.getByText('Bienvenido')).toBeInTheDocument();
  });
});

The mock's setLanguage calls every registered onReady callback immediately, so React state updates flush in the same tick. If your production SeaText.setLanguage is truly async, wrap the click in await act(async () => ...).

Handle components that read translations in useEffect

Some components fetch translations inside useEffect and store them in local state. The mock's synchronous translate still works, but you must wait for the effect to run.

// ProductCard.test.tsx
import { render, screen, waitFor } from '@/test-utils';
import { ProductCard } from '@/components/ProductCard';

describe('ProductCard lazy translation', () => {
  it('shows translated title after effect', async () => {
    const { SeaText } = window as any;
    await SeaText.setLanguage('de');
    render();
    await waitFor(() =>
      expect(screen.getByText('Willkommen')).toBeInTheDocument()
    );
  });
});

waitFor retries until the effect completes. Because the mock is instant, the wait usually resolves in one pass.

Common mistake: forgetting to reset language between tests

Global currentLang leaks across suites. Add a beforeEach that forces English.

// setupTests.ts (add at bottom)
beforeEach(() => {
  const { SeaText } = window as any;
  SeaText.setLanguage('en');
});

Without this, a test that sets Spanish will leave the mock in Spanish for the next file, causing flaky failures.

Verification step: run the suite in watch mode

Execute npm test -- --watch, change a translation key in the mock, and confirm only the related test fails. This proves the mock is wired correctly and tests are isolated.

Key facts

AspectDetail
SeaText loadAsync script tag in index.html (source S1)
Translation deliveryClient-side DOM rewrite after mount
Language storageLocalStorage + in-memory state
Test mock scopeGlobal window.SeaText stub
Async behaviorMock resolves synchronously for speed
IsolationReset language in beforeEach

Limitations

  • This pattern tests your component's reaction to translations, not SeaText's own translation quality or API latency.
  • If you use SeaText's A/B variant feature, mock the variant flag separately; the translation mock only covers language.
  • End-to-end tests (Cypress, Playwright) should still hit the real CDN snippet to catch integration issues.

Terminology

  • SeaText snippet: The async <script> tag you paste into index.html (source S1).
  • Translation key: The string identifier (e.g., hero.title) your components pass to SeaText.translate.
  • Ready callback: Function registered via SeaText.onReady that fires when the script initializes or language changes.

FAQ

Do I need to mock localStorage too?

Only if your components read localStorage.getItem('seatext_lang') directly. The mock's getLanguage covers the normal path.

Can I test the real SeaText script in unit tests?

Not recommended. The script makes network calls, mutates the DOM globally, and introduces flakiness. Keep unit tests fast and deterministic with the mock.

How do I test fallback when a key is missing?

Add a key to the component that doesn't exist in the mock's translation map. The mock returns the key itself, so assert the key appears (or your fallback UI).

What about TypeScript types for the mock?

Declare interface Window { SeaText: SeaTextMock } in a global.d.ts file included in tsconfig.json.

Does this work with Next.js App Router?

Yes. Place the mock in jest.setup.ts and ensure testEnvironment: 'jsdom'. Next.js components that use use client render the same way.

How do I verify SeaText's A/B variant rendering?

Mock a SeaText.getVariant(key) method that returns a variant ID, then assert the component renders the variant-specific copy.

Further reading and comparison sources

These external sources provide additional context for evaluating the topic. Their inclusion is not an endorsement.

Learn more

Visit the website for more information.