Skip to content

Latest commit

 

History

History
254 lines (172 loc) · 8.39 KB

File metadata and controls

254 lines (172 loc) · 8.39 KB

Contributing to Contentful Field Editors

Please take a moment to review this document in order to make the contribution process easy and effective for everyone involved.

Getting started

Requirements

  • Node.js: >=16.18.0
  • Yarn: >=1.21.1

To install all dependencies and build all packages run the following commands from the root of the project.

yarn
yarn build

You are ready to go! You can either develop your apps from apps folder or run a playground of all components in development mode.

Submitting a Pull Request

Good pull requests, such as patches, improvements, and new features, are a fantastic help. They should remain focused in scope and avoid containing unrelated commits.

Please ask first if somebody else is already working on this or the Contentful developers think your feature is in-scope. Generally always have a related issue with discussions for whatever you are including.

Folder structure

@contenful/field-editors is a monorepo, meaning it is divided into independent sub-packages. These packages can be found in the packages/ directory.

This monorepo is maintained using Lerna. Get started with Lerna by following this link.

Local development

Using docz

We use storybook as a components playground.

# to run playground in a development mode
yarn start

Making changes in shared packages

Go to a shared package and run the following command to watch for changes and rebuild the package once it's changed:

cd packages/_shared
yarn watch

Integration to Contentful web application

Relevant for Contentful employees only.

It is convenient to link a local copy of a package to a locally running Contentful web application without publishing a package.

yarn
yarn build
cd packages/single-line
# register a symplink
yarn link
# watch for changes in the package
yarn watch

In the web app repository:

# create a symlink to a local folder
yarn link '@contentful/field-editor-single-line'

Adding packages

To add another package create a new directory in the packages folder. Since we are using Lerna all package scripts are available from the root by running lerna run <script_name>.

Internationalization (i18n) setup

The project supports i18n thanks to the Lingui library.

Prerequisites & Naming conventions

To translate your strings, you need to make them identifiable with a translation key, represented by the id.

To ensure uniqueness, consistency, and descriptive keys, the keys follow a naming convention.

A custom ESLint rule is available to ensure the naming convention is followed, and that only supported projects prefixes are used.

Adding translations

Depending on the structure and complexity of your strings, use the t function from @lingui/core/macro or the Trans react component from @lingui/react. Important: you have to add a default message to every new key added, in case the frontend application where a package is used, is not providing a translation catalog.

Quality & Code Style

Commit messages

All commit messages should meet the conventional commit format. The easiest way is to use yarn cm command which launches commit message wizard.

Code formatting

You don't need to worry about formatting your code. It is automatically reformatted using prettier on every commit using Git hooks.

Linting

We use ESLint and Typescript ESLint for linting and checking code for errors.

All modern editors should automatically pick up configuration and show errors and warnings while you type.

Run ESLint for all packages

# at the monorepo root
yarn lint

Checking types

Run Typescript checker for all packages

# at the monorepo root
yarn tsc

Tests

We use Vitest and Testing Library for writing unit tests.

Run tests for concrete package

cd packages/single-line
yarn test

Run tests for all packages

# at the monorepo root
yarn test:ci

Links

Accessibility

The repo has three complementary layers of accessibility tooling, active at different points in the development workflow.

Storybook (@storybook/addon-a11y)

The accessibility addon is pre-configured. Run yarn start and open any story — the Accessibility panel shows live axe results as you develop.

Unit tests (vitest-axe)

We use vitest-axe for automated accessibility checks in unit tests. The toHaveNoViolations matcher is globally available.

Every component needs axe coverage across its meaningful states. A single test on the default render is not sufficient: a component that passes in its default state can still have violations when disabled, in an error state, or with different content rendered. Cover each state where the DOM structure or ARIA attributes change.

What to cover per component:

  • Default / empty — baseline render with no value
  • Filled — rendered with actual content (especially relevant for rich text or media editors where content changes the DOM)
  • Disabled — if element can be rendered in a disabled state
  • Error / validation state — after errors are emitted
  • Any state that adds or removes interactive elements (e.g. dialogs, popovers, expanded panels)

Example unit test

import { axe } from 'vitest-axe';
import { render } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { createFakeFieldAPI, createFakeLocalesAPI } from '@contentful/field-editor-test-utils';

import { SingleLineEditor } from './SingleLineEditor';

describe('SingleLineEditor accessibility', () => {
  it('has no violations in default state', async () => {
    const [field] = createFakeFieldAPI((f) => ({ ...f, type: 'Symbol' }));
    const { container } = render(
      <SingleLineEditor
        field={field}
        isInitiallyDisabled={false}
        locales={createFakeLocalesAPI()}
      />,
    );
    expect(await axe(container)).toHaveNoViolations();
  });

  it('has no violations when disabled', async () => {
    const [field] = createFakeFieldAPI((f) => ({ ...f, type: 'Symbol' }));
    const { container } = render(
      <SingleLineEditor
        field={field}
        isInitiallyDisabled={true}
        locales={createFakeLocalesAPI()}
      />,
    );
    expect(await axe(container)).toHaveNoViolations();
  });

  it('has no violations with a validation error', async () => {
    const [field, mitt] = createFakeFieldAPI((f) => ({ ...f, type: 'Symbol' }));
    const { container } = render(
      <SingleLineEditor
        field={field}
        isInitiallyDisabled={false}
        locales={createFakeLocalesAPI()}
      />,
    );
    // trigger an error state before asserting
    act(() => mitt.emit('schemaErrors', [{ message: 'Required' }]));
    expect(await axe(container)).toHaveNoViolations();
  });
});

Component tests (cypress-axe)

Component tests have cypress-axe wired up via cypress/support/component.ts. Use cy.checkA11y() to assert no violations after mounting a component.

Axe is injected lazily on the first cy.checkA11y() call. Four structural rules are globally disabled as they do not apply to isolated component tests: html-has-lang, landmark-one-main, page-has-heading-one, region. Violations are printed to the terminal for readable CI output.

To scope the check or disable a rule for one test:

// Check only a specific element
cy.checkA11y('[role="dialog"]');

// Disable a rule for one test
cy.checkA11y(undefined, { rules: { 'color-contrast': { enabled: false } } });

Example component test

it('has no a11y violations', () => {
  cy.mount(<MyComponent />);
  cy.checkA11y();
});

Further reading