Skip to content

Commit

Permalink
Merge branch 'trunk' into ci/update-concurrency-group-for-pr-label
Browse files Browse the repository at this point in the history
  • Loading branch information
adimoldovan committed Mar 22, 2024
2 parents eb9b51e + 88834ff commit 64e440e
Show file tree
Hide file tree
Showing 61 changed files with 1,797 additions and 645 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
name: Run tests against PR in an environment with HPOS disabled
on:
pull_request:
push:
branches:
- 'trunk'
- 'release/*'
workflow_dispatch:

concurrency:
Expand Down Expand Up @@ -66,20 +69,6 @@ jobs:
retention-days: 1
compression-level: 9

e2e-tests-success:
name: Evaluate e2e tests results
runs-on: ubuntu-latest
needs: non-hpos-e2e-tests-run
if: ${{ always() }}
steps:
- run: |
result="${{ needs.non-hpos-e2e-tests-run.result }}"
if [[ $result != "success" && $result != "skipped" ]]; then
echo "One or more e2e tests have failed!"
exit 1
fi
echo "e2e tests have completed successfully."
merge-reports:
name: Merge e2e test reports
# Merge reports after playwright-tests, even if some shards have failed
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: add

[Product Block Editor]: document woocommerce/product-text-area-field field block
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: add

[Product Block Editor]: introduce `woocommerce/entity-product` binding source handler
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* External dependencies
*/
import { useEntityProp } from '@wordpress/core-data';
import { __ } from '@wordpress/i18n';
import { useCallback } from '@wordpress/element';

/**
* Internal dependencies
*/
import type {
BindingSourceHandlerProps,
BindingUseSourceProps,
BlockProps,
} from '../../../bindings/types';
import type { WooCommerceEntityProductSourceArgs } from './types';

/**
* React custom hook to bind a source to a block.
*
* @param {BlockProps} blockProps - The block props.
* @param {WooCommerceEntityProductSourceArgs} sourceArgs - The source args.
* @return {BindingUseSourceProps} The source value and setter.
*/
const useSource = (
blockProps: BlockProps,
sourceArgs: WooCommerceEntityProductSourceArgs
): BindingUseSourceProps => {
if ( typeof sourceArgs === 'undefined' ) {
throw new Error( 'The "args" argument is required.' );
}

if ( ! sourceArgs?.prop ) {
throw new Error( 'The "prop" argument is required.' );
}

const { prop, id } = sourceArgs;

const [ value, updateValue ] = useEntityProp(
'postType',
'product',
prop,
id
);

const updateValueHandler = useCallback(
( nextEntityPropValue: string ) => {
updateValue( nextEntityPropValue );
},
[ updateValue ]
);

return {
placeholder: null,
value,
updateValue: updateValueHandler,
};
};

/*
* Create the product-entity
* block binding source handler.
*
* source ID: `woocommerce/entity-product`
* args:
* - prop: The name of the entity property to bind.
*
* In the example below,
* the `content` attribute is bound to the `short_description` property.
* `product` entity and `postType` kind are defined by the context.
*
* ```
* metadata: {
* bindings: {
* content: {
* source: 'woocommerce/entity-product',
* args: {
* prop: 'short_description',
* },
* },
* },
* ```
*/
export default {
name: 'woocommerce/entity-product',
label: __( 'Product Entity', 'woocommerce' ),
useSource,
lockAttributesEditing: true,
} as BindingSourceHandlerProps< WooCommerceEntityProductSourceArgs >;
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* External dependencies
*/
import { renderHook } from '@testing-library/react-hooks';

/**
* Internal dependencies
*/
import productEntitySourceHandler from '..';

let mockState;

jest.mock( '@wordpress/core-data', () => ( {
useEntityProp: jest.fn().mockImplementation( ( kind, name, key ) => {
return mockState?.[ key ] ? mockState[ key ] : [];
} ),
} ) );

describe( 'useSource', () => {
let blockInstance;

mockState = {
external_property_name: [ 'External source property Value' ],
};

beforeEach( () => {
blockInstance = {
name: 'woocommerce/block-with-entity',
attributes: {
prop: 'value',
},
className: 'wp-block-woocommerce-block-with-entity',
context: {},
clientId: '<client-id-instance>',
isSelected: false,
setAttributes: jest.fn(),
};
} );

it( 'throws an error if sourceArgs is undefined', () => {
const { useSource } = productEntitySourceHandler;
const { result } = renderHook( () =>
useSource( blockInstance, undefined )
);

expect( result.error ).toEqual(
new Error( 'The "args" argument is required.' )
);
} );

it( 'throws an error if prop in sourceArgs is undefined', () => {
const { useSource } = productEntitySourceHandler;
const { result } = renderHook( () =>
useSource( blockInstance, { prop: undefined } )
);

expect( result.error ).toEqual(
new Error( 'The "prop" argument is required.' )
);
} );

it( 'return the value of the product entity property', () => {
const { useSource } = productEntitySourceHandler;
const { result } = renderHook( () =>
useSource( blockInstance, {
prop: 'external_property_name',
} )
);

expect( result.current.value ).toEqual(
'External source property Value'
);
} );
} );
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type WooCommerceEntityProductSourceArgs = {
/*
* The name of the entity property to bind.
*/
prop: string;

/*
* The ID of the entity to bind.
*/
id?: string;
};
77 changes: 77 additions & 0 deletions packages/js/product-editor/src/bindings/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* External dependencies
*/
import type { BlockEditProps, BlockAttributes } from '@wordpress/blocks';
import {
// @ts-expect-error no exported member.
type ComponentType,
} from '@wordpress/element';

export type AttributeBindingProps = {
source: string;
args: { prop: string };
};

export type MetadataBindingsProps = Record< string, AttributeBindingProps >;

export type BoundBlockAttributes = BlockAttributes & {
metadata?: {
bindings: MetadataBindingsProps;
};
};

export type BoundBlockEditInstance = CoreBlockEditProps< BoundBlockAttributes >;
export type BoundBlockEditComponent = ComponentType< BoundBlockEditInstance >;

/*
* Block Binding API
*/
export type BindingUseSourceProps = {
/*
* The placeholder value for the source.
*/
placeholder: string | null;
/*
* The value of the source.
*/
value: any; // eslint-disable-line @typescript-eslint/no-explicit-any
/*
* Callback function to set the source value.
*/
updateValue: ( newValue: any ) => void; // eslint-disable-line @typescript-eslint/no-explicit-any
};

export interface BindingSourceHandlerProps< T > {
/*
* The name of the binding source handler.
*/
name: string;

/*
* The human-readable label for the binding source handler.
*/
label: string;

/*
* React custom hook to bind a source to a block.
*/
useSource: (
blockProps: CoreBlockEditProps< BlockAttributes >,
sourceArgs: T
) => BindingUseSourceProps;

lockAttributesEditing: boolean;
}

/*
* Core Types
*/

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export interface CoreBlockEditProps< T extends Record< string, any > >
extends BlockEditProps< T > {
readonly name: string;
readonly context: Record< string, string >;
}

export type BlockProps = CoreBlockEditProps< BlockAttributes >;
102 changes: 102 additions & 0 deletions packages/js/product-editor/src/blocks/generic/text-area/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# woocommerce/product-text-area-field

A reusable text area field for the product editor, enhancing product data input with a multiline text area suitable for detailed information, descriptions, and more.

## Attributes

### label

- **Type:** `String`
- **Required:** `Yes`

The label appears above the textarea field.

### property

- **Type:** `String`
- **Required:** `Yes`

The product entity property where the value is stored.

### help

- **Type:** `String`
- **Required:** `No`

Help text that appears below the field, providing additional guidance to the user.

### required

- **Type:** `Boolean`|`String`
- **Required:** `No`

Indicates that the field is required.

### tooltip

- **Type:** `String`
- **Required:** `No`

Shows a tooltip next to the label with additional information when provided.

### placeholder

- **Type:** `String`
- **Required:** `No`

Placeholder text that appears within the textarea when it is empty.

### mode

- **Type:** `String`
- **Required:** `No`
- **Default:** `'rich-text'`

Defines the editing mode of the textarea. Options are `'plain-text'` or `'rich-text'`.

### allowedFormats

- **Type:** `Array`
- **Required:** `No`
- **Default:** `['core/bold', 'core/italic', 'core/link', etc.]`

Specifies the allowed formatting options when in 'rich-text' mode.

### direction

- **Type:** `String`
- **Required:** `No`
- **Default:** `'ltr'`
- **Options:** `'ltr'`, `'rtl'`

The text directionality for the textarea, supporting left-to-right (ltr) or right-to-left (rtl) content.

### align

- **Type:** `String`
- **Required:** `No`
- **Options:** `'left'`, `'center'`, `'right'`, `'justify'`

Aligns the text within the text area field according to the specified value.

## Usage

Here's a snippet that adds a text area field to the product editor, similar to the screenshot:

```php
$section->add_block(
array(
'id' => 'example-text-area-meta',
'blockName' => 'woocommerce/product-text-area-field',
'order' => 15,
'attributes' => array(
'label' => 'Detailed Description',
'property' => 'short_description',
'placeholder' => 'Enter a short product information here',
'required' => false,
'help' => 'Add any additional details or information that customers should know.',
'mode' => 'rich-text',
'allowedFormats' => [ 'core/bold', 'core/italic', 'core/link' ],
),
)
);
Loading

0 comments on commit 64e440e

Please sign in to comment.