HEX
Server: LiteSpeed
System: Linux eko108.isimtescil.net 4.18.0-477.21.1.lve.1.el8.x86_64 #1 SMP Tue Sep 5 23:08:35 UTC 2023 x86_64
User: uyarreklamcomtr (11202)
PHP: 7.4.33
Disabled: opcache_get_status
Upload Files
File: /var/www/vhosts/uyarreklam.com.tr/httpdocs/hocs.tar
index.js000064400000001166151550047500006217 0ustar00export { default as withAttributes } from './with-attributes';
export { default as withCategories } from './with-categories';
export { default as withCategory } from './with-category';
export { default as withProduct } from './with-product';
export { default as withProductVariations } from './with-product-variations';
export { default as withSearchedProducts } from './with-searched-products';
export { default as withTransformSingleSelectToMultipleSelect } from './with-transform-single-select-to-multiple-select';
export {
	SelectedOption,
	WithMaybeSelectedOption,
} from './with-transform-single-select-to-multiple-select';
test/with-categories.js000064400000005554151550047500011172 0ustar00// We need to disable the following eslint check as it's only applicable
// to testing-library/react not `react-test-renderer` used here
/* eslint-disable testing-library/await-async-query */

/**
 * External dependencies
 */
import TestRenderer from 'react-test-renderer';
import * as mockUtils from '@woocommerce/editor-components/utils';

/**
 * Internal dependencies
 */
import withCategories from '../with-categories';
import * as mockBaseUtils from '../../base/utils/errors';

jest.mock( '@woocommerce/editor-components/utils', () => ( {
	getCategories: jest.fn(),
} ) );

jest.mock( '../../base/utils/errors', () => ( {
	formatError: jest.fn(),
} ) );

const mockCategories = [
	{ id: 1, name: 'Clothing' },
	{ id: 2, name: 'Food' },
];
const TestComponent = withCategories( ( props ) => {
	return (
		<div
			data-error={ props.error }
			data-isLoading={ props.isLoading }
			data-categories={ props.categories }
		/>
	);
} );
const render = () => {
	return TestRenderer.create( <TestComponent /> );
};

describe( 'withCategories Component', () => {
	let renderer;
	afterEach( () => {
		mockUtils.getCategories.mockReset();
	} );

	describe( 'lifecycle events', () => {
		beforeEach( () => {
			mockUtils.getCategories.mockImplementation( () =>
				Promise.resolve()
			);
			renderer = render();
		} );

		it( 'getCategories is called on mount', () => {
			const { getCategories } = mockUtils;
			expect( getCategories ).toHaveBeenCalledTimes( 1 );
		} );
	} );

	describe( 'when the API returns categories data', () => {
		beforeEach( () => {
			mockUtils.getCategories.mockImplementation( () =>
				Promise.resolve( mockCategories )
			);
			renderer = render();
		} );

		it( 'sets the categories props', () => {
			const props = renderer.root.findByType( 'div' ).props;

			expect( props[ 'data-error' ] ).toBeNull();
			expect( props[ 'data-isLoading' ] ).toBe( false );
			expect( props[ 'data-categories' ] ).toEqual( mockCategories );
		} );
	} );

	describe( 'when the API returns an error', () => {
		const error = { message: 'There was an error.' };
		const getCategoriesPromise = Promise.reject( error );
		const formattedError = { message: 'There was an error.', type: 'api' };

		beforeEach( () => {
			mockUtils.getCategories.mockImplementation(
				() => getCategoriesPromise
			);
			mockBaseUtils.formatError.mockImplementation(
				() => formattedError
			);
			renderer = render();
		} );

		test( 'sets the error prop', async () => {
			await expect( () => getCategoriesPromise() ).toThrow();

			const { formatError } = mockBaseUtils;
			const props = renderer.root.findByType( 'div' ).props;

			expect( formatError ).toHaveBeenCalledWith( error );
			expect( formatError ).toHaveBeenCalledTimes( 1 );
			expect( props[ 'data-error' ] ).toEqual( formattedError );
			expect( props[ 'data-isLoading' ] ).toBe( false );
			expect( props[ 'data-categories' ] ).toEqual( [] );
		} );
	} );
} );
test/with-category.js000064400000007441151550047500010657 0ustar00// We need to disable the following eslint check as it's only applicable
// to testing-library/react not `react-test-renderer` used here
/* eslint-disable testing-library/await-async-query */
/**
 * External dependencies
 */
import TestRenderer from 'react-test-renderer';
import * as mockUtils from '@woocommerce/editor-components/utils';

/**
 * Internal dependencies
 */
import withCategory from '../with-category';
import * as mockBaseUtils from '../../base/utils/errors';

jest.mock( '@woocommerce/editor-components/utils', () => ( {
	getCategory: jest.fn(),
} ) );

jest.mock( '../../base/utils/errors', () => ( {
	formatError: jest.fn(),
} ) );

const mockCategory = { name: 'Clothing' };
const attributes = { categoryId: 1 };
const TestComponent = withCategory( ( props ) => {
	return (
		<div
			data-error={ props.error }
			data-getCategory={ props.getCategory }
			data-isLoading={ props.isLoading }
			data-category={ props.category }
		/>
	);
} );
const render = () => {
	return TestRenderer.create( <TestComponent attributes={ attributes } /> );
};

describe( 'withCategory Component', () => {
	let renderer;
	afterEach( () => {
		mockUtils.getCategory.mockReset();
	} );

	describe( 'lifecycle events', () => {
		beforeEach( () => {
			mockUtils.getCategory.mockImplementation( () => Promise.resolve() );
			renderer = render();
		} );

		it( 'getCategory is called on mount with passed in category id', () => {
			const { getCategory } = mockUtils;

			expect( getCategory ).toHaveBeenCalledWith( attributes.categoryId );
			expect( getCategory ).toHaveBeenCalledTimes( 1 );
		} );

		it( 'getCategory is called on component update', () => {
			const { getCategory } = mockUtils;
			const newAttributes = { ...attributes, categoryId: 2 };
			renderer.update( <TestComponent attributes={ newAttributes } /> );

			expect( getCategory ).toHaveBeenNthCalledWith(
				2,
				newAttributes.categoryId
			);
			expect( getCategory ).toHaveBeenCalledTimes( 2 );
		} );

		it( 'getCategory is hooked to the prop', () => {
			const { getCategory } = mockUtils;
			const props = renderer.root.findByType( 'div' ).props;

			props[ 'data-getCategory' ]();

			expect( getCategory ).toHaveBeenCalledTimes( 2 );
		} );
	} );

	describe( 'when the API returns category data', () => {
		beforeEach( () => {
			mockUtils.getCategory.mockImplementation( ( categoryId ) =>
				Promise.resolve( { ...mockCategory, id: categoryId } )
			);
			renderer = render();
		} );

		it( 'sets the category props', () => {
			const props = renderer.root.findByType( 'div' ).props;

			expect( props[ 'data-error' ] ).toBeNull();
			expect( typeof props[ 'data-getCategory' ] ).toBe( 'function' );
			expect( props[ 'data-isLoading' ] ).toBe( false );
			expect( props[ 'data-category' ] ).toEqual( {
				...mockCategory,
				id: attributes.categoryId,
			} );
		} );
	} );

	describe( 'when the API returns an error', () => {
		const error = { message: 'There was an error.' };
		const getCategoryPromise = Promise.reject( error );
		const formattedError = { message: 'There was an error.', type: 'api' };

		beforeEach( () => {
			mockUtils.getCategory.mockImplementation(
				() => getCategoryPromise
			);
			mockBaseUtils.formatError.mockImplementation(
				() => formattedError
			);
			renderer = render();
		} );

		test( 'sets the error prop', async () => {
			await expect( () => getCategoryPromise() ).toThrow();

			const { formatError } = mockBaseUtils;
			const props = renderer.root.findByType( 'div' ).props;

			expect( formatError ).toHaveBeenCalledWith( error );
			expect( formatError ).toHaveBeenCalledTimes( 1 );
			expect( props[ 'data-error' ] ).toEqual( formattedError );
			expect( typeof props[ 'data-getCategory' ] ).toBe( 'function' );
			expect( props[ 'data-isLoading' ] ).toBe( false );
			expect( props[ 'data-category' ] ).toBeNull();
		} );
	} );
} );
test/with-product-variations.js000064400000011723151550047500012675 0ustar00// We need to disable the following eslint check as it's only applicable
// to testing-library/react not `react-test-renderer` used here
/* eslint-disable testing-library/await-async-query */
/**
 * External dependencies
 */
import TestRenderer from 'react-test-renderer';
import * as mockUtils from '@woocommerce/editor-components/utils';

/**
 * Internal dependencies
 */
import withProductVariations from '../with-product-variations';
import * as mockBaseUtils from '../../base/utils/errors';

jest.mock( '@woocommerce/editor-components/utils', () => ( {
	getProductVariations: jest.fn(),
} ) );

jest.mock( '../../base/utils/errors', () => ( {
	formatError: jest.fn(),
} ) );

const mockProducts = [
	{ id: 1, name: 'Hoodie', variations: [ { id: 3 }, { id: 4 } ] },
	{ id: 2, name: 'Backpack' },
];
const mockVariations = [
	{ id: 3, name: 'Blue' },
	{ id: 4, name: 'Red' },
];
const TestComponent = withProductVariations( ( props ) => {
	return (
		<div
			data-error={ props.error }
			data-expandedProduct={ props.expandedProduct }
			data-isLoading={ props.isLoading }
			data-variations={ props.variations }
			data-variationsLoading={ props.variationsLoading }
		/>
	);
} );
const render = () => {
	return TestRenderer.create(
		<TestComponent
			error={ null }
			isLoading={ false }
			products={ mockProducts }
			selected={ [ 1 ] }
			showVariations={ true }
		/>
	);
};

describe( 'withProductVariations Component', () => {
	let renderer;
	afterEach( () => {
		mockUtils.getProductVariations.mockReset();
	} );

	describe( 'lifecycle events', () => {
		beforeEach( () => {
			mockUtils.getProductVariations.mockImplementation( () =>
				Promise.resolve( mockVariations )
			);
		} );

		it( 'getProductVariations is called on mount', () => {
			renderer = render();
			const { getProductVariations } = mockUtils;

			expect( getProductVariations ).toHaveBeenCalledWith( 1 );
			expect( getProductVariations ).toHaveBeenCalledTimes( 1 );
		} );

		it( 'getProductVariations is called on component update', () => {
			renderer = TestRenderer.create(
				<TestComponent
					error={ null }
					isLoading={ false }
					products={ mockProducts }
				/>
			);
			const { getProductVariations } = mockUtils;

			expect( getProductVariations ).toHaveBeenCalledTimes( 0 );

			renderer.update(
				<TestComponent
					error={ null }
					isLoading={ false }
					products={ mockProducts }
					selected={ [ 1 ] }
					showVariations={ true }
				/>
			);

			expect( getProductVariations ).toHaveBeenCalledWith( 1 );
			expect( getProductVariations ).toHaveBeenCalledTimes( 1 );
		} );

		it( 'getProductVariations is not called if selected product has no variations', () => {
			TestRenderer.create(
				<TestComponent
					error={ null }
					isLoading={ false }
					products={ mockProducts }
					selected={ [ 2 ] }
					showVariations={ true }
				/>
			);
			const { getProductVariations } = mockUtils;

			expect( getProductVariations ).toHaveBeenCalledTimes( 0 );
		} );

		it( 'getProductVariations is called if selected product is a variation', () => {
			TestRenderer.create(
				<TestComponent
					error={ null }
					isLoading={ false }
					products={ mockProducts }
					selected={ [ 3 ] }
					showVariations={ true }
				/>
			);
			const { getProductVariations } = mockUtils;

			expect( getProductVariations ).toHaveBeenCalledWith( 1 );
			expect( getProductVariations ).toHaveBeenCalledTimes( 1 );
		} );
	} );

	describe( 'when the API returns variations data', () => {
		beforeEach( () => {
			mockUtils.getProductVariations.mockImplementation( () =>
				Promise.resolve( mockVariations )
			);
			renderer = render();
		} );

		it( 'sets the variations props', () => {
			const props = renderer.root.findByType( 'div' ).props;
			const expectedVariations = {
				1: [
					{ id: 3, name: 'Blue', parent: 1 },
					{ id: 4, name: 'Red', parent: 1 },
				],
			};

			expect( props[ 'data-error' ] ).toBeNull();
			expect( props[ 'data-isLoading' ] ).toBe( false );
			expect( props[ 'data-variations' ] ).toEqual( expectedVariations );
		} );
	} );

	describe( 'when the API returns an error', () => {
		const error = { message: 'There was an error.' };
		const getProductVariationsPromise = Promise.reject( error );
		const formattedError = { message: 'There was an error.', type: 'api' };

		beforeEach( () => {
			mockUtils.getProductVariations.mockImplementation(
				() => getProductVariationsPromise
			);
			mockBaseUtils.formatError.mockImplementation(
				() => formattedError
			);
			renderer = render();
		} );

		test( 'sets the error prop', async () => {
			await expect( () => getProductVariationsPromise() ).toThrow();

			const { formatError } = mockBaseUtils;
			const props = renderer.root.findByType( 'div' ).props;

			expect( formatError ).toHaveBeenCalledWith( error );
			expect( formatError ).toHaveBeenCalledTimes( 1 );
			expect( props[ 'data-error' ] ).toEqual( formattedError );
			expect( props[ 'data-isLoading' ] ).toBe( false );
			expect( props[ 'data-variations' ] ).toEqual( { 1: null } );
		} );
	} );
} );
test/with-product.js000064400000007354151550047500010525 0ustar00// We need to disable the following eslint check as it's only applicable
// to testing-library/react not `react-test-renderer` used here
/* eslint-disable testing-library/await-async-query */
/**
 * External dependencies
 */
import TestRenderer from 'react-test-renderer';
import * as mockUtils from '@woocommerce/editor-components/utils';

/**
 * Internal dependencies
 */
import withProduct from '../with-product';
import * as mockBaseUtils from '../../base/utils/errors';

jest.mock( '@woocommerce/editor-components/utils', () => ( {
	getProduct: jest.fn(),
} ) );

jest.mock( '../../base/utils/errors', () => ( {
	formatError: jest.fn(),
} ) );

const mockProduct = { name: 'T-Shirt' };
const attributes = { productId: 1 };
const TestComponent = withProduct( ( props ) => {
	return (
		<div
			data-error={ props.error }
			data-getProduct={ props.getProduct }
			data-isLoading={ props.isLoading }
			data-product={ props.product }
		/>
	);
} );
const render = () => {
	return TestRenderer.create( <TestComponent attributes={ attributes } /> );
};

describe( 'withProduct Component', () => {
	let renderer;
	afterEach( () => {
		mockUtils.getProduct.mockReset();
	} );

	describe( 'lifecycle events', () => {
		beforeEach( () => {
			mockUtils.getProduct.mockImplementation( () => Promise.resolve() );
			renderer = render();
		} );

		it( 'getProduct is called on mount with passed in product id', () => {
			const { getProduct } = mockUtils;

			expect( getProduct ).toHaveBeenCalledWith( attributes.productId );
			expect( getProduct ).toHaveBeenCalledTimes( 1 );
		} );

		it( 'getProduct is called on component update', () => {
			const { getProduct } = mockUtils;
			const newAttributes = { ...attributes, productId: 2 };
			renderer.update( <TestComponent attributes={ newAttributes } /> );

			expect( getProduct ).toHaveBeenNthCalledWith(
				2,
				newAttributes.productId
			);
			expect( getProduct ).toHaveBeenCalledTimes( 2 );
		} );

		it( 'getProduct is hooked to the prop', () => {
			const { getProduct } = mockUtils;
			const props = renderer.root.findByType( 'div' ).props;

			props[ 'data-getProduct' ]();

			expect( getProduct ).toHaveBeenCalledTimes( 2 );
		} );
	} );

	describe( 'when the API returns product data', () => {
		beforeEach( () => {
			mockUtils.getProduct.mockImplementation( ( productId ) =>
				Promise.resolve( { ...mockProduct, id: productId } )
			);
			renderer = render();
		} );

		it( 'sets the product props', () => {
			const props = renderer.root.findByType( 'div' ).props;

			expect( props[ 'data-error' ] ).toBeNull();
			expect( typeof props[ 'data-getProduct' ] ).toBe( 'function' );
			expect( props[ 'data-isLoading' ] ).toBe( false );
			expect( props[ 'data-product' ] ).toEqual( {
				...mockProduct,
				id: attributes.productId,
			} );
		} );
	} );

	describe( 'when the API returns an error', () => {
		const error = { message: 'There was an error.' };
		const getProductPromise = Promise.reject( error );
		const formattedError = { message: 'There was an error.', type: 'api' };

		beforeEach( () => {
			mockUtils.getProduct.mockImplementation( () => getProductPromise );
			mockBaseUtils.formatError.mockImplementation(
				() => formattedError
			);
			renderer = render();
		} );

		test( 'sets the error prop', async () => {
			await expect( () => getProductPromise() ).toThrow();

			const { formatError } = mockBaseUtils;
			const props = renderer.root.findByType( 'div' ).props;

			expect( formatError ).toHaveBeenCalledWith( error );
			expect( formatError ).toHaveBeenCalledTimes( 1 );
			expect( props[ 'data-error' ] ).toEqual( formattedError );
			expect( typeof props[ 'data-getProduct' ] ).toBe( 'function' );
			expect( props[ 'data-isLoading' ] ).toBe( false );
			expect( props[ 'data-product' ] ).toBeNull();
		} );
	} );
} );
test/with-searched-products.js000064400000004473151550047500012463 0ustar00// We need to disable the following eslint check as it's only applicable
// to testing-library/react not `react-test-renderer` used here
/* eslint-disable testing-library/await-async-query */
/**
 * External dependencies
 */
import TestRenderer, { act } from 'react-test-renderer';
import * as mockUtils from '@woocommerce/editor-components/utils';
import { useDebouncedCallback } from 'use-debounce';

/**
 * Internal dependencies
 */
import withSearchedProducts from '../with-searched-products';

// Add a mock implementation of debounce for testing so we can spy on the onSearch call.
jest.mock( 'use-debounce', () => {
	return {
		useDebouncedCallback: jest
			.fn()
			.mockImplementation(
				( search ) => () => mockUtils.getProducts( search )
			),
	};
} );

jest.mock( '@woocommerce/block-settings', () => ( {
	__esModule: true,
	blocksConfig: {
		productCount: 101,
	},
} ) );

// Mock the getProducts values for tests.
mockUtils.getProducts = jest.fn().mockImplementation( () =>
	Promise.resolve( [
		{ id: 10, name: 'foo', parent: 0 },
		{ id: 20, name: 'bar', parent: 0 },
	] )
);

describe( 'withSearchedProducts Component', () => {
	const { getProducts } = mockUtils;
	afterEach( () => {
		useDebouncedCallback.mockClear();
		mockUtils.getProducts.mockClear();
	} );
	const TestComponent = withSearchedProducts(
		( { selected, products, isLoading, onSearch } ) => {
			return (
				<div
					data-products={ products }
					data-selected={ selected }
					data-isLoading={ isLoading }
					data-onSearch={ onSearch }
				/>
			);
		}
	);
	describe( 'lifecycle tests', () => {
		const selected = [ 10 ];
		let props, renderer;

		act( () => {
			renderer = TestRenderer.create(
				<TestComponent selected={ selected } />
			);
		} );

		it( 'has expected values for props', () => {
			props = renderer.root.findByType( 'div' ).props;
			expect( props[ 'data-selected' ] ).toEqual( selected );
			expect( props[ 'data-products' ] ).toEqual( [
				{ id: 10, name: 'foo', parent: 0 },
				{ id: 20, name: 'bar', parent: 0 },
			] );
		} );

		it( 'debounce and getProducts is called on search event', async () => {
			props = renderer.root.findByType( 'div' ).props;

			act( () => {
				props[ 'data-onSearch' ]();
			} );

			expect( useDebouncedCallback ).toHaveBeenCalled();
			expect( getProducts ).toHaveBeenCalledTimes( 1 );
		} );
	} );
} );
test/with-transform-single-select-to-multiple-select.js000064400000002345151550047500017335 0ustar00// We need to disable the following eslint check as it's only applicable
// to testing-library/react not `react-test-renderer` used here
/* eslint-disable testing-library/await-async-query */
/**
 * External dependencies
 */
import TestRenderer from 'react-test-renderer';

/**
 * Internal dependencies
 */
import withTransformSingleSelectToMultipleSelect from '../with-transform-single-select-to-multiple-select';

const TestComponent = withTransformSingleSelectToMultipleSelect( ( props ) => {
	return <div selected={ props.selected } />;
} );

describe( 'withTransformSingleSelectToMultipleSelect Component', () => {
	describe( 'when the API returns an error', () => {
		it( 'converts the selected value into an array', () => {
			const selected = 123;
			const renderer = TestRenderer.create(
				<TestComponent selected={ selected } />
			);
			const props = renderer.root.findByType( 'div' ).props;
			expect( props.selected ).toEqual( [ selected ] );
		} );

		it( 'passes an empty array as the selected prop if selected was null', () => {
			const renderer = TestRenderer.create(
				<TestComponent selected={ null } />
			);
			const props = renderer.root.findByType( 'div' ).props;
			expect( props.selected ).toEqual( [] );
		} );
	} );
} );
with-attributes.js000064400000006151151550047500010246 0ustar00/**
 * External dependencies
 */
import { useState, useEffect } from '@wordpress/element';
import { getAttributes, getTerms } from '@woocommerce/editor-components/utils';

/**
 * Internal dependencies
 */
import { formatError } from '../base/utils/errors';

/**
 * Get attribute data (name, taxonomy etc) from server data.
 *
 * @param {number}     attributeId   Attribute ID to look for.
 * @param {Array|null} attributeList List of attributes.
 * @param {string}     matchField    Field to match on. e.g. id or slug.
 */
const getAttributeData = ( attributeId, attributeList, matchField = 'id' ) => {
	return Array.isArray( attributeList )
		? attributeList.find( ( attr ) => attr[ matchField ] === attributeId )
		: null;
};

/**
 * HOC that calls the useAttributes hook.
 *
 * @param {Function} OriginalComponent Component being wrapped.
 */
const withAttributes = ( OriginalComponent ) => {
	return ( props ) => {
		const { selected = [] } = props;
		const selectedSlug = selected.length ? selected[ 0 ].attr_slug : null;
		const [ attributes, setAttributes ] = useState( null );
		const [ expandedAttribute, setExpandedAttribute ] = useState( 0 );
		const [ termsList, setTermsList ] = useState( {} );
		const [ loading, setLoading ] = useState( true );
		const [ termsLoading, setTermsLoading ] = useState( false );
		const [ error, setError ] = useState( null );

		useEffect( () => {
			if ( attributes === null ) {
				getAttributes()
					.then( ( newAttributes ) => {
						newAttributes = newAttributes.map( ( attribute ) => ( {
							...attribute,
							parent: 0,
						} ) );

						setAttributes( newAttributes );

						if ( selectedSlug ) {
							const selectedAttributeFromTerm = getAttributeData(
								selectedSlug,
								newAttributes,
								'taxonomy'
							);

							if ( selectedAttributeFromTerm ) {
								setExpandedAttribute(
									selectedAttributeFromTerm.id
								);
							}
						}
					} )
					.catch( async ( e ) => {
						setError( await formatError( e ) );
					} )
					.finally( () => {
						setLoading( false );
					} );
			}
		}, [ attributes, selectedSlug ] );

		useEffect( () => {
			const attributeData = getAttributeData(
				expandedAttribute,
				attributes
			);

			if ( ! attributeData ) {
				return;
			}

			setTermsLoading( true );

			getTerms( expandedAttribute )
				.then( ( newTerms ) => {
					newTerms = newTerms.map( ( term ) => ( {
						...term,
						parent: expandedAttribute,
						attr_slug: attributeData.taxonomy,
					} ) );

					setTermsList( ( previousTermsList ) => ( {
						...previousTermsList,
						[ expandedAttribute ]: newTerms,
					} ) );
				} )
				.catch( async ( e ) => {
					setError( await formatError( e ) );
				} )
				.finally( () => {
					setTermsLoading( false );
				} );
		}, [ expandedAttribute, attributes ] );

		return (
			<OriginalComponent
				{ ...props }
				attributes={ attributes || [] }
				error={ error }
				expandedAttribute={ expandedAttribute }
				onExpandAttribute={ setExpandedAttribute }
				isLoading={ loading }
				termsAreLoading={ termsLoading }
				termsList={ termsList }
			/>
		);
	};
};

export default withAttributes;
with-categories.js000064400000002672151550047500010211 0ustar00/**
 * External dependencies
 */
import { Component } from '@wordpress/element';
import { createHigherOrderComponent } from '@wordpress/compose';
import { getCategories } from '@woocommerce/editor-components/utils';

/**
 * Internal dependencies
 */
import { formatError } from '../base/utils/errors';

/**
 * HOC that queries categories for a component.
 *
 * @param {Function} OriginalComponent Component being wrapped.
 */
const withCategories = createHigherOrderComponent( ( OriginalComponent ) => {
	return class WrappedComponent extends Component {
		constructor() {
			super( ...arguments );
			this.state = {
				error: null,
				loading: false,
				categories: [],
			};
			this.loadCategories = this.loadCategories.bind( this );
		}

		componentDidMount() {
			this.loadCategories();
		}

		loadCategories() {
			this.setState( { loading: true } );

			getCategories()
				.then( ( categories ) => {
					this.setState( {
						categories,
						loading: false,
						error: null,
					} );
				} )
				.catch( async ( e ) => {
					const error = await formatError( e );

					this.setState( {
						categories: [],
						loading: false,
						error,
					} );
				} );
		}

		render() {
			const { error, loading, categories } = this.state;

			return (
				<OriginalComponent
					{ ...this.props }
					error={ error }
					isLoading={ loading }
					categories={ categories }
				/>
			);
		}
	};
}, 'withCategories' );

export default withCategories;
with-category.js000064400000003634151550047500007700 0ustar00/**
 * External dependencies
 */
import { Component } from '@wordpress/element';
import { createHigherOrderComponent } from '@wordpress/compose';
import { getCategory } from '@woocommerce/editor-components/utils';

/**
 * Internal dependencies
 */
import { formatError } from '../base/utils/errors';

/**
 * HOC that queries a category for a component.
 *
 * @param {Function} OriginalComponent Component being wrapped.
 */
const withCategory = createHigherOrderComponent( ( OriginalComponent ) => {
	return class WrappedComponent extends Component {
		constructor() {
			super( ...arguments );
			this.state = {
				error: null,
				loading: false,
				category:
					this.props.attributes.categoryId === 'preview'
						? this.props.attributes.previewCategory
						: null,
			};
			this.loadCategory = this.loadCategory.bind( this );
		}

		componentDidMount() {
			this.loadCategory();
		}

		componentDidUpdate( prevProps ) {
			if (
				prevProps.attributes.categoryId !==
				this.props.attributes.categoryId
			) {
				this.loadCategory();
			}
		}

		loadCategory() {
			const { categoryId } = this.props.attributes;

			if ( categoryId === 'preview' ) {
				return;
			}

			if ( ! categoryId ) {
				this.setState( {
					category: null,
					loading: false,
					error: null,
				} );
				return;
			}

			this.setState( { loading: true } );

			getCategory( categoryId )
				.then( ( category ) => {
					this.setState( { category, loading: false, error: null } );
				} )
				.catch( async ( e ) => {
					const error = await formatError( e );

					this.setState( { category: null, loading: false, error } );
				} );
		}

		render() {
			const { error, loading, category } = this.state;

			return (
				<OriginalComponent
					{ ...this.props }
					error={ error }
					getCategory={ this.loadCategory }
					isLoading={ loading }
					category={ category }
				/>
			);
		}
	};
}, 'withCategory' );

export default withCategory;
with-product-variations.js000064400000010656151550047500011722 0ustar00/**
 * External dependencies
 */
import { Component } from '@wordpress/element';
import { createHigherOrderComponent } from '@wordpress/compose';
import PropTypes from 'prop-types';
import isShallowEqual from '@wordpress/is-shallow-equal';
import { getProductVariations } from '@woocommerce/editor-components/utils';

/**
 * Internal dependencies
 */
import { formatError } from '../base/utils/errors';

/**
 * HOC that queries variations for a component.
 *
 * @param {Function} OriginalComponent Component being wrapped.
 */
const withProductVariations = createHigherOrderComponent(
	( OriginalComponent ) => {
		class WrappedComponent extends Component {
			state = {
				error: null,
				loading: false,
				variations: {},
			};

			componentDidMount() {
				const { selected, showVariations } = this.props;

				if ( selected && showVariations ) {
					this.loadVariations();
				}
			}

			componentDidUpdate( prevProps ) {
				const { isLoading, selected, showVariations } = this.props;

				if (
					showVariations &&
					( ! isShallowEqual( prevProps.selected, selected ) ||
						( prevProps.isLoading && ! isLoading ) )
				) {
					this.loadVariations();
				}
			}

			loadVariations = () => {
				const { products } = this.props;
				const { loading, variations } = this.state;

				if ( loading ) {
					return;
				}

				const expandedProduct = this.getExpandedProduct();

				if ( ! expandedProduct || variations[ expandedProduct ] ) {
					return;
				}

				const productDetails = products.find(
					( findProduct ) => findProduct.id === expandedProduct
				);

				if (
					! productDetails.variations ||
					productDetails.variations.length === 0
				) {
					this.setState( {
						variations: {
							...this.state.variations,
							[ expandedProduct ]: null,
						},
						loading: false,
						error: null,
					} );
					return;
				}

				this.setState( { loading: true } );

				getProductVariations( expandedProduct )
					.then( ( expandedProductVariations ) => {
						const newVariations = expandedProductVariations.map(
							( variation ) => ( {
								...variation,
								parent: expandedProduct,
							} )
						);
						this.setState( {
							variations: {
								...this.state.variations,
								[ expandedProduct ]: newVariations,
							},
							loading: false,
							error: null,
						} );
					} )
					.catch( async ( e ) => {
						const error = await formatError( e );

						this.setState( {
							variations: {
								...this.state.variations,
								[ expandedProduct ]: null,
							},
							loading: false,
							error,
						} );
					} );
			};

			isProductId( itemId ) {
				const { products } = this.props;
				return products.some( ( p ) => p.id === itemId );
			}

			findParentProduct( variationId ) {
				const { products } = this.props;
				const parentProduct = products.filter(
					( p ) =>
						p.variations &&
						p.variations.find( ( { id } ) => id === variationId )
				);
				return parentProduct[ 0 ]?.id;
			}

			getExpandedProduct() {
				const { isLoading, selected, showVariations } = this.props;

				if ( ! showVariations ) {
					return null;
				}

				let selectedItem =
					selected && selected.length ? selected[ 0 ] : null;

				// If there is no selected item, check if there was one in the past, so we
				// can keep the same product expanded.
				if ( selectedItem ) {
					this.prevSelectedItem = selectedItem;
				} else if ( this.prevSelectedItem ) {
					// If previous selected item was a variation
					if (
						! isLoading &&
						! this.isProductId( this.prevSelectedItem )
					) {
						selectedItem = this.prevSelectedItem;
					}
				}

				if ( ! isLoading && selectedItem ) {
					return this.isProductId( selectedItem )
						? selectedItem
						: this.findParentProduct( selectedItem );
				}

				return null;
			}

			render() {
				const { error: propsError, isLoading } = this.props;
				const { error, loading, variations } = this.state;

				return (
					<OriginalComponent
						{ ...this.props }
						error={ error || propsError }
						expandedProduct={ this.getExpandedProduct() }
						isLoading={ isLoading }
						variations={ variations }
						variationsLoading={ loading }
					/>
				);
			}

			static propTypes = {
				selected: PropTypes.array,
				showVariations: PropTypes.bool,
			};

			static defaultProps = {
				selected: [],
				showVariations: false,
			};
		}
		return WrappedComponent;
	},
	'withProductVariations'
);

export default withProductVariations;
with-product.js000064400000003400151550047500007532 0ustar00/**
 * External dependencies
 */
import { Component } from '@wordpress/element';
import { createHigherOrderComponent } from '@wordpress/compose';
import { getProduct } from '@woocommerce/editor-components/utils';

/**
 * Internal dependencies
 */
import { formatError } from '../base/utils/errors';

/**
 * HOC that queries a product for a component.
 *
 * @param {Function} OriginalComponent Component being wrapped.
 */
const withProduct = createHigherOrderComponent( ( OriginalComponent ) => {
	return class WrappedComponent extends Component {
		state = {
			error: null,
			loading: false,
			product:
				this.props.attributes.productId === 'preview'
					? this.props.attributes.previewProduct
					: null,
		};

		componentDidMount() {
			this.loadProduct();
		}

		componentDidUpdate( prevProps ) {
			if (
				prevProps.attributes.productId !==
				this.props.attributes.productId
			) {
				this.loadProduct();
			}
		}

		loadProduct = () => {
			const { productId } = this.props.attributes;

			if ( productId === 'preview' ) {
				return;
			}

			if ( ! productId ) {
				this.setState( { product: null, loading: false, error: null } );
				return;
			}

			this.setState( { loading: true } );

			getProduct( productId )
				.then( ( product ) => {
					this.setState( { product, loading: false, error: null } );
				} )
				.catch( async ( e ) => {
					const error = await formatError( e );

					this.setState( { product: null, loading: false, error } );
				} );
		};

		render() {
			const { error, loading, product } = this.state;

			return (
				<OriginalComponent
					{ ...this.props }
					error={ error }
					getProduct={ this.loadProduct }
					isLoading={ loading }
					product={ product }
				/>
			);
		}
	};
}, 'withProduct' );

export default withProduct;
with-searched-products.tsx000064400000004610151550047500011677 0ustar00/**
 * External dependencies
 */
import { useEffect, useState, useCallback, useRef } from '@wordpress/element';
import { blocksConfig } from '@woocommerce/block-settings';
import { getProducts } from '@woocommerce/editor-components/utils';
import { useDebouncedCallback } from 'use-debounce';
import type {
	ProductResponseItem,
	WithInjectedSearchedProducts,
} from '@woocommerce/types';

/**
 * Internal dependencies
 */
import { formatError } from '../base/utils/errors';

interface WithSearchedProductProps {
	selected: number[];
}

/**
 * A higher order component that enhances the provided component with products from a search query.
 */
const withSearchedProducts = <
	T extends Record< string, unknown > & WithSearchedProductProps
>(
	OriginalComponent: React.ComponentType< T & WithInjectedSearchedProducts >
) => {
	return ( { selected, ...props }: T ): JSX.Element => {
		const [ isLoading, setIsLoading ] = useState( true );
		const [ error, setError ] = useState< {
			message: string;
			type: string;
		} | null >( null );
		const [ productsList, setProductsList ] = useState<
			ProductResponseItem[]
		>( [] );
		const isLargeCatalog = blocksConfig.productCount > 100;

		const setErrorState = async ( e: {
			message: string;
			type: string;
		} ) => {
			const formattedError = ( await formatError( e ) ) as {
				message: string;
				type: string;
			};
			setError( formattedError );
			setIsLoading( false );
		};

		const selectedRef = useRef( selected );

		useEffect( () => {
			getProducts( { selected: selectedRef.current } )
				.then( ( results ) => {
					setProductsList( results as ProductResponseItem[] );
					setIsLoading( false );
				} )
				.catch( setErrorState );
		}, [ selectedRef ] );

		const debouncedSearch = useDebouncedCallback( ( search: string ) => {
			getProducts( { selected, search } )
				.then( ( results ) => {
					setProductsList( results as ProductResponseItem[] );
					setIsLoading( false );
				} )
				.catch( setErrorState );
		}, 400 );

		const onSearch = useCallback(
			( search: string ) => {
				setIsLoading( true );
				debouncedSearch( search );
			},
			[ setIsLoading, debouncedSearch ]
		);

		return (
			<OriginalComponent
				{ ...( props as T ) }
				selected={ selected }
				error={ error }
				products={ productsList }
				isLoading={ isLoading }
				onSearch={ isLargeCatalog ? onSearch : null }
			/>
		);
	};
};

export default withSearchedProducts;
with-transform-single-select-to-multiple-select.tsx000064400000002123151550047500016552 0ustar00/**
 * External dependencies
 */
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - Ignoring because @wordpress/element library does not have type definition for FunctionComponent
// eslint-disable-next-line
import { FunctionComponent } from '@wordpress/element';

export type SelectedOption = number | string | null | number[] | string[];

export interface WithMaybeSelectedOption {
	selected?: SelectedOption;
}

/**
 * HOC that transforms a single select to a multiple select.
 */
const withTransformSingleSelectToMultipleSelect = <
	T extends Record< string, unknown >
>(
	OriginalComponent: FunctionComponent< T & WithMaybeSelectedOption >
) => {
	return ( props: T & WithMaybeSelectedOption ): JSX.Element => {
		let { selected } = props;
		selected = selected === undefined ? null : selected;
		const isNil = selected === null;

		return Array.isArray( selected ) ? (
			<OriginalComponent { ...props } />
		) : (
			<OriginalComponent
				{ ...props }
				selected={ isNil ? [] : [ selected ] }
			/>
		);
	};
};

export default withTransformSingleSelectToMultipleSelect;