Implement PHP DI container and refactor. Also implements new Asset data interface for extendable settings passed to js. (https://github.com/woocommerce/woocommerce-blocks/pull/956)

* Add dependency injection container for blocks

* Add new Pacakge and Bootstrap classes.

- Bootstrap for bootstrapping the plugin.
- Package will replace `src/Package` and added as a dependency for any classes needing package info.

* Introduce AssetsDataRegistry for managing asset data

* refactor existing classes to use new DIC and Asset Data Registry

- this is the bare minimum needed to make this pull viable.
- further refactors will be done in more atomic smaller pulls for easier review.

* add new settings handling and export `@woocommerce/settings` as an alias to wc.wcSettings

- the export is exposed php side on the `wc-settings` handle.

* Remove unnecessary concatenation

* Fix typos and improve doc blocks

* fix php linting issue

* Use better escaping function.

* improve jsdoc spacing

* improve test assertion

* use fully qualified class names in bootstrap

* improve comment block to account for dynamic version string replace on build

* handle exceptions a bit differently

* correct dependency reference in webpack config

* remove blank lines

* fix doc block comment alignment

* Various doc/grammar/spacing fixes from code review.

Co-Authored-By: Albert Juhé Lluveras <contact@albertjuhe.com>

* improve naming, documentation and logic of filter callbacks

While this is intended for sanitization/validation, the callback ultimately provides flexibility for filtering the value before returning or setting in state so `filter` is a better name for this.
This commit is contained in:
Darren Ethier 2019-09-23 14:07:13 -04:00 committed by GitHub
parent 574b5230d9
commit 57fdb8fe9c
31 changed files with 1521 additions and 239 deletions

View File

@ -4,19 +4,11 @@ module.exports = {
'jest/globals': true, 'jest/globals': true,
}, },
globals: { globals: {
wc_product_block_data: true,
wcSettings: true, wcSettings: true,
}, },
plugins: [ 'jest' ], plugins: [ 'jest' ],
rules: { rules: {
'@wordpress/dependency-group': 'off', '@wordpress/dependency-group': 'off',
camelcase: [
'error',
{
allow: [ 'wc_product_block_data' ],
properties: 'never',
},
],
'valid-jsdoc': 'off', 'valid-jsdoc': 'off',
}, },
}; };

View File

@ -0,0 +1,19 @@
import { getSetting } from '@woocommerce/settings';
export const ENABLE_REVIEW_RATING = getSetting( 'enableReviewRating', true );
export const SHOW_AVATARS = getSetting( 'showAvatars', true );
export const MAX_COLUMNS = getSetting( 'max_columns', 6 );
export const MIN_COLUMNS = getSetting( 'min_columns', 1 );
export const DEFAULT_COLUMNS = getSetting( 'default_columns', 3 );
export const MAX_ROWS = getSetting( 'max_rows', 6 );
export const MIN_ROWS = getSetting( 'min_rows', 1 );
export const DEFAULT_ROWS = getSetting( 'default_rows', 1 );
export const MIN_HEIGHT = getSetting( 'min_height', 500 );
export const DEFAULT_HEIGHT = getSetting( 'default_height', 500 );
export const PLACEHOLDER_IMG_SRC = getSetting( 'placeholderImgSrc ', '' );
export const THUMBNAIL_SIZE = getSetting( 'thumbnail_size', 300 );
export const IS_LARGE_CATALOG = getSetting( 'isLargeCatalog' );
export const LIMIT_TAGS = getSetting( 'limitTags' );
export const HAS_TAGS = getSetting( 'hasTags', true );
export const HOME_URL = getSetting( 'homeUrl ', '' );
export const PRODUCT_CATEGORIES = getSetting( 'productCategories', [] );

View File

@ -1,37 +1,2 @@
const getConstantFromData = ( property, fallback = false ) => { export * from './constants';
if (
typeof wc_product_block_data === 'object' &&
wc_product_block_data.hasOwnProperty( property )
) {
return wc_product_block_data[ property ];
}
return fallback;
};
export const ENABLE_REVIEW_RATING = getConstantFromData(
'enableReviewRating',
true
);
export const SHOW_AVATARS = getConstantFromData( 'showAvatars', true );
export const MAX_COLUMNS = getConstantFromData( 'max_columns', 6 );
export const MIN_COLUMNS = getConstantFromData( 'min_columns', 1 );
export const DEFAULT_COLUMNS = getConstantFromData( 'default_columns', 3 );
export const MAX_ROWS = getConstantFromData( 'max_rows', 6 );
export const MIN_ROWS = getConstantFromData( 'min_rows', 1 );
export const DEFAULT_ROWS = getConstantFromData( 'default_rows', 1 );
export const MIN_HEIGHT = getConstantFromData( 'min_height', 500 );
export const DEFAULT_HEIGHT = getConstantFromData( 'default_height', 500 );
export const PLACEHOLDER_IMG_SRC = getConstantFromData(
'placeholderImgSrc ',
''
);
export const THUMBNAIL_SIZE = getConstantFromData( 'thumbnail_size', 300 );
export const IS_LARGE_CATALOG = getConstantFromData( 'isLargeCatalog' );
export const LIMIT_TAGS = getConstantFromData( 'limitTags' );
export const HAS_TAGS = getConstantFromData( 'hasTags', true );
export const HOME_URL = getConstantFromData( 'homeUrl ', '' );
export const PRODUCT_CATEGORIES = getConstantFromData(
'productCategories',
[]
);
export { ENDPOINTS } from './endpoints'; export { ENDPOINTS } from './endpoints';

View File

@ -1,16 +0,0 @@
/**
* Wrapper for the wcSettings global, which sets defaults if data is missing.
*
* Only settings used by blocks are defined here. Component settings are left out.
*/
const currency = wcSettings.currency || {
code: 'USD',
precision: 2,
symbol: '$',
position: 'left',
decimal_separator: '.',
thousand_separator: ',',
price_format: '%1$s%2$s',
};
export default currency;

View File

@ -0,0 +1,13 @@
/**
* Internal dependencies
*/
import { allSettings } from './settings-init';
export const ADMIN_URL = allSettings.adminUrl;
export const COUNTRIES = allSettings.countries;
export const CURRENCY = allSettings.currency;
export const LOCALE = allSettings.locale;
export const ORDER_STATUSES = allSettings.orderStatuses;
export const SITE_TITLE = allSettings.siteTitle;
export const WC_ASSET_URL = allSettings.wcAssetUrl;
export const DEFAULT_DATE_RANGE = allSettings.defaultDateRange;

View File

@ -0,0 +1,26 @@
/**
* Internal dependencies
*/
import { allSettings } from './settings-init';
/**
* Retrieves a setting value from the setting state.
*
* @export
* @param {string} name The identifier for the setting.
* @param {mixed} [fallback=false] The value to use as a fallback
* if the setting is not in the
* state.
* @param {function} [filter=( val ) => val] A callback for filtering the
* value before it's returned.
* Receives both the found value
* (if it exists for the key) and
* the provided fallback arg.
* @returns {mixed}
*/
export function getSetting( name, fallback = false, filter = ( val ) => val ) {
const value = allSettings.hasOwnProperty( name )
? allSettings[ name ]
: fallback;
return filter( value, fallback );
}

View File

@ -1,2 +1,3 @@
// Exports shared settings from wcSettings global. export { getSetting } from './get-setting';
export { default as currency } from './currency'; export { setSetting } from './set-setting';
export * from './default-constants';

View File

@ -0,0 +1,17 @@
import { allSettings } from './settings-init';
/**
* Sets a value to a property on the settings state.
*
* @export
* @param {string} name The setting property key for the
* setting being mutated.
* @param {mixed} value The value to set.
* @param {function} [filter=( val ) => val] Allows for providing a callback
* to sanitize the setting (eg.
* ensure it's a number)
*/
export function setSetting( name, value, filter = ( val ) => val ) {
value = filter( value );
allSettings[ name ] = filter( value );
}

View File

@ -0,0 +1,42 @@
const defaults = {
adminUrl: '',
countries: [],
currency: {
code: 'USD',
precision: 2,
symbol: '$',
symbolPosition: 'left',
decimalSeparator: '.',
priceFormat: '%1$s%2$s',
thousandSeparator: ',',
},
defaultDateRange: 'period=month&compare=previous_year',
locale: {
siteLocale: 'en_US',
userLocale: 'en_US',
weekdaysShort: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ],
},
orderStatuses: [],
siteTitle: '',
wcAssetUrl: '',
};
const globalSharedSettings = typeof wcSettings === 'object' ? wcSettings : {};
// Use defaults or global settings, depending on what is set.
const allSettings = {
...defaults,
...globalSharedSettings,
};
allSettings.currency = {
...defaults.currency,
...allSettings.currency,
};
allSettings.locale = {
...defaults.locale,
...allSettings.locale,
};
export { allSettings };

View File

@ -0,0 +1,17 @@
/**
* Internal dependencies
*/
import { getSetting } from '../get-setting';
import { ADMIN_URL } from '../default-constants';
describe( 'getSetting', () => {
it( 'returns provided default for non available setting', () => {
expect( getSetting( 'nada', 'really nada' ) ).toBe( 'really nada' );
} );
it( 'returns expected value for existing setting', () => {
expect( getSetting( 'adminUrl', 'not this' ) ).toEqual( ADMIN_URL );
} );
it( 'filters value via provided filter callback', () => {
expect( getSetting( 'some value', 'default', () => 42 ) ).toBe( 42 );
} );
} );

View File

@ -0,0 +1,20 @@
/**
* Internal dependencies
*/
import { setSetting } from '../set-setting';
import { getSetting } from '../get-setting';
describe( 'setSetting', () => {
it( 'should add a new value to the settings state for value not present', () => {
setSetting( 'aSetting', 42 );
expect( getSetting( 'aSetting' ) ).toBe( 42 );
} );
it( 'should replace existing value', () => {
setSetting( 'adminUrl', 'not original' );
expect( getSetting( 'adminUrl' ) ).toBe( 'not original' );
} );
it( 'should save the value run through the provided filter', () => {
setSetting( 'aSetting', 'who', () => 42 );
expect( getSetting( 'aSetting' ) ).toBe( 42 );
} );
} );

View File

@ -16,19 +16,23 @@ class Assets {
/** /**
* Initialize class features on init. * Initialize class features on init.
*
* @since $VID:$
* Moved most initialization to BootStrap and AssetDataRegistry
* classes as a part of ongoing refactor
*/ */
public static function init() { public static function init() {
add_action( 'init', array( __CLASS__, 'register_assets' ) ); add_action( 'init', array( __CLASS__, 'register_assets' ) );
add_action( 'admin_print_scripts', array( __CLASS__, 'print_shared_settings' ), 1 );
add_action( 'admin_print_scripts', array( __CLASS__, 'maybe_add_asset_data' ), 1 );
add_action( 'admin_print_footer_scripts', array( __CLASS__, 'maybe_add_asset_data' ), 1 );
add_action( 'wp_print_scripts', array( __CLASS__, 'maybe_add_asset_data' ), 1 );
add_action( 'wp_print_footer_scripts', array( __CLASS__, 'maybe_add_asset_data' ), 1 );
add_action( 'body_class', array( __CLASS__, 'add_theme_body_class' ), 1 ); add_action( 'body_class', array( __CLASS__, 'add_theme_body_class' ), 1 );
add_filter( 'woocommerce_shared_settings', array( __CLASS__, 'get_wc_block_data' ) );
} }
/** /**
* Register block scripts & styles. * Register block scripts & styles.
*
* @since $VID:$
* Moved data related enqueuing to new AssetDataRegistry class
* as part of ongoing refactoring.
*/ */
public static function register_assets() { public static function register_assets() {
self::register_style( 'wc-block-editor', plugins_url( 'build/editor.css', __DIR__ ), array( 'wp-edit-blocks' ) ); self::register_style( 'wc-block-editor', plugins_url( 'build/editor.css', __DIR__ ), array( 'wp-edit-blocks' ) );
@ -37,10 +41,8 @@ class Assets {
wp_style_add_data( 'wc-block-style', 'rtl', 'replace' ); wp_style_add_data( 'wc-block-style', 'rtl', 'replace' );
// Shared libraries and components across all blocks. // Shared libraries and components across all blocks.
self::register_script( 'wc-shared-settings', plugins_url( 'build/wc-shared-settings.js', __DIR__ ), [], false );
self::register_script( 'wc-block-settings', plugins_url( 'build/wc-block-settings.js', __DIR__ ), [], false );
self::register_script( 'wc-blocks', plugins_url( 'build/blocks.js', __DIR__ ), [], false ); self::register_script( 'wc-blocks', plugins_url( 'build/blocks.js', __DIR__ ), [], false );
self::register_script( 'wc-vendors', plugins_url( 'build/vendors.js', __DIR__ ), [ 'wc-shared-settings' ], false ); self::register_script( 'wc-vendors', plugins_url( 'build/vendors.js', __DIR__ ), [], false );
// Individual blocks. // Individual blocks.
self::register_script( 'wc-handpicked-products', plugins_url( 'build/handpicked-products.js', __DIR__ ), array( 'wc-vendors', 'wc-blocks' ) ); self::register_script( 'wc-handpicked-products', plugins_url( 'build/handpicked-products.js', __DIR__ ), array( 'wc-vendors', 'wc-blocks' ) );
@ -60,30 +62,6 @@ class Assets {
self::register_script( 'wc-product-search', plugins_url( 'build/product-search.js', __DIR__ ), array( 'wc-vendors', 'wc-blocks' ) ); self::register_script( 'wc-product-search', plugins_url( 'build/product-search.js', __DIR__ ), array( 'wc-vendors', 'wc-blocks' ) );
} }
/**
* Print wcSettings in all pages. This is a temporary fix until we find a better
* solution to share settings between WooCommerce Admin and WooCommerce Blocks.
* See https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/932
*/
public static function print_shared_settings() {
echo '<script>';
echo "var wcSettings = wcSettings || JSON.parse( decodeURIComponent( '" . esc_js( self::get_wc_settings_data() ) . "' ) );";
echo '</script>';
}
/**
* Attach data to registered assets using inline scripts.
*/
public static function maybe_add_asset_data() {
if ( wp_script_is( 'wc-block-settings', 'enqueued' ) ) {
wp_add_inline_script(
'wc-block-settings',
self::get_wc_block_data(),
'before'
);
}
}
/** /**
* Add body classes. * Add body classes.
* *
@ -95,50 +73,17 @@ class Assets {
return $classes; return $classes;
} }
/**
* Returns javascript to inject as data for enqueued wc-shared-settings script.
*
* @return string;
* @since 2.4.0
*/
protected static function get_wc_settings_data() {
global $wp_locale;
$code = get_woocommerce_currency();
$settings = apply_filters(
'woocommerce_components_settings',
array(
'adminUrl' => admin_url(),
'wcAssetUrl' => plugins_url( 'assets/', WC_PLUGIN_FILE ),
'siteLocale' => esc_attr( get_bloginfo( 'language' ) ),
'currency' => array(
'code' => $code,
'precision' => wc_get_price_decimals(),
'symbol' => html_entity_decode( get_woocommerce_currency_symbol( $code ) ),
'position' => get_option( 'woocommerce_currency_pos' ),
'decimal_separator' => wc_get_price_decimal_separator(),
'thousand_separator' => wc_get_price_thousand_separator(),
'price_format' => html_entity_decode( get_woocommerce_price_format() ),
),
'stockStatuses' => wc_get_product_stock_status_options(),
'siteTitle' => get_bloginfo( 'name' ),
'dataEndpoints' => [],
'l10n' => array(
'userLocale' => get_user_locale(),
'weekdaysShort' => array_values( $wp_locale->weekday_abbrev ),
),
)
);
return rawurlencode( wp_json_encode( $settings ) );
}
/** /**
* Returns block-related data for enqueued wc-block-settings script. * Returns block-related data for enqueued wc-block-settings script.
* *
* This is used to map site settings & data into JS-accessible variables. * This is used to map site settings & data into JS-accessible variables.
* *
* @param array $settings The original settings array from the filter.
*
* @since 2.4.0 * @since 2.4.0
* @since $VID:$ returned merged data along with incoming $settings
*/ */
protected static function get_wc_block_data() { public static function get_wc_block_data( $settings ) {
$tag_count = wp_count_terms( 'product_tag' ); $tag_count = wp_count_terms( 'product_tag' );
$product_counts = wp_count_posts( 'product' ); $product_counts = wp_count_posts( 'product' );
$product_categories = get_terms( $product_categories = get_terms(
@ -153,7 +98,9 @@ class Assets {
} }
// Global settings used in each block. // Global settings used in each block.
$block_settings = array( return array_merge(
$settings,
[
'min_columns' => wc_get_theme_support( 'product_blocks::min_columns', 1 ), 'min_columns' => wc_get_theme_support( 'product_blocks::min_columns', 1 ),
'max_columns' => wc_get_theme_support( 'product_blocks::max_columns', 6 ), 'max_columns' => wc_get_theme_support( 'product_blocks::max_columns', 6 ),
'default_columns' => wc_get_theme_support( 'product_blocks::default_columns', 3 ), 'default_columns' => wc_get_theme_support( 'product_blocks::default_columns', 3 ),
@ -168,12 +115,11 @@ class Assets {
'limitTags' => $tag_count > 100, 'limitTags' => $tag_count > 100,
'hasTags' => $tag_count > 0, 'hasTags' => $tag_count > 0,
'productCategories' => $product_categories, 'productCategories' => $product_categories,
'homeUrl' => esc_js( home_url( '/' ) ), 'homeUrl' => esc_url( home_url( '/' ) ),
'showAvatars' => '1' === get_option( 'show_avatars' ), 'showAvatars' => '1' === get_option( 'show_avatars' ),
'enableReviewRating' => 'yes' === get_option( 'woocommerce_enable_review_rating' ), 'enableReviewRating' => 'yes' === get_option( 'woocommerce_enable_review_rating' ),
]
); );
$block_settings = rawurlencode( wp_json_encode( $block_settings ) );
return "var wc_product_block_data = JSON.parse( decodeURIComponent( '" . $block_settings . "' ) );";
} }
/** /**

View File

@ -0,0 +1,150 @@
<?php
/**
* Contains asset api methods
*
* @package WooCommerce/Blocks
*/
namespace Automattic\WooCommerce\Blocks\Assets;
use Automattic\WooCommerce\Blocks\Domain\Package;
/**
* The Api class provides an interface to various asset registration helpers.
*
* @since $VID:$
*/
class Api {
/**
* Reference to the Package instance
*
* @var Package
*/
private $package;
/**
* Constructor for class
*
* @param Package $package An instance of Package.
*/
public function __construct( Package $package ) {
$this->package = $package;
}
/**
* Get the file modified time as a cache buster if we're in dev mode.
*
* @param string $file Local path to the file (relative to the plugin
* directory).
* @return string The cache buster value to use for the given file.
*/
protected function get_file_version( $file ) {
if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
return filemtime( $this->package->get_path( trim( $file, '/' ) ) );
}
return $this->package->get_version();
}
/**
* Retrieve the url to an asset for this plugin.
*
* @param string $relative_path An optional relative path appended to the
* returned url.
*
* @return string
*/
protected function get_asset_url( $relative_path = '' ) {
return $this->package->get_url( $relative_path );
}
/**
* Returns the dependency array for the given asset relative path.
*
* @param string $asset_relative_path Something like 'build/constants.js'
* considered to be relative to the main
* asset path.
* @param array $extra_dependencies Extra dependencies to be explicitly
* added to the generated array.
*
* @return array An array of dependencies
*/
protected function get_dependencies(
$asset_relative_path,
$extra_dependencies = []
) {
$dependency_path = $this->package->get_path(
str_replace( '.js', '.deps.json', $asset_relative_path )
);
// phpcs:ignore WordPress.WP.AlternativeFunctions
$dependencies = file_exists( $dependency_path )
// phpcs:ignore WordPress.WP.AlternativeFunctions
? json_decode( file_get_contents( $dependency_path ) )
: [];
return array_merge( $dependencies, $extra_dependencies );
}
/**
* Registers a script according to `wp_register_script`, additionally
* loading the translations for the file.
*
* @since $VID:$
*
* @param string $handle Name of the script. Should be unique.
* @param string $relative_src Relative url for the script to the path
* from plugin root.
* @param array $deps Optional. An array of registered script
* handles this script depends on. Default
* empty array.
* @param bool $has_i18n Optional. Whether to add a script
* translation call to this file. Default:
* true.
*/
public function register_script( $handle, $relative_src, $deps = [], $has_i18n = true ) {
wp_register_script(
$handle,
$this->get_asset_url( $relative_src ),
$this->get_dependencies( $relative_src, $deps ),
$this->get_file_version( $relative_src ),
true
);
if ( $has_i18n && function_exists( 'wp_set_script_translations' ) ) {
wp_set_script_translations(
$handle,
'woo-gutenberg-products-block',
$this->package->get_path( 'languages' )
);
}
}
/**
* Queues a block script.
*
* @since $VID:$
*
* @param string $name Name of the script used to identify the file inside build folder.
*/
public function register_block_script( $name ) {
$src = 'build/' . $name . '.js';
$handle = 'wc-' . $name;
$this->register_script( $handle, $src );
wp_enqueue_script( $handle );
}
/**
* Registers a style according to `wp_register_style`.
*
* @since $VID:$
*
* @param string $handle Name of the stylesheet. Should be unique.
* @param string $src Full URL of the stylesheet, or path of the stylesheet relative to the WordPress root directory.
* @param array $deps Optional. An array of registered stylesheet handles this stylesheet depends on. Default empty array.
* @param string $media Optional. The media for which this stylesheet has been defined. Default 'all'. Accepts media types like
* 'all', 'print' and 'screen', or media queries like '(orientation: portrait)' and '(max-width: 640px)'.
*/
public function register_style( $handle, $src, $deps = [], $media = 'all' ) {
$filename = str_replace( plugins_url( '/', __DIR__ ), '', $src );
$ver = $this->get_file_version( $filename );
wp_register_style( $handle, $src, $deps, $ver, $media );
}
}

View File

@ -0,0 +1,268 @@
<?php
/**
* Holds data registered for output on the current view session when
* `wc-settings` is enqueued (directly or via dependency)
*
* @package WooCommerce/Blocks
* @since $VID:$
*/
namespace Automattic\WooCommerce\Blocks\Assets;
use Exception;
use InvalidArgumentException;
/**
* Class instance for registering data used on the current view session by
* assets.
*
* @since $VID:$
*/
class AssetDataRegistry {
/**
* Contains registered data.
*
* @var array
*/
private $data = [];
/**
* Lazy data is an array of closures that will be invoked just before
* asset data is generated for the enqueued script.
*
* @var array
*/
private $lazy_data = [];
/**
* Asset handle for registered data.
*
* @var string
*/
private $handle = 'wc-settings';
/**
* Asset API interface for various asset registration.
*
* @var API
*/
private $api;
/**
* Constructor
*
* @param Api $asset_api Asset API interface for various asset registration.
*/
public function __construct( Api $asset_api ) {
$this->api = $asset_api;
$this->init();
}
/**
* Hook into WP asset registration for enqueueing asset data.
*/
protected function init() {
add_action( 'init', array( $this, 'register_data_script' ) );
add_action( 'wp_print_footer_scripts', array( $this, 'enqueue_asset_data' ), 1 );
add_action( 'admin_print_footer_scripts', array( $this, 'enqueue_asset_data' ), 1 );
}
/**
* Exposes core asset data
*
* @return array An array containing core data.
*/
protected function get_core_data() {
global $wp_locale;
$currency = get_woocommerce_currency();
return [
'adminUrl' => admin_url(),
'countries' => WC()->countries->get_countries(),
'currency' => [
'code' => $currency,
'precision' => wc_get_price_decimals(),
'symbol' => html_entity_decode( get_woocommerce_currency_symbol( $currency ) ),
'symbolPosition' => get_option( 'woocommerce_currency_pos' ),
'decimalSeparator' => wc_get_price_decimal_separator(),
'thousandSeparator' => wc_get_price_thousand_separator(),
'priceFormat' => html_entity_decode( get_woocommerce_price_format() ),
],
'locale' => [
'siteLocale' => get_locale(),
'userLocale' => get_user_locale(),
'weekdaysShort' => array_values( $wp_locale->weekday_abbrev ),
],
'orderStatuses' => $this->get_order_statuses( wc_get_order_statuses() ),
'siteTitle' => get_bloginfo( 'name ' ),
'wcAssetUrl' => plugins_url( 'assets/', WC_PLUGIN_FILE ),
];
}
/**
* Returns block-related data for enqueued wc-settings script.
* Format order statuses by removing a leading 'wc-' if present.
*
* @param array $statuses Order statuses.
* @return array formatted statuses.
*/
protected function get_order_statuses( $statuses ) {
$formatted_statuses = array();
foreach ( $statuses as $key => $value ) {
$formatted_key = preg_replace( '/^wc-/', '', $key );
$formatted_statuses[ $formatted_key ] = $value;
}
return $formatted_statuses;
}
/**
* Used for on demand initialization of asset data and registering it with
* the internal data registry.
*
* Note: core data will overwrite any externally registered data via the api.
*/
protected function initialize_core_data() {
/**
* Low level hook for registration of new data late in the cycle.
*
* Developers, do not use this hook as it is likely to be removed.
* Instead, use the data api:
* wc_blocks_container()
* ->get( Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry::class )
* ->add( $key, $value )
*/
$settings = apply_filters(
'woocommerce_shared_settings',
$this->data
);
// note this WILL wipe any data already registered to these keys because
// they are protected.
$this->data = array_merge_recursive( $settings, $this->get_core_data() );
}
/**
* Loops through each registered lazy data callback and adds the returned
* value to the data array.
*
* This method is executed right before preparing the data for printing to
* the rendered screen.
*
* @return void
*/
protected function execute_lazy_data() {
foreach ( $this->lazy_data as $key => $callback ) {
$this->data[ $key ] = $callback();
}
}
/**
* Exposes private registered data to child classes.
*
* @return array The registered data on the private data property
*/
protected function get() {
return $this->data;
}
/**
* Interface for adding data to the registry.
*
* @param string $key The key used to reference the data being registered.
* You can only register data that is not already in the
* registry identified by the given key.
* @param mixed $data If not a function, registered to the registry as is.
* If a function, then the callback is invoked right
* before output to the screen.
*
* @throws InvalidArgumentException Only throws when site is in debug mode.
* Always logs the error.
*/
public function add( $key, $data ) {
try {
$this->add_data( $key, $data );
} catch ( Exception $e ) {
if ( $this->debug() ) {
// bubble up.
throw $e;
}
wc_caught_exception( $e, __METHOD__, [ $key, $data ] );
}
}
/**
* Callback for registering the data script via WordPress API.
*
* @return void
*/
public function register_data_script() {
$this->api->register_script(
$this->handle,
'build/wc-settings.js',
[],
false
);
}
/**
* Callback for enqueuing asset data via the WP api.
*
* Note: while this is hooked into print/admin_print_scripts, it still only
* happens if the script attached to `wc-settings` handle is enqueued. This
* is done to allow for any potentially expensive data generation to only
* happen for routes that need it.
*/
public function enqueue_asset_data() {
if ( wp_script_is( $this->handle, 'enqueued' ) ) {
$this->initialize_core_data();
$this->execute_lazy_data();
$data = rawurlencode( wp_json_encode( $this->data ) );
wp_add_inline_script(
$this->handle,
"var wcSettings = wcSettings || JSON.parse( decodeURIComponent( '"
. esc_js( $data )
. "' ) );",
'before'
);
}
}
/**
* See self::add() for docs.
*
* @param string $key Key for the data.
* @param mixed $data Value for the data.
*
* @throws InvalidArgumentException If key is not a string or already
* exists in internal data cache.
*/
protected function add_data( $key, $data ) {
if ( ! is_string( $key ) ) {
if ( $this->debug() ) {
throw new InvalidArgumentException(
'Key for the data being registered must be a string'
);
}
}
if ( isset( $this->data[ $key ] ) ) {
if ( $this->debug() ) {
throw new InvalidArgumentException(
'Overriding existing data with an already registered key is not allowed'
);
}
return;
}
if ( \method_exists( $data, '__invoke' ) ) {
$this->lazy_data[ $key ] = $data;
return;
}
$this->data[ $key ] = $data;
}
/**
* Exposes whether the current site is in debug mode or not.
*
* @return boolean True means the site is in debug mode.
*/
protected function debug() {
return defined( 'WP_DEBUG' ) && WP_DEBUG;
}
}

View File

@ -0,0 +1,79 @@
<?php
/**
* Backwards Compatibility file for plugins using wcSettings in prior versions
*
* @package WooCommerce/Blocks
* @since $VID:$
*/
namespace Automattic\WooCommerce\Blocks\Assets;
/**
* Backwards Compatibility class for plugins using wcSettings in prior versions.
*
* Note: This will be removed at some point.
*
* @since $VID:$
*/
class BackCompatAssetDataRegistry extends AssetDataRegistry {
/**
* Overrides parent method.
*
* @see AssetDataRegistry::enqueue_asset_data
*/
public function enqueue_asset_data() {
$this->initialize_core_data();
$this->execute_lazy_data();
/**
* Back-compat filter, developers, use 'woocommerce_shared_settings'
* filter, not this one.
*
* @deprecated $VID:$
*/
$data = apply_filters(
'woocommerce_components_settings',
$this->get()
);
$data = rawurlencode( wp_json_encode( $data ) );
// for back compat with wc-admin (or other plugins) that expects
// wcSettings to be always available.
// @see https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/932.
echo '<script>';
echo "var wcSettings = wcSettings || JSON.parse( decodeURIComponent( '" . esc_js( $data ) . "' ) );";
echo '</script>';
}
/**
* Override parent method.
*
* @see AssetDataRegistry::get_core_data
*
* @return array An array of data to output for enqueued script.
*/
protected function get_core_data() {
global $wp_locale;
$core_data = parent::get_core_data();
return array_merge(
$core_data,
[
'siteLocale' => $core_data['locale']['siteLocale'],
'stockStatuses' => $core_data['orderStatuses'],
'dataEndpoints' => [],
'l10n' => [
'userLocale' => $core_data['locale']['userLocale'],
'weekdaysShort' => $core_data['locale']['weekdaysShort'],
],
'currency' => array_merge(
$core_data['currency'],
[
'position' => $core_data['currency']['symbolPosition'],
'decimal_separator' => $core_data['currency']['decimalSeparator'],
'thousand_separator' => $core_data['currency']['thousandSeparator'],
'price_format' => $core_data['currency']['priceFormat'],
]
),
]
);
}
}

View File

@ -0,0 +1,151 @@
<?php
/**
* Contains the Bootstrap class
*
* @package WooCommerce/Blocks
*/
namespace Automattic\WooCommerce\Blocks\Domain;
use Automattic\WooCommerce\Blocks\Assets as OldAssets;
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
use Automattic\WooCommerce\Blocks\Assets\BackCompatAssetDataRegistry;
use Automattic\WooCommerce\Blocks\Library;
use Automattic\WooCommerce\Blocks\Registry\Container;
use Automattic\WooCommerce\Blocks\RestApi;
/**
* Takes care of bootstrapping the plugin.
*
* @since $VID:$
*/
class Bootstrap {
/**
* Holds the Dependency Injection Container
*
* @var Container
*/
private $container;
/**
* Holds the Package instance
*
* @var Package
*/
private $package;
/**
* Constructor
*
* @param Container $container The Dependency Injection Container.
*/
public function __construct( Container $container ) {
$this->container = $container;
$this->package = $container->get( Package::class );
$this->init();
/**
* Usable as a safe event hook for when the plugin has been loaded.
*/
do_action( 'woocommerce_blocks_loaded' );
}
/**
* Init the package - load the blocks library and define constants.
*/
public function init() {
if ( ! $this->has_dependencies() ) {
return;
}
$this->remove_core_blocks();
if ( ! $this->is_built() ) {
$this->add_build_notice();
}
// register core dependencies with the container.
$this->container->register(
AssetApi::class,
function ( Container $container ) {
return new AssetApi( $container->get( Package::class ) );
}
);
$this->container->register(
AssetDataRegistry::class,
function( Container $container ) {
$asset_api = $container->get( AssetApi::class );
$load_back_compat = (
defined( 'WC_ADMIN_VERSION_NUMBER' )
&& version_compare( WC_ADMIN_VERSION_NUMBER, '0.19.0', '<=' )
) ||
(
version_compare( WC_VERSION, '3.7.0', '<=' )
);
return $load_back_compat
? new BackCompatAssetDataRegistry( $asset_api )
: new AssetDataRegistry( $asset_api );
}
);
// load AssetDataRegistry.
$this->container->get( AssetDataRegistry::class );
Library::init();
OldAssets::init();
RestApi::init();
}
/**
* Check dependencies exist.
*
* @return boolean
*/
protected function has_dependencies() {
return class_exists( 'WooCommerce' ) && function_exists( 'register_block_type' );
}
/**
* See if files have been built or not.
*
* @return bool
*/
protected function is_built() {
return file_exists(
$this->package->get_path( 'build/featured-product.js' )
);
}
/**
* Add a notice stating that the build has not been done yet.
*/
protected function add_build_notice() {
add_action(
'admin_notices',
function() {
echo '<div class="error"><p>';
printf(
/* Translators: %1$s is the install command, %2$s is the build command, %3$s is the watch command. */
esc_html__( 'WooCommerce Blocks development mode requires files to be built. From the plugin directory, run %1$s to install dependencies, %2$s to build the files or %3$s to build the files and watch for changes.', 'woo-gutenberg-products-block' ),
'<code>npm install</code>',
'<code>npm run build</code>',
'<code>npm start</code>'
);
echo '</p></div>';
}
);
}
/**
* Remove core blocks (for 3.6 and above).
*/
protected function remove_core_blocks() {
remove_action( 'init', array( 'WC_Block_Library', 'init' ) );
remove_action( 'init', array( 'WC_Block_Library', 'register_blocks' ) );
remove_action( 'init', array( 'WC_Block_Library', 'register_assets' ) );
remove_filter( 'block_categories', array( 'WC_Block_Library', 'add_block_category' ) );
remove_action( 'admin_print_footer_scripts', array( 'WC_Block_Library', 'print_script_settings' ), 1 );
remove_action( 'init', array( 'WGPB_Block_Library', 'init' ) );
}
}

View File

@ -0,0 +1,95 @@
<?php
/**
* Returns information about the package and handles init.
*
* @package WooCommerce/Blocks
*/
namespace Automattic\WooCommerce\Blocks\Domain;
/**
* Main package class.
*
* @since $VID:$
*/
class Package {
/**
* Holds the current version for the plugin.
*
* @var string
*/
private $version;
/**
* Holds the main path to the plugin directory.
*
* @var string
*/
private $path;
/**
* Holds the path to the main plugin file (entry)
*
* @var string
*/
private $plugin_file;
/**
* Constructor
*
* @param string $version Version of the plugin.
* @param string $plugin_file Path to the main plugin file.
*/
public function __construct( $version, $plugin_file ) {
$this->version = $version;
$this->plugin_file = $plugin_file;
$this->path = dirname( $plugin_file );
}
/**
* Returns the version of the plugin.
*
* @return string
*/
public function get_version() {
return $this->version;
}
/**
* Returns the path to the main plugin file.
*
* @return string
*/
public function get_plugin_file() {
return $this->plugin_file;
}
/**
* Returns the path to the plugin directory.
*
* @param string $relative_path If provided, the relative path will be
* appended to the plugin path.
*
* @return string
*/
public function get_path( $relative_path = '' ) {
return '' === $relative_path
? trailingslashit( $this->path )
: trailingslashit( $this->path ) . $relative_path;
}
/**
* Returns the url to the plugin directory.
*
* @param string $relative_url If provided, the relative url will be
* appended to the plugin url.
*
* @return string
*/
public function get_url( $relative_url = '' ) {
return '' === $relative_url
? plugin_dir_url( $this->get_plugin_file() )
: plugin_dir_url( $this->get_plugin_file() ) . $relative_url;
}
}

View File

@ -7,45 +7,36 @@
namespace Automattic\WooCommerce\Blocks; namespace Automattic\WooCommerce\Blocks;
use Automattic\WooCommerce\Blocks\Domain\Package as NewPackage;
defined( 'ABSPATH' ) || exit; defined( 'ABSPATH' ) || exit;
/** /**
* Main package class. * Main package class.
*
* @deprecated $VID:$
*/ */
class Package { class Package {
/** /**
* Version. * For back compat this is provided. Ideally, you should register your
* class with Automattic\Woocommerce\Blocks\Container and make Package a
* dependency.
* *
* @var string * @since $VID:$
* @return Package The Package instance class
*/ */
const VERSION = '2.5.0-dev'; protected static function get_package() {
return wc_blocks_container()->get( NewPackage::class );
/** }
* Stores if init has ran yet.
*
* @var boolean
*/
protected static $did_init = false;
/** /**
* Init the package - load the blocks library and define constants. * Init the package - load the blocks library and define constants.
*
* @since $VID:$ Handled by new NewPackage.
*/ */
public static function init() { public static function init() {
if ( true === self::$did_init || ! self::has_dependencies() ) { // noop.
return;
}
self::$did_init = true;
self::remove_core_blocks();
if ( ! self::is_built() ) {
self::add_build_notice();
}
Library::init();
Assets::init();
RestApi::init();
} }
/** /**
@ -54,7 +45,7 @@ class Package {
* @return string * @return string
*/ */
public static function get_version() { public static function get_version() {
return self::VERSION; return self::get_package()->get_version();
} }
/** /**
@ -63,57 +54,6 @@ class Package {
* @return string * @return string
*/ */
public static function get_path() { public static function get_path() {
return dirname( __DIR__ ); return self::get_package()->get_path();
} }
/**
* Check dependencies exist.
*
* @return boolean
*/
protected static function has_dependencies() {
return class_exists( 'WooCommerce' ) && function_exists( 'register_block_type' );
}
/**
* See if files have been built or not.
*
* @return bool
*/
protected static function is_built() {
return file_exists( dirname( __DIR__ ) . '/build/featured-product.js' );
}
/**
* Add a notice stating that the build has not been done yet.
*/
protected static function add_build_notice() {
add_action(
'admin_notices',
function() {
echo '<div class="error"><p>';
printf(
/* Translators: %1$s is the install command, %2$s is the build command, %3$s is the watch command. */
esc_html__( 'WooCommerce Blocks development mode requires files to be built. From the plugin directory, run %1$s to install dependencies, %2$s to build the files or %3$s to build the files and watch for changes.', 'woo-gutenberg-products-block' ),
'<code>npm install</code>',
'<code>npm run build</code>',
'<code>npm start</code>'
);
echo '</p></div>';
}
);
}
/**
* Remove core blocks (for 3.6 and below).
*/
protected static function remove_core_blocks() {
remove_action( 'init', array( 'WC_Block_Library', 'init' ) );
remove_action( 'init', array( 'WC_Block_Library', 'register_blocks' ) );
remove_action( 'init', array( 'WC_Block_Library', 'register_assets' ) );
remove_filter( 'block_categories', array( 'WC_Block_Library', 'add_block_category' ) );
remove_action( 'admin_print_footer_scripts', array( 'WC_Block_Library', 'print_script_settings' ), 1 );
remove_action( 'init', array( 'WGPB_Block_Library', 'init' ) );
}
} }

View File

@ -0,0 +1,60 @@
<?php
/**
* Holds the AbstractDependencyType class.
*
* @package WooCommerce\Blocks
*/
namespace Automattic\WooCommerce\Blocks\Registry;
/**
* An abstract class for dependency types.
*
* Dependency types are instances of a dependency used by the
* Dependency Injection Container for storing dependencies to invoke as they
* are needed.
*
* @since $VID:$
*/
abstract class AbstractDependencyType {
/**
* Holds a callable or value provided for this type.
*
* @var mixed
*/
private $callable_or_value;
/**
* Constructor
*
* @param mixed $callable_or_value A callable or value for the dependency
* type instance.
*/
public function __construct( $callable_or_value ) {
$this->callable_or_value = $callable_or_value;
}
/**
* Resolver for the internal dependency value.
*
* @param Container $container The Dependency Injection Container.
*
* @return mixed
*/
protected function resolve_value( Container $container ) {
$callback = $this->callable_or_value;
return \method_exists( $callback, '__invoke' )
? $callback( $container )
: $callback;
}
/**
* Retrieves the value stored internally for this DependencyType
*
* @param Container $container The Dependency Injection Container.
*
* @return void
*/
abstract public function get( Container $container );
}

View File

@ -0,0 +1,104 @@
<?php
/**
* Contains the Container class.
*
* @package WooCommerce\Blocks
*/
namespace Automattic\WooCommerce\Blocks\Registry;
use Closure;
use Exception;
/**
* A simple Dependency Injection Container
*
* This is used to manage dependencies used throughout the plugin.
*
* @since $VID:$
*/
class Container {
/**
* A map of Dependency Type objects used to resolve dependencies.
*
* @var AbstractDependencyType[]
*/
private $registry = [];
/**
* Public api for adding a factory to the container.
*
* Factory dependencies will have the instantiation callback invoked
* everytime the dependency is requested.
*
* Typical Usage:
*
* ```
* $container->register( MyClass::class, $container->factory( $mycallback ) );
* ```
*
* @param Closure $instantiation_callback This will be invoked when the
* dependency is required. It will
* receive an instance of this
* container so the callback can
* retrieve dependencies from the
* container.
*
* @return FactoryType An instance of the FactoryType dependency.
*/
public function factory( Closure $instantiation_callback ) {
return new FactoryType( $instantiation_callback );
}
/**
* Interface for registering a new dependency with the container.
*
* By default, the $value will be added as a shared dependency. This means
* that it will be a single instance shared among any other classes having
* that dependency.
*
* If you want a new instance everytime it's required, then wrap the value
* in a call to the factory method (@see Container::factory for example)
*
* Note: Currently if the provided id already is registered in the container,
* the provided value is ignored.
*
* @param string $id A unique string identifier for the provided value.
* Typically it's the fully qualified name for the
* dependency.
* @param mixed $value The value for the dependency. Typically, this is a
* closure that will create the class instance needed.
*/
public function register( $id, $value ) {
if ( empty( $this->registry[ $id ] ) ) {
if ( ! $value instanceof FactoryType ) {
$value = new SharedType( $value );
}
$this->registry[ $id ] = $value;
}
}
/**
* Interface for retrieving the dependency stored in the container for the
* given identifier.
*
* @param string $id The identifier for the dependency being retrieved.
* @throws Exception If there is no dependency for the given identifier in
* the container.
*
* @return mixed Typically a class instance.
*/
public function get( $id ) {
if ( ! isset( $this->registry[ $id ] ) ) {
// this is a developer facing exception, hence it is not localized.
throw new Exception(
sprintf(
'Cannot construct an instance of %s because it has not been registered.',
$id
)
);
}
return $this->registry[ $id ]->get( $this );
}
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Holds the FactoryType class
*
* @package WooCommerce/Blocks
*/
namespace Automattic\WooCommerce\Blocks\Registry;
/**
* Definition for the FactoryType dependency type.
*
* @since $VID:$
*/
class FactoryType extends AbstractDependencyType {
/**
* Invokes and returns the value from the stored internal callback.
*
* @param Container $container An instance of the dependency injection
* container.
*
* @return mixed
*/
public function get( Container $container ) {
return $this->resolve_value( $container );
}
}

View File

@ -0,0 +1,38 @@
<?php
/**
* Holds the SharedType class definition
*
* @package WooCommerce\Blocks
*/
namespace Automattic\WooCommerce\Blocks\Registry;
/**
* A definition for the SharedType dependency type.
*
* @since $VID:$
*/
class SharedType extends AbstractDependencyType {
/**
* Holds a cached instance of the value stored (or returned) internally.
*
* @var mixed
*/
private $shared_instance;
/**
* Returns the internal stored and shared value after initial generation.
*
* @param Container $container An instance of the dependency injection
* container.
*
* @return mixed
*/
public function get( Container $container ) {
if ( empty( $this->shared_instance ) ) {
$this->shared_instance = $this->resolve_value( $container );
}
return $this->shared_instance;
}
}

View File

@ -4,8 +4,12 @@ global.wp = {};
// wcSettings is required by @woocommerce/* packages // wcSettings is required by @woocommerce/* packages
global.wcSettings = { global.wcSettings = {
adminUrl: 'https://vagrant.local/wp/wp-admin/', adminUrl: 'https://vagrant.local/wp/wp-admin/',
locale: 'en-US', countries: [],
currency: { code: 'USD', precision: 2, symbol: '&#36;' }, currency: {
code: 'USD',
precision: 2,
symbol: '&#36;',
},
date: { date: {
dow: 0, dow: 0,
}, },
@ -18,7 +22,8 @@ global.wcSettings = {
refunded: 'Refunded', refunded: 'Refunded',
failed: 'Failed', failed: 'Failed',
}, },
l10n: { locale: {
siteLocale: 'en_US',
userLocale: 'en_US', userLocale: 'en_US',
weekdaysShort: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ], weekdaysShort: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ],
}, },

View File

@ -0,0 +1,55 @@
<?php
namespace Automattic\WooCommerce\Blocks\Tests\Assets;
use \WP_UnitTestCase;
use Automattic\WooCommerce\Blocks\Assets\Api;
use Automattic\WooCommerce\Blocks\Tests\Mocks\AssetDataRegistryMock;
use InvalidArgumentException;
/**
* Tests for the AssetDataRegistry class.
*
* @since $VID:$
*/
class AssetDataRegistry extends WP_UnitTestCase {
private $registry;
public function setUp() {
$this->registry = new AssetDataRegistryMock(
wc_blocks_container()->get( API::class )
);
}
public function test_initial_data() {
$this->assertEmpty( $this->registry->get() );
}
public function test_add_data() {
$this->registry->add( 'test', 'foo' );
$this->assertEquals( [ 'test' => 'foo' ], $this->registry->get() );
}
public function test_add_lazy_data() {
$lazy = function () {
return 'bar';
};
$this->registry->add( 'foo', $lazy );
// should not be in data yet
$this->assertEmpty( $this->registry->get() );
$this->registry->execute_lazy_data();
// should be in data now
$this->assertEquals( [ 'foo' => 'bar' ], $this->registry->get() );
}
public function test_invalid_key_on_adding_data() {
$this->expectException( InvalidArgumentException::class );
$this->registry->add( [ 'some_value' ], 'foo' );
}
public function test_already_existing_key_on_adding_data() {
$this->registry->add( 'foo', 'bar' );
$this->expectException( InvalidArgumentException::class );
$this->registry->add( 'foo', 'yar' );
}
}

View File

@ -0,0 +1,49 @@
<?php
/**
* Contains Tests for the main file (woocommerce-gutenberg-products-blocks.php)
* bootstrap.
*
* @package WooCommerce\Blocks\Tests
*/
namespace Automattic\WooCommerce\Blocks\Tests\Bootstrap;
use \WP_UnitTestCase;
use Automattic\WooCommerce\Blocks\Domain\Bootstrap;
use Automattic\WooCommerce\Blocks\Registry\Container;
/**
* Test class for the bootstrap in the plugin main file
*
* @since $VID:$
*/
class MainFile extends WP_UnitTestCase {
/**
* Holds an instance of the dependency injection container
*
* @var Container
*/
private $container;
/**
* Ensure that container is reset between tests.
*/
public function setUp() {
// reset container
$this->container = wc_blocks_container( true );
}
public function test_wc_blocks_container_returns_same_instance() {
$container = wc_blocks_container();
$this->assertSame( $container, $this->container );
}
public function test_wc_blocks_container_reset() {
$container = wc_blocks_container( true );
$this->assertNotSame( $container, $this->container );
}
public function wc_blocks_bootstrap() {
$this->assertInstanceOf( Bootstrap::class, wc_blocks_bootstrap() );
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace Automattic\WooCommerce\Blocks\Tests\Domain\Package;
use \WP_UnitTestCase;
use Automattic\WooCommerce\Blocks\Domain\Package as TestedPackage;
/**
* Tests the Package class
*
* @since $VID:$
*/
class Package extends WP_UnitTestCase {
private function get_package() {
return new TestedPackage( '1.0.0', __FILE__ );
}
public function test_get_version() {
$this->assertEquals( '1.0.0', $this->get_package()->get_version() );
}
public function test_get_plugin_file() {
$this->assertEquals( __FILE__, $this->get_package()->get_plugin_file() );
}
public function test_get_path() {
$package = $this->get_package();
// test without relative
$this->assertEquals( dirname( __FILE__ ) . '/', $package->get_path() );
//test with relative
$expect = dirname( __FILE__ ) . '/build/test';
$this->assertEquals( $expect, $package->get_path( 'build/test') );
}
public function test_get_url() {
$package = $this->get_package();
$test_url = plugin_dir_url( __FILE__ );
// test without relative
$this->assertEquals( $test_url, $package->get_url() );
//test with relative
$this->assertEquals(
$test_url . 'build/test',
$package->get_url( 'build/test' )
);
}
}

View File

@ -0,0 +1,78 @@
<?php
namespace Automattic\WooCommerce\Blocks\Tests\Registry;
use Automattic\WooCommerce\Blocks\Registry\Container as ContainerTest;
use Automattic\WooCommerce\Blocks\Registry\FactoryType;
use Automattic\WooCommerce\Blocks\Tests\Mocks\MockTestDependency;
use PHPUnit\Framework\TestCase;
/**
* Tests the Container functionality
*
* This also implicitly tests the FactoryType and SharedType classes.
*
* @since $VID:$
* @group testing
*/
class Container extends TestCase {
private $container;
public function setUp() {
$this->container = new ContainerTest;
}
public function test_factory() {
$factory = $this->container->factory( function () { return 'foo'; } );
$this->assertInstanceOf( FactoryType::class, $factory );
}
public function test_registering_factory_type() {
$this->container->register(
MockTestDependency::class,
$this->container->factory(
function () { return new MockTestDependency; }
)
);
$instanceA = $this->container->get( MockTestDependency::class );
$instanceB = $this->container->get( MockTestDependency::class );
// should not be the same instance;
$this->assertNotSame( $instanceA, $instanceB );
}
public function test_registering_shared_type() {
$this->container->register(
MockTestDependency::class,
function () { return new MockTestDependency; }
);
$instanceA = $this->container->get( MockTestDependency::class );
$instanceB = $this->container->get( MockTestDependency::class );
// should not be the same instance;
$this->assertSame( $instanceA, $instanceB );
}
public function test_registering_shared_type_dependent_on_another_shared_type() {
$this->container->register(
MockTestDependency::class . 'A',
function() { return new MockTestDependency; }
);
$this->container->register(
MockTestDependency::class . 'B',
function( $container ) {
return new MockTestDependency(
$container->get( MockTestDependency::class . 'A' )
);
}
);
$instanceA = $this->container->get( MockTestDependency::class . 'A' );
$instanceB = $this->container->get( MockTestDependency::class . 'B' );
// should not be the same instance
$this->assertNotSame( $instanceA, $instanceB );
// dependency on B should be the same as A
$this->assertSame( $instanceA, $instanceB->dependency );
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Automattic\WooCommerce\Blocks\Tests\Mocks;
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
class AssetDataRegistryMock extends AssetDataRegistry {
private $debug = true;
public function execute_lazy_data() {
parent::execute_lazy_data();
}
public function get() {
return parent::get();
}
public function set_debug( $debug ) {
$this->debug = $debug;
}
protected function debug() {
return $this->debug;
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Automattic\WooCommerce\Blocks\Tests\Mocks;
class MockTestDependency {
public $dependency;
public function __construct( $dependency = null ) {
$this->dependency = $dependency;
}
};

View File

@ -2,6 +2,7 @@
* External dependencies * External dependencies
*/ */
const path = require( 'path' ); const path = require( 'path' );
const { kebabCase } = require( 'lodash' );
const MergeExtractFilesPlugin = require( './bin/merge-extract-files-webpack-plugin' ); const MergeExtractFilesPlugin = require( './bin/merge-extract-files-webpack-plugin' );
const MiniCssExtractPlugin = require( 'mini-css-extract-plugin' ); const MiniCssExtractPlugin = require( 'mini-css-extract-plugin' );
const { CleanWebpackPlugin } = require( 'clean-webpack-plugin' ); const { CleanWebpackPlugin } = require( 'clean-webpack-plugin' );
@ -36,10 +37,16 @@ const baseConfig = {
}, },
}; };
const alias = {
'@woocommerce/block-settings': path.resolve(
__dirname,
'assets/js/settings/blocks'
),
};
const requestToExternal = ( request ) => { const requestToExternal = ( request ) => {
const wcDepMap = { const wcDepMap = {
'@woocommerce/settings': [ 'wc', 'wc-shared-settings' ], '@woocommerce/settings': { this: [ 'wc', 'wcSettings' ] },
'@woocommerce/block-settings': [ 'wc', 'wc-block-settings' ],
}; };
if ( wcDepMap[ request ] ) { if ( wcDepMap[ request ] ) {
return wcDepMap[ request ]; return wcDepMap[ request ];
@ -48,8 +55,8 @@ const requestToExternal = ( request ) => {
const requestToHandle = ( request ) => { const requestToHandle = ( request ) => {
const wcHandleMap = { const wcHandleMap = {
'@woocommerce/settings': 'wc-shared-settings', '@woocommerce/settings': 'wc-settings',
'@woocommerce/block-settings': 'wc-block-settings', '@woocommerce/block-settings': 'wc-settings',
}; };
if ( wcHandleMap[ request ] ) { if ( wcHandleMap[ request ] ) {
return wcHandleMap[ request ]; return wcHandleMap[ request ];
@ -59,11 +66,12 @@ const requestToHandle = ( request ) => {
const CoreConfig = { const CoreConfig = {
...baseConfig, ...baseConfig,
entry: { entry: {
'wc-shared-settings': './assets/js/settings/shared/index.js', wcSettings: './assets/js/settings/shared/index.js',
'wc-block-settings': './assets/js/settings/blocks/index.js',
}, },
output: { output: {
filename: '[name].js', filename: ( chunkData ) => {
return `${ kebabCase( chunkData.chunk.name ) }.js`;
},
path: path.resolve( __dirname, './build/' ), path: path.resolve( __dirname, './build/' ),
library: [ 'wc', '[name]' ], library: [ 'wc', '[name]' ],
libraryTarget: 'this', libraryTarget: 'this',
@ -241,6 +249,7 @@ const GutenbergBlocksConfig = {
requestToHandle, requestToHandle,
} ), } ),
], ],
resolve: { alias },
}; };
const BlocksFrontendConfig = { const BlocksFrontendConfig = {
@ -326,6 +335,7 @@ const BlocksFrontendConfig = {
requestToHandle, requestToHandle,
} ), } ),
], ],
resolve: { alias },
}; };
module.exports = [ CoreConfig, GutenbergBlocksConfig, BlocksFrontendConfig ]; module.exports = [ CoreConfig, GutenbergBlocksConfig, BlocksFrontendConfig ];

View File

@ -68,4 +68,49 @@ if ( is_readable( $autoloader ) ) {
return; return;
} }
add_action( 'plugins_loaded', array( '\Automattic\WooCommerce\Blocks\Package', 'init' ) ); /**
* Loads the dependency injection container for woocommerce blocks.
*
* @param boolean $reset Used to reset the container to a fresh instance.
* Note: this means all dependencies will be reconstructed.
*/
function wc_blocks_container( $reset = false ) {
static $container;
if (
! $container instanceof Automattic\WooCommerce\Blocks\Registry\Container
|| $reset
) {
$container = new Automattic\WooCommerce\Blocks\Registry\Container();
// register Package.
$container->register(
Automattic\WooCommerce\Blocks\Domain\Package::class,
function ( $container ) {
return new Automattic\WooCommerce\Blocks\Domain\Package(
'2.5.0-dev',
__FILE__
);
}
);
// register Bootstrap.
$container->register(
Automattic\WooCommerce\Blocks\Domain\Bootstrap::class,
function ( $container ) {
return new Automattic\WooCommerce\Blocks\Domain\Bootstrap(
$container
);
}
);
}
return $container;
}
add_action( 'plugins_loaded', 'wc_blocks_bootstrap' );
/**
* Boostrap WooCommerce Blocks App
*/
function wc_blocks_bootstrap() {
// initialize bootstrap.
wc_blocks_container()->get(
Automattic\WooCommerce\Blocks\Domain\Bootstrap::class
);
}