Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

### Internal

- Migrate JSX files to TypeScript and remove their ESLint filename suppressions ([#82132](https://github.com/WordPress/gutenberg/pull/82132)).
- Use the `.jsx` extension for JavaScript source files that contain JSX ([#80990](https://github.com/WordPress/gutenberg/pull/80990)).
- Remove tsconfig project references to packages that are not dependencies ([#82106](https://github.com/WordPress/gutenberg/pull/82106)).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { BorderControl } from '../';
import type { Border, BorderControlProps } from '../types';
import { COLORS } from '../../utils';

const colors = [
Expand All @@ -23,11 +24,13 @@ const defaultBorder = {
width: '1px',
};

function createProps( customProps ) {
const props = {
function createProps(
customProps: Partial< BorderControlProps > = {}
): BorderControlProps {
const props: BorderControlProps = {
colors,
label: 'Border',
onChange: jest.fn().mockImplementation( ( newValue ) => {
onChange: jest.fn().mockImplementation( ( newValue?: Border ) => {
props.value = newValue;
} ),
value: defaultBorder,
Expand All @@ -38,7 +41,7 @@ function createProps( customProps ) {

const toggleLabelRegex = /Border color( and style)* picker/;

const openPopover = async ( user ) => {
const openPopover = async ( user: ReturnType< typeof userEvent.setup > ) => {
const toggleButton = screen.getByLabelText( toggleLabelRegex );
await user.click( toggleButton );

Expand All @@ -50,27 +53,31 @@ const openPopover = async ( user ) => {
await waitFor( () => expect( pickerButton ).toBePositionedPopover() );
};

const getButton = ( name ) => {
const getButton = ( name: string | RegExp ) => {
return screen.getByRole( 'button', { name } );
};

const getColorOption = ( color ) => {
const getColorOption = ( color: string ) => {
return screen.getByRole( 'option', { name: `${ color }` } );
};

const queryButton = ( name ) => {
const queryButton = ( name: string | RegExp ) => {
return screen.queryByRole( 'button', { name } );
};

const getSliderInput = () => {
return screen.getByRole( 'slider', { name: 'Border width' } );
return screen.getByRole< HTMLInputElement >( 'slider', {
name: 'Border width',
} );
};

const getWidthInput = () => {
return screen.getByRole( 'spinbutton', { name: 'Border width' } );
return screen.getByRole< HTMLInputElement >( 'spinbutton', {
name: 'Border width',
} );
};

function TestBorderControl( restProps ) {
function TestBorderControl( restProps: BorderControlProps ) {
return <BorderControl { ...restProps } />;
}

Expand All @@ -80,7 +87,7 @@ describe( 'BorderControl', () => {
const props = createProps();
render( <TestBorderControl { ...props } /> );

const label = screen.getByText( props.label );
const label = screen.getByText( props.label! );
const colorButton = screen.getByLabelText( toggleLabelRegex );
const widthInput = getWidthInput();
const unitSelect = screen.getByRole( 'combobox', {
Expand All @@ -100,7 +107,7 @@ describe( 'BorderControl', () => {
it( 'should hide label', () => {
const props = createProps( { hideLabelFromVision: true } );
render( <TestBorderControl { ...props } /> );
const label = screen.getByText( props.label );
const label = screen.getByText( props.label! );

// As visually hidden labels are still included in the document
// and do not have `display: none` styling, we can't rely on
Expand Down Expand Up @@ -128,7 +135,7 @@ describe( 'BorderControl', () => {
} );

describe( 'color indicator inline styles', () => {
const getIndicatorWrapper = ( border ) => {
const getIndicatorWrapper = ( border: Border ) => {
render(
<TestBorderControl
{ ...createProps( { value: border } ) }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import deepmerge from 'deepmerge';
import fastDeepEqual from 'fast-deep-equal/es6/index.js';
import { isPlainObject } from 'is-plain-object';
import type { ReactNode } from 'react';
import {
createContext,
useContext,
Expand All @@ -11,9 +12,12 @@ import {
import warn from '@wordpress/warning';
import { useUpdateEffect } from '../utils';

export const ComponentsContext = createContext(
/** @type {Record<string, any>} */ ( {} )
);
type ContextSystemValue = Record<
string,
Record< string, unknown > | undefined
>;

export const ComponentsContext = createContext< ContextSystemValue >( {} );
ComponentsContext.displayName = 'ComponentsContext';

export const useComponentsContext = () => useContext( ComponentsContext );
Expand All @@ -27,7 +31,11 @@ export const useComponentsContext = () => useContext( ComponentsContext );
* @param {Record<string, any>} props.value
* @return {Record<string, any>} The consolidated value.
*/
function useContextSystemBridge( { value } ) {
function useContextSystemBridge( {
value,
}: {
value?: ContextSystemValue;
} ): ContextSystemValue {
const parentContext = useComponentsContext();

const valueRef = useRef( value );
Expand Down Expand Up @@ -82,7 +90,13 @@ function useContextSystemBridge( { value } ) {
* @param {T} options.value Props to render into connected components.
* @return {React.JSX.Element} A Provider wrapped component.
*/
const BaseContextSystemProvider = ( { children, value } ) => {
const BaseContextSystemProvider = ( {
children,
value,
}: {
children: ReactNode;
value?: ContextSystemValue;
} ) => {
const contextValue = useContextSystemBridge( { value } );

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { View } from '../../view';
import { Text } from '../../text';
import { ContextSystemProvider } from '../index';

const SomeContext = createContext();
const SomeContext = createContext< number | undefined >( undefined );
const useSomeContext = () => useContext( SomeContext );

export default {
Expand Down Expand Up @@ -39,7 +39,7 @@ const innerContext = {

const InnerContent = memo( () => {
const state = useSomeContext();
const isEven = state % 2 === 0;
const isEven = state !== undefined && state % 2 === 0;
return (
<View style={ { background: isEven ? 'red' : 'initial' } }>
<Text>Card (inside innerContext)</Text>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
exports[`props should render _override props 1`] = `
<div>
<div
class="components-component test-component css-1c3028l-View emotion-0"
class="components-component test-component css-52wcqg-View emotion-0"
data-wp-c16t="true"
data-wp-component="Component"
>
Expand All @@ -15,7 +15,7 @@ exports[`props should render _override props 1`] = `
exports[`props should render context props 1`] = `
<div>
<div
class="components-component css-1c3028l-View emotion-0"
class="components-component css-52wcqg-View emotion-0"
data-wp-c16t="true"
data-wp-component="Component"
>
Expand All @@ -27,7 +27,7 @@ exports[`props should render context props 1`] = `
exports[`props should render correctly 1`] = `
<div>
<div
class="components-component css-1c3028l-View emotion-0"
class="components-component css-52wcqg-View emotion-0"
data-wp-c16t="true"
data-wp-component="Component"
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,65 @@
import { render, screen } from '@testing-library/react';
import styled from '@emotion/styled';
import type {
ComponentPropsWithoutRef,
ForwardedRef,
ReactElement,
ReactNode,
} from 'react';
import { cloneElement } from '@wordpress/element';
import warn from '@wordpress/warning';
import { contextConnect } from '../context-connect';
import { ContextSystemProvider } from '../context-system-provider';
import { useContextSystem } from '../use-context-system';

jest.mock( '@wordpress/warning', () => jest.fn() );

const View = styled.div``;

type TestComponentProps = ComponentPropsWithoutRef< 'div' > & {
quote?: ReactNode;
};

function TestComponent(
props: TestComponentProps,
ref: ForwardedRef< HTMLDivElement >
) {
return <View { ...useContextSystem( props, 'Component' ) } ref={ ref } />;
}

function TestComponentWithQuote(
props: TestComponentProps,
ref: ForwardedRef< HTMLDivElement >
) {
const { quote, ...otherProps } = useContextSystem( props, 'Component' );
return (
<View { ...otherProps } ref={ ref }>
{ quote }
</View>
);
}

describe( 'props', () => {
test( 'should render correctly', () => {
const Component = ( props, ref ) => (
<View { ...useContextSystem( props, 'Component' ) } ref={ ref } />
test( 'should not warn when rerendered without a value', () => {
jest.mocked( warn ).mockClear();

const { rerender } = render(
<ContextSystemProvider>
<div />
</ContextSystemProvider>
);
const ConnectedComponent = contextConnect( Component, 'Component' );

rerender(
<ContextSystemProvider>
<div />
</ContextSystemProvider>
);

expect( warn ).not.toHaveBeenCalled();
} );

test( 'should render correctly', () => {
const ConnectedComponent = contextConnect( TestComponent, 'Component' );
const { container } = render(
<ContextSystemProvider>
<ConnectedComponent />
Expand All @@ -23,19 +70,10 @@ describe( 'props', () => {
} );

test( 'should render context props', () => {
const Component = ( props, ref ) => {
const { quote, ...otherProps } = useContextSystem(
props,
'Component'
);
return (
<View { ...otherProps } ref={ ref }>
{ quote }
</View>
);
};

const ConnectedComponent = contextConnect( Component, 'Component' );
const ConnectedComponent = contextConnect(
TestComponentWithQuote,
'Component'
);

const contextValue = {
Component: {
Expand All @@ -54,19 +92,10 @@ describe( 'props', () => {
} );

test( 'should render _override props', () => {
const Component = ( props, ref ) => {
const { quote, ...otherProps } = useContextSystem(
props,
'Component'
);
return (
<View { ...otherProps } ref={ ref }>
{ quote }
</View>
);
};

const ConnectedComponent = contextConnect( Component, 'Component' );
const ConnectedComponent = contextConnect(
TestComponentWithQuote,
'Component'
);

const contextValue = {
Component: {
Expand Down Expand Up @@ -99,10 +128,7 @@ describe( 'props', () => {

describe( 'children', () => {
test( 'should pass through children', () => {
const Component = ( props, ref ) => (
<View { ...useContextSystem( props, 'Component' ) } ref={ ref } />
);
const ConnectedComponent = contextConnect( Component, 'Component' );
const ConnectedComponent = contextConnect( TestComponent, 'Component' );

render(
<ContextSystemProvider>
Expand All @@ -114,13 +140,11 @@ describe( 'children', () => {
} );

test( 'should not accept children via `context`', () => {
const Component = ( props, ref ) => (
<View { ...useContextSystem( props, 'Component' ) } ref={ ref } />
);
const ConnectedComponent = contextConnect( Component, 'Component' );
const ConnectedComponent = contextConnect( TestComponent, 'Component' );

render(
<ContextSystemProvider
// @ts-expect-error Verify that the unsupported `context` prop is ignored.
context={ { Component: { children: 'Override' } } }
>
<ConnectedComponent />
Expand All @@ -132,13 +156,18 @@ describe( 'children', () => {

// This matches the behavior for normal, non-context-connected components.
test( 'should not override inherent children', () => {
const Component = ( props, ref ) => (
const Component = (
props: TestComponentProps,
ref: ForwardedRef< HTMLDivElement >
) => (
<View { ...useContextSystem( props, 'Component' ) } ref={ ref }>
Inherent
</View>
);
const ConnectedComponent = contextConnect( Component, 'Component' );
const NormalComponent = ( props ) => <div { ...props }>Inherent</div>;
const NormalComponent = (
props: ComponentPropsWithoutRef< 'div' >
) => <div { ...props }>Inherent</div>;

render(
<ContextSystemProvider>
Expand All @@ -153,12 +182,21 @@ describe( 'children', () => {
} );

describe( 'when connected component does a `cloneElement()`', () => {
// eslint-disable-next-line no-unused-vars
const ComponentThatClones = ( { content, ...props }, _ref ) =>
cloneElement(
type CloneComponentProps = {
content: ReactElement;
children?: ReactNode;
className?: string;
};
const ComponentThatClones = (
{ content, ...props }: CloneComponentProps,
ref: ForwardedRef< HTMLElement >
) => {
void ref;
return cloneElement(
content,
useContextSystem( props, 'ComponentThatClones' )
);
};
const ConnectedComponentThatClones = contextConnect(
ComponentThatClones,
'ComponentThatClones'
Expand Down
Loading
Loading