Files
supabase/apps/studio/tests
Danny White 0cb8bd95dd feat(studio): add spot colour control to Appearance (#50782)
## Problem

The theme's primary hue can change in CSS, but Appearance had no way to
try other spot colours. That makes it hard to find controls whose colour
still depends on the fixed Supabase brand palette.

## Solution

Add a **Spot color** control under Appearance → Theme colors
(employee-only via ConfigCat `appearanceSpotColor`, targeted to Supabase
Team Email).

### Spot color UX
- Rainbow spectrum track with a thin outline so pale tracks stay visible
- Live trifecta swatches for `--primary-solid`, `--primary`, and
`--primary-bright` (darkest → lightest) next to the degree readout
- Drag updates are rAF-batched so React paint and CSS preview stay to
one frame

### Canvas tint coupling
- `--surface-hue` is derived in CSS as `calc(var(--primary-hue) +
var(--surface-hue-offset))`
- Dark: offset `0` (same hue as spot)
- Light: offset `180` (complementary canvas tint; brand green ≈157.5° →
rose ≈337.5°)
- No JS override of `--surface-hue`. Changing Spot color moves primary
controls and the low-chroma canvas tint together

### Other theme sliders
- Renamed **Color intensity** → **Surface tint** (it only drives the
neutral ramp via `--chroma`, not spot chroma)
- Meaning-shaped tracks for every knob (spectrum, grey→tint, soft→hard,
dark→light, flat→lift)
- Same outline treatment on those tracks

| Before | After |
| --- | --- |
| <img width="1476" height="2174" alt="CleanShot 2026-10-05 at 15 02
21@2x"
src="https://github.com/user-attachments/assets/d08b0fa8-32af-450e-adce-861f59c9d6ca"
/> | <img width="1474" height="2354" alt="CleanShot 2026-10-05 at 14 56
35@2x"
src="https://github.com/user-attachments/assets/9a1e6183-a4ba-4c8e-a458-bb0eea946db8"
/> |
| _Anyone else_ | _With staff flag, custom settings_ |

## Review instructions

1. Confirm ConfigCat flag `appearanceSpotColor` is on for your staff
account (or flip it in the Dev Toolbar).
2. Open `/account/me` → **Appearance → Theme colors**.
3. Without the flag: Spot color is hidden; other theme sliders still
work.
4. With the flag: drag Spot color in light and dark. Primary controls
and canvas tint should move together; Supabase brand assets should stay
fixed.
5. Raise Surface tint and confirm the canvas hue follows the
complementary (light) or same-hue (dark) offset.
6. Refresh, switch modes, and use **Reset** to check persistence and
defaults.
2026-10-06 10:11:26 +11:00
..
2026-09-11 12:17:49 +08:00

UI Testing Notes

Rules

  • All tests should be run consistently (avoid situations whereby tests fails "sometimes")

  • Group tests in folders based on the feature they are testing. Avoid file/folder based folder names since those can change and we will forget to update the tests.

Examples: /logs /reports /projects /database-settings /auth

Custom Render and Custom Render Hook

customRender and customRenderHook are wrappers around render and renderHook that add some necessary providers like QueryClientProvider, TooltipProvider and NuqsTestingAdapter.

Generally use those instead of the default render and renderHook functions.

import { customRender, customRenderHook } from 'tests/lib/custom-render'

customRender(<MyComponent />)
customRenderHook(() => useMyHook())

Mocking API Requests

To mock API requests, we use the msw library.

Global mocks can be found in tests/lib/msw-global-api-mocks.ts.

To mock an endpoint you can use the addAPIMock function. Make sure to add the mock in the beforeEach hook. It won't work with beforeAll if you have many tests.

beforeEach(() => {
  addAPIMock({
    method: 'get',
    path: '/api/my-endpoint',
    response: {
      data: { foo: 'bar' },
    },
  })
})

API Mocking Tips:

  • Keep mocks in the same folder as the tests that use them
  • Add a test to verify the mock is working

This will make debugging and updating the mocks easier.

test('mock is working', async () => {
  const response = await fetch('/api/my-endpoint')
  expect(response.json()).resolves.toEqual({ data: { foo: 'bar' } })
})

Mocking Nuqs URL Parameters

To render a component that uses Nuqs with some predefined query parameters, you can use customRender with the nuqs prop.


customRender(<MyComponent />, {
  nuqs: {
    searchParams: {
      search: 'hello world',
    },
  },
})

<Popover> vs <Dropdown>

When simulating clicks on these components, do the following:

// for Popovers
import userEvent from '@testing-library/user-event'
await userEvent.click('Hello world')

// for Dropdowns
import clickDropdown from 'tests/helpers'
clickDropdown('Hello world')