2025-10-29 22:16:33 -06:00
|
|
|
/**
|
|
|
|
|
* Test utilities for React Testing Library
|
|
|
|
|
* Provides custom render functions with necessary context providers
|
|
|
|
|
*/
|
|
|
|
|
|
2026-01-23 19:51:02 -07:00
|
|
|
import React, {createContext, useContext} from 'react';
|
2025-10-29 22:16:33 -06:00
|
|
|
import {render} from '@testing-library/react';
|
2025-12-07 11:54:20 -07:00
|
|
|
import {BrowserRouter} from 'react-router';
|
ui: migrate the extension pages and Configs; five components they needed
Phase 2, part 1. Three files leave Semantic UI (baseline 75 -> 72), and the
components they needed join the library and the gallery:
Header Semantic's shape (as/icon/subheader/dividing). 47 files use
<Header>, and the level now picks the size, so the type scale
stays a scale instead of each call site choosing a font size.
Message gains an icon and a dismiss button.
ActionInput input and button sharing one outline, for <Input action={...}/>.
Loading centered loader with a caption, replacing the
<Loader active inline='centered'> + wrapper div in 19 places.
Modal becomes compound - Modal.Header/.Content/.Actions, plus
Semantic's open, closeIcon, and size names - because 34 files
are written against that shape.
test-utils mounts MantineProvider with ThemeProvider's exact configuration:
migrated components render Mantine internals and every one of their test files
would otherwise need its own provider.
One Semantic bleed found and fixed: semantic.min.css sets
`h1,h2,h3,h4,h5 { font-family: Lato }` and loads after Mantine's element rule,
so headings ignored the theme's body face. Only visible in amber, where the
body is mono and the headings were not. A class-level restatement outranks it;
the rule comes out when Semantic does.
Verified: 925 jest tests, clean production build, and a night-mode audit of
every painted pixel in the gallery (red-only, one known false positive). The
Configs backup and restore flow was exercised against the real API.
2026-07-29 20:25:34 -06:00
|
|
|
import {MantineProvider} from '@mantine/core';
|
2025-10-29 22:16:33 -06:00
|
|
|
import {MediaContextProvider, ThemeContext} from './contexts/contexts';
|
ui: migrate the extension pages and Configs; five components they needed
Phase 2, part 1. Three files leave Semantic UI (baseline 75 -> 72), and the
components they needed join the library and the gallery:
Header Semantic's shape (as/icon/subheader/dividing). 47 files use
<Header>, and the level now picks the size, so the type scale
stays a scale instead of each call site choosing a font size.
Message gains an icon and a dismiss button.
ActionInput input and button sharing one outline, for <Input action={...}/>.
Loading centered loader with a caption, replacing the
<Loader active inline='centered'> + wrapper div in 19 places.
Modal becomes compound - Modal.Header/.Content/.Actions, plus
Semantic's open, closeIcon, and size names - because 34 files
are written against that shape.
test-utils mounts MantineProvider with ThemeProvider's exact configuration:
migrated components render Mantine internals and every one of their test files
would otherwise need its own provider.
One Semantic bleed found and fixed: semantic.min.css sets
`h1,h2,h3,h4,h5 { font-family: Lato }` and loads after Mantine's element rule,
so headings ignored the theme's body face. Only visible in amber, where the
body is mono and the headings were not. A class-level restatement outranks it;
the rule comes out when Semantic does.
Verified: 925 jest tests, clean production build, and a night-mode audit of
every painted pixel in the gallery (red-only, one known false positive). The
Configs backup and restore flow was exercised against the real API.
2026-07-29 20:25:34 -06:00
|
|
|
import {cssVariablesResolver, mantineTheme} from './themes/mantine';
|
2025-10-29 22:16:33 -06:00
|
|
|
|
2026-01-23 19:51:02 -07:00
|
|
|
// Mock FileWorkerStatusContext for tests - avoids API calls during testing
|
|
|
|
|
const MockFileWorkerStatusContext = createContext(null);
|
|
|
|
|
|
|
|
|
|
function MockFileWorkerStatusProvider({children}) {
|
|
|
|
|
const mockValue = {
|
|
|
|
|
status: null,
|
|
|
|
|
error: null,
|
|
|
|
|
refresh: jest.fn(),
|
|
|
|
|
setFastPolling: jest.fn(),
|
|
|
|
|
};
|
|
|
|
|
return (
|
|
|
|
|
<MockFileWorkerStatusContext.Provider value={mockValue}>
|
|
|
|
|
{children}
|
|
|
|
|
</MockFileWorkerStatusContext.Provider>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Re-export hooks that use the mock context
|
|
|
|
|
export function useFileWorkerStatus() {
|
|
|
|
|
const context = useContext(MockFileWorkerStatusContext);
|
|
|
|
|
if (!context) {
|
|
|
|
|
throw new Error('useFileWorkerStatus must be used within FileWorkerStatusProvider');
|
|
|
|
|
}
|
|
|
|
|
return context;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useReorganizationStatus() {
|
|
|
|
|
const {status: progress, refresh} = useFileWorkerStatus();
|
|
|
|
|
return {
|
|
|
|
|
isReorganizing: false,
|
|
|
|
|
taskType: null,
|
|
|
|
|
collectionId: null,
|
|
|
|
|
collectionKind: null,
|
|
|
|
|
batchStatus: null,
|
|
|
|
|
workerStatus: progress,
|
|
|
|
|
refresh,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-29 22:16:33 -06:00
|
|
|
/**
|
|
|
|
|
* Custom render function that wraps components with necessary providers
|
|
|
|
|
* @param {React.Component} ui - Component to render
|
|
|
|
|
* @param {Object} options - Render options
|
|
|
|
|
* @param {boolean} options.inverted - Whether to use dark theme
|
|
|
|
|
* @param {Object} options.themeContext - Custom theme context values
|
|
|
|
|
* @param {boolean} options.withMedia - Include MediaContextProvider (default: false)
|
|
|
|
|
* @param {Object} options.renderOptions - Additional React Testing Library options
|
|
|
|
|
*/
|
|
|
|
|
export function renderWithProviders(
|
|
|
|
|
ui,
|
|
|
|
|
{
|
|
|
|
|
inverted = false,
|
|
|
|
|
themeContext = {},
|
|
|
|
|
withMedia = false,
|
|
|
|
|
...renderOptions
|
|
|
|
|
} = {}
|
|
|
|
|
) {
|
|
|
|
|
const defaultThemeContext = {
|
|
|
|
|
inverted,
|
|
|
|
|
setInverted: jest.fn(),
|
|
|
|
|
// Theme components use different properties:
|
|
|
|
|
i: inverted ? {inverted: true} : {inverted: undefined}, // For Semantic UI elements (Segment, Form, etc.)
|
|
|
|
|
s: inverted ? {style: {backgroundColor: '#1B1C1D', color: '#dddddd'}} : {}, // For style inversion
|
|
|
|
|
t: inverted ? {style: {color: '#eeeeee'}} : {}, // For text color inversion (Header, etc.)
|
|
|
|
|
theme: inverted ? 'dark' : 'light',
|
ui: theme tokens and a four-theme runtime
Every color now resolves through CSS custom properties on
html[data-theme=...], so a theme is a table of values rather than code.
Four themes ship: light, dark, night (red hues only, to preserve dark
adaptation off-grid) and amber (monochrome amber terminal).
Fonts are exposed as two roles, --font-body and --font-mono, which a theme
assigns a bundled face to; amber uses the mono face. Faces must be
self-hosted, never fetched from a CDN, so the app keeps working offline.
Night mode also filters media that cannot consume tokens - video, images,
PDFs, embedded pages, canvases - through an SVG color matrix that maps
luminance onto the red channel. Tag such an element with `.media`; a filter
on an app-level ancestor would create a containing block and break fixed
positioning.
ThemeProvider persists the choice and index.html stamps data-theme from
localStorage before the bundle loads, so the first paint already matches the
saved theme on a slow connection. The OS preference is followed only while
"system" is selected; night and amber are explicit choices. The nav toggle
becomes a five-item picker.
Semantic components still render themselves, so only the shell is
token-driven for now; the inverted props stay in place until each component
is migrated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 18:32:50 -06:00
|
|
|
isDark: inverted,
|
|
|
|
|
savedTheme: null,
|
ui: media filtering is a per-theme setting, not a night-mode hardcode
Filtering media was keyed on `data-theme="night"`, which made it a property of
the theme that no user could turn off. Some people want their media tinted to
match the interface; some want to see the actual image.
A theme now declares a filter and whether it starts on, and the user decides:
night night-red on by default. An unfiltered thumbnail undoes dark
adaptation, which is the whole point of the mode.
amber amber-mono off by default. Amber is a look, not a night-vision
aid, so media keeps its real colors until asked for.
light - no filter offered, so no toggle is shown.
dark -
The choice is stored per theme, so wanting amber's tint says nothing about
wanting night's. The toggle lives in the theme picker, which puts it on the
Settings page and in the gallery at once.
Mechanically the rules now key on a `data-media-filter` attribute rather than
`data-theme`: a rule keyed on the theme could not be turned off. index.html
stamps that attribute before first paint alongside data-theme -- without it,
night mode paints a screen of unfiltered thumbnails while the bundle arrives
over a hotspot, costing the user the adaptation the theme exists to protect.
Tests cover both the attribute rule and the fact that the inline script's
duplicated ids and defaults still agree with names.ts.
The amber matrix is the same luminance collapse tinted to #ff9500 (red full,
green 0.584, blue zero) with a gentler gamma than night's: amber carries far
more luminance and clips to a flat wash otherwise.
The gallery gains a Media filtering section showing the WROLPi logo filtered,
beside an unfiltered copy that opts out with `night-unfiltered`. That copy is
behind a click on purpose -- an unfiltered image is a bright patch, and night
mode should not spring one on someone reading in the dark.
ui-design.md gains the media filtering contract it never got in Phase 0,
including the note that a Phase 3 custom theme names an existing filter id and
cannot ship its own matrix, since that would be code rather than data.
2026-07-30 08:51:28 -06:00
|
|
|
// Media filtering is off by default here; a test that needs it sets these.
|
|
|
|
|
mediaFilter: undefined,
|
|
|
|
|
mediaFilterEnabled: false,
|
|
|
|
|
setMediaFilterEnabled: jest.fn(),
|
ui: theme tokens and a four-theme runtime
Every color now resolves through CSS custom properties on
html[data-theme=...], so a theme is a table of values rather than code.
Four themes ship: light, dark, night (red hues only, to preserve dark
adaptation off-grid) and amber (monochrome amber terminal).
Fonts are exposed as two roles, --font-body and --font-mono, which a theme
assigns a bundled face to; amber uses the mono face. Faces must be
self-hosted, never fetched from a CDN, so the app keeps working offline.
Night mode also filters media that cannot consume tokens - video, images,
PDFs, embedded pages, canvases - through an SVG color matrix that maps
luminance onto the red channel. Tag such an element with `.media`; a filter
on an app-level ancestor would create a containing block and break fixed
positioning.
ThemeProvider persists the choice and index.html stamps data-theme from
localStorage before the bundle loads, so the first paint already matches the
saved theme on a slow connection. The OS preference is followed only while
"system" is selected; night and amber are explicit choices. The nav toggle
becomes a five-item picker.
Semantic components still render themselves, so only the shell is
token-driven for now; the inverted props stay in place until each component
is migrated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 18:32:50 -06:00
|
|
|
setTheme: jest.fn(),
|
|
|
|
|
setDarkTheme: jest.fn(),
|
|
|
|
|
setLightTheme: jest.fn(),
|
|
|
|
|
cycleSavedTheme: jest.fn(),
|
2025-10-29 22:16:33 -06:00
|
|
|
...themeContext
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function Wrapper({children}) {
|
ui: migrate the extension pages and Configs; five components they needed
Phase 2, part 1. Three files leave Semantic UI (baseline 75 -> 72), and the
components they needed join the library and the gallery:
Header Semantic's shape (as/icon/subheader/dividing). 47 files use
<Header>, and the level now picks the size, so the type scale
stays a scale instead of each call site choosing a font size.
Message gains an icon and a dismiss button.
ActionInput input and button sharing one outline, for <Input action={...}/>.
Loading centered loader with a caption, replacing the
<Loader active inline='centered'> + wrapper div in 19 places.
Modal becomes compound - Modal.Header/.Content/.Actions, plus
Semantic's open, closeIcon, and size names - because 34 files
are written against that shape.
test-utils mounts MantineProvider with ThemeProvider's exact configuration:
migrated components render Mantine internals and every one of their test files
would otherwise need its own provider.
One Semantic bleed found and fixed: semantic.min.css sets
`h1,h2,h3,h4,h5 { font-family: Lato }` and loads after Mantine's element rule,
so headings ignored the theme's body face. Only visible in amber, where the
body is mono and the headings were not. A class-level restatement outranks it;
the rule comes out when Semantic does.
Verified: 925 jest tests, clean production build, and a night-mode audit of
every painted pixel in the gallery (red-only, one known false positive). The
Configs backup and restore flow was exercised against the real API.
2026-07-29 20:25:34 -06:00
|
|
|
// MantineProvider, configured exactly as ThemeProvider configures it: components
|
|
|
|
|
// from src/components/ui render Mantine internals and need it in the tree.
|
2025-10-29 22:16:33 -06:00
|
|
|
const content = (
|
|
|
|
|
<BrowserRouter>
|
ui: migrate the extension pages and Configs; five components they needed
Phase 2, part 1. Three files leave Semantic UI (baseline 75 -> 72), and the
components they needed join the library and the gallery:
Header Semantic's shape (as/icon/subheader/dividing). 47 files use
<Header>, and the level now picks the size, so the type scale
stays a scale instead of each call site choosing a font size.
Message gains an icon and a dismiss button.
ActionInput input and button sharing one outline, for <Input action={...}/>.
Loading centered loader with a caption, replacing the
<Loader active inline='centered'> + wrapper div in 19 places.
Modal becomes compound - Modal.Header/.Content/.Actions, plus
Semantic's open, closeIcon, and size names - because 34 files
are written against that shape.
test-utils mounts MantineProvider with ThemeProvider's exact configuration:
migrated components render Mantine internals and every one of their test files
would otherwise need its own provider.
One Semantic bleed found and fixed: semantic.min.css sets
`h1,h2,h3,h4,h5 { font-family: Lato }` and loads after Mantine's element rule,
so headings ignored the theme's body face. Only visible in amber, where the
body is mono and the headings were not. A class-level restatement outranks it;
the rule comes out when Semantic does.
Verified: 925 jest tests, clean production build, and a night-mode audit of
every painted pixel in the gallery (red-only, one known false positive). The
Configs backup and restore flow was exercised against the real API.
2026-07-29 20:25:34 -06:00
|
|
|
<MantineProvider
|
|
|
|
|
theme={mantineTheme}
|
|
|
|
|
cssVariablesResolver={cssVariablesResolver}
|
|
|
|
|
forceColorScheme={inverted ? 'dark' : 'light'}
|
|
|
|
|
>
|
|
|
|
|
<MockFileWorkerStatusProvider>
|
|
|
|
|
<ThemeContext.Provider value={defaultThemeContext}>
|
|
|
|
|
{children}
|
|
|
|
|
</ThemeContext.Provider>
|
|
|
|
|
</MockFileWorkerStatusProvider>
|
|
|
|
|
</MantineProvider>
|
2025-10-29 22:16:33 -06:00
|
|
|
</BrowserRouter>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Only wrap with MediaContextProvider if explicitly requested
|
|
|
|
|
// (since it requires window.matchMedia which can be tricky in tests)
|
|
|
|
|
if (withMedia) {
|
|
|
|
|
return <MediaContextProvider>{content}</MediaContextProvider>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return content;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return render(ui, {wrapper: Wrapper, ...renderOptions});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates a mock domain collection object for testing
|
|
|
|
|
*/
|
|
|
|
|
export function createMockDomain(overrides = {}) {
|
|
|
|
|
return {
|
|
|
|
|
id: 1,
|
|
|
|
|
domain: 'example.com',
|
|
|
|
|
archive_count: 42,
|
|
|
|
|
size: 1024000,
|
|
|
|
|
tag_name: null,
|
|
|
|
|
directory: '',
|
|
|
|
|
can_be_tagged: false,
|
|
|
|
|
description: '',
|
|
|
|
|
...overrides
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates multiple mock domains for list testing
|
|
|
|
|
*/
|
|
|
|
|
export function createMockDomains(count = 3) {
|
|
|
|
|
return Array.from({length: count}, (_, i) => createMockDomain({
|
|
|
|
|
id: i + 1,
|
|
|
|
|
domain: `example${i + 1}.com`,
|
|
|
|
|
archive_count: (i + 1) * 10,
|
|
|
|
|
size: (i + 1) * 1000000,
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Mock fetch implementation for API calls
|
|
|
|
|
*/
|
|
|
|
|
export function mockFetch(data, options = {}) {
|
|
|
|
|
const {
|
|
|
|
|
ok = true,
|
|
|
|
|
status = 200,
|
|
|
|
|
delay = 0,
|
|
|
|
|
} = options;
|
|
|
|
|
|
|
|
|
|
return jest.fn(() =>
|
|
|
|
|
new Promise((resolve) => {
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
resolve({
|
|
|
|
|
ok,
|
|
|
|
|
status,
|
|
|
|
|
json: async () => data,
|
|
|
|
|
text: async () => JSON.stringify(data),
|
|
|
|
|
});
|
|
|
|
|
}, delay);
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Mock API error response
|
|
|
|
|
*/
|
|
|
|
|
export function mockFetchError(error = 'An error occurred', status = 400) {
|
|
|
|
|
return jest.fn(() =>
|
|
|
|
|
Promise.resolve({
|
|
|
|
|
ok: false,
|
|
|
|
|
status,
|
|
|
|
|
json: async () => ({error}),
|
|
|
|
|
text: async () => error,
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Wait for async updates to complete
|
|
|
|
|
* Useful for testing loading states
|
|
|
|
|
*/
|
|
|
|
|
export async function waitForLoadingToFinish() {
|
|
|
|
|
const {waitFor} = await import('@testing-library/react');
|
2025-12-06 14:49:46 -07:00
|
|
|
await waitFor(() => {
|
|
|
|
|
}, {timeout: 100});
|
2025-10-29 22:16:33 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test helper to render components in dark mode
|
|
|
|
|
*
|
|
|
|
|
* Usage:
|
|
|
|
|
* renderInDarkMode(<MyComponent />)
|
|
|
|
|
*
|
|
|
|
|
* Verify inverted styling is applied:
|
|
|
|
|
* const element = container.querySelector('.ui.segment.inverted');
|
|
|
|
|
* expect(element).toBeInTheDocument();
|
|
|
|
|
*/
|
|
|
|
|
export function renderInDarkMode(ui, options = {}) {
|
|
|
|
|
return renderWithProviders(ui, {
|
|
|
|
|
inverted: true,
|
|
|
|
|
...options
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test helper to render components in light mode
|
|
|
|
|
* (This is the default, but provided for explicitness in tests)
|
|
|
|
|
*/
|
|
|
|
|
export function renderInLightMode(ui, options = {}) {
|
|
|
|
|
return renderWithProviders(ui, {
|
|
|
|
|
inverted: false,
|
|
|
|
|
...options
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Helper to check if an element has theme-aware (inverted) styling
|
|
|
|
|
* Returns true if the element has the 'inverted' class
|
|
|
|
|
*
|
|
|
|
|
* Usage:
|
|
|
|
|
* const segment = container.querySelector('.ui.segment');
|
|
|
|
|
* expect(hasInvertedStyling(segment)).toBe(true);
|
|
|
|
|
*/
|
|
|
|
|
export function hasInvertedStyling(element) {
|
|
|
|
|
return element && element.classList.contains('inverted');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates a test-friendly form object using real useForm hook
|
|
|
|
|
*
|
|
|
|
|
* This uses the actual useForm implementation, making tests more reliable.
|
|
|
|
|
* The form starts in a "ready" state with the provided data.
|
|
|
|
|
*
|
|
|
|
|
* Usage:
|
|
|
|
|
* const form = createTestForm({domain: 'test.com', directory: '/path'});
|
|
|
|
|
* render(<CollectionEditForm form={form} metadata={metadata} />);
|
|
|
|
|
*
|
|
|
|
|
* // With overrides:
|
|
|
|
|
* const form = createTestForm(data, {overrides: {loading: true, disabled: true}});
|
|
|
|
|
*/
|
|
|
|
|
export function createTestForm(initialData = {}, config = {}) {
|
|
|
|
|
// Return a plain object that mimics the useForm interface without actual React hooks
|
|
|
|
|
// This avoids async state updates that cause act() warnings in tests
|
|
|
|
|
const _ = require('lodash');
|
|
|
|
|
const formData = {...initialData};
|
|
|
|
|
|
|
|
|
|
const setValue = (path, newValue) => {
|
|
|
|
|
_.set(formData, path, newValue);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const form = {
|
|
|
|
|
formData,
|
|
|
|
|
ready: true,
|
|
|
|
|
loading: false,
|
|
|
|
|
disabled: false,
|
|
|
|
|
dirty: false,
|
2026-07-16 19:40:28 -06:00
|
|
|
error: null,
|
2025-10-29 22:16:33 -06:00
|
|
|
errors: {},
|
|
|
|
|
// Methods that update formData directly
|
|
|
|
|
patchFormData: jest.fn((updates) => {
|
|
|
|
|
Object.assign(formData, updates);
|
|
|
|
|
}),
|
|
|
|
|
reset: jest.fn(() => {
|
|
|
|
|
Object.keys(formData).forEach(key => delete formData[key]);
|
|
|
|
|
Object.assign(formData, initialData);
|
|
|
|
|
}),
|
|
|
|
|
onSubmit: config.submitter || jest.fn(async () => initialData),
|
|
|
|
|
// Input helpers
|
|
|
|
|
setError: jest.fn(),
|
|
|
|
|
setValidator: jest.fn(),
|
|
|
|
|
setValidValue: jest.fn(),
|
|
|
|
|
setRequired: jest.fn(),
|
|
|
|
|
// Property getter for field values
|
|
|
|
|
get: (path) => _.get(formData, path),
|
|
|
|
|
setValue: jest.fn((path, value) => setValue(path, value)),
|
|
|
|
|
// getCustomProps - mimics the real useForm method
|
|
|
|
|
getCustomProps: ({name, path, required = false, type = 'text'}) => {
|
|
|
|
|
path = path || name;
|
|
|
|
|
const value = _.get(formData, path);
|
|
|
|
|
|
|
|
|
|
const inputProps = {
|
|
|
|
|
type,
|
|
|
|
|
disabled: form.disabled,
|
|
|
|
|
value: value !== undefined ? value : (type === 'array' ? [] : null),
|
|
|
|
|
onChange: (newValue) => {
|
|
|
|
|
setValue(path, newValue);
|
|
|
|
|
},
|
|
|
|
|
'data-path': path,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const inputAttrs = {
|
|
|
|
|
valid: true,
|
|
|
|
|
path,
|
|
|
|
|
localSetValue: (newValue) => setValue(path, newValue),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return [inputProps, inputAttrs];
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Apply any overrides
|
|
|
|
|
if (config.overrides) {
|
|
|
|
|
Object.assign(form, config.overrides);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return form;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Re-export everything from React Testing Library
|
|
|
|
|
export * from '@testing-library/react';
|
|
|
|
|
|
|
|
|
|
// Override the default render with our custom one
|
|
|
|
|
export {renderWithProviders as render};
|