From 3136704fe34b77fc7f5235aeafa54fee6d0cf21d Mon Sep 17 00:00:00 2001 From: Ari Stathopoulos Date: Fri, 15 Jul 2022 11:18:03 +0300 Subject: [PATCH] Experiment: Style Engine Rules & Store objects (#42222) * Add a CSS_Rule object * Add a rules store * some inline docs & cleanup on the store * typos & fixes * remove parent relations for rules * escape the selector * Allow for multiple named instances of the store * Use a single declarations object per rule * Allow chaining methods * Remove esc_html * Add a default value for store_name * use static() * Adding tests * rename get_rule to add_rule * add a remove_rule method * minor performance improvement * cleanup & added a return doc Co-authored-by: ramonjd --- lib/load.php | 6 + .../class-wp-style-engine-css-rule.php | 115 ++++++++++++++++++ .../class-wp-style-engine-css-rules-store.php | 94 ++++++++++++++ .../class-wp-style-engine-css-rule-test.php | 96 +++++++++++++++ ...s-wp-style-engine-css-rules-store-test.php | 115 ++++++++++++++++++ tools/webpack/packages.js | 2 + 6 files changed, 428 insertions(+) create mode 100644 packages/style-engine/class-wp-style-engine-css-rule.php create mode 100644 packages/style-engine/class-wp-style-engine-css-rules-store.php create mode 100644 packages/style-engine/phpunit/class-wp-style-engine-css-rule-test.php create mode 100644 packages/style-engine/phpunit/class-wp-style-engine-css-rules-store-test.php diff --git a/lib/load.php b/lib/load.php index b125ef04913a62..57f7993440f57a 100644 --- a/lib/load.php +++ b/lib/load.php @@ -116,6 +116,12 @@ function gutenberg_is_experiment_enabled( $name ) { if ( file_exists( __DIR__ . '/../build/style-engine/class-wp-style-engine-css-declarations-gutenberg.php' ) ) { require_once __DIR__ . '/../build/style-engine/class-wp-style-engine-css-declarations-gutenberg.php'; } +if ( file_exists( __DIR__ . '/../build/style-engine/class-wp-style-engine-css-rule-gutenberg.php' ) ) { + require_once __DIR__ . '/../build/style-engine/class-wp-style-engine-css-rule-gutenberg.php'; +} +if ( file_exists( __DIR__ . '/../build/style-engine/class-wp-style-engine-css-rules-store-gutenberg.php' ) ) { + require_once __DIR__ . '/../build/style-engine/class-wp-style-engine-css-rules-store-gutenberg.php'; +} // Block supports overrides. require __DIR__ . '/block-supports/utils.php'; diff --git a/packages/style-engine/class-wp-style-engine-css-rule.php b/packages/style-engine/class-wp-style-engine-css-rule.php new file mode 100644 index 00000000000000..855dd7ecc4b712 --- /dev/null +++ b/packages/style-engine/class-wp-style-engine-css-rule.php @@ -0,0 +1,115 @@ + value pairs), + * or a WP_Style_Engine_CSS_Declarations object. + */ + public function __construct( $selector = '', $declarations = array() ) { + $this->set_selector( $selector ); + $this->set_declarations( $declarations ); + } + + /** + * Set the selector. + * + * @param string $selector The CSS selector. + * + * @return WP_Style_Engine_CSS_Rule Returns the object to allow chaining of methods. + */ + public function set_selector( $selector ) { + if ( empty( $selector ) ) { + return; + } + $this->selector = $selector; + + return $this; + } + + /** + * Set the declarations. + * + * @param array|WP_Style_Engine_CSS_Declarations $declarations An array of declarations (property => value pairs), + * or a WP_Style_Engine_CSS_Declarations object. + * + * @return WP_Style_Engine_CSS_Rule Returns the object to allow chaining of methods. + */ + public function set_declarations( $declarations ) { + $is_declarations_object = ! is_array( $declarations ); + $declarations_array = $is_declarations_object ? $declarations->get_declarations() : $declarations; + + if ( null === $this->declarations && $is_declarations_object ) { + $this->declarations = $declarations; + return $this; + } + if ( null === $this->declarations ) { + $this->declarations = new WP_Style_Engine_CSS_Declarations( $declarations_array ); + } + $this->declarations->add_declarations( $declarations_array ); + + return $this; + } + + /** + * Get the declarations object. + * + * @return WP_Style_Engine_CSS_Declarations + */ + public function get_declarations() { + return $this->declarations; + } + + /** + * Get the full selector. + * + * @return string + */ + public function get_selector() { + return $this->selector; + } + + /** + * Get the CSS. + * + * @return string + */ + public function get_css() { + return $this->get_selector() . ' {' . $this->declarations->get_declarations_string() . '}'; + } +} diff --git a/packages/style-engine/class-wp-style-engine-css-rules-store.php b/packages/style-engine/class-wp-style-engine-css-rules-store.php new file mode 100644 index 00000000000000..1dc4da265accc7 --- /dev/null +++ b/packages/style-engine/class-wp-style-engine-css-rules-store.php @@ -0,0 +1,94 @@ +rules; + } + + /** + * Get a WP_Style_Engine_CSS_Rule object by its selector. + * If the rule does not exist, it will be created. + * + * @param string $selector The CSS selector. + * + * @return WP_Style_Engine_CSS_Rule|null Returns a WP_Style_Engine_CSS_Rule object, or null if the selector is empty. + */ + public function add_rule( $selector ) { + + $selector = trim( $selector ); + + // Bail early if there is no selector. + if ( empty( $selector ) ) { + return; + } + + // Create the rule if it doesn't exist. + if ( empty( $this->rules[ $selector ] ) ) { + $this->rules[ $selector ] = new WP_Style_Engine_CSS_Rule( $selector ); + } + + return $this->rules[ $selector ]; + } + + /** + * Remove a selector from the store. + * + * @param string $selector The CSS selector. + * + * @return void + */ + public function remove_rule( $selector ) { + unset( $this->rules[ $selector ] ); + } +} diff --git a/packages/style-engine/phpunit/class-wp-style-engine-css-rule-test.php b/packages/style-engine/phpunit/class-wp-style-engine-css-rule-test.php new file mode 100644 index 00000000000000..9a42ade4acb16f --- /dev/null +++ b/packages/style-engine/phpunit/class-wp-style-engine-css-rule-test.php @@ -0,0 +1,96 @@ + '10px', + 'font-size' => '2rem', + ); + $css_declarations = new WP_Style_Engine_CSS_Declarations( $input_declarations ); + $css_rule = new WP_Style_Engine_CSS_Rule( $selector, $css_declarations ); + + $this->assertSame( $selector, $css_rule->get_selector() ); + + $expected = "$selector {{$css_declarations->get_declarations_string()}}"; + $this->assertSame( $expected, $css_rule->get_css() ); + } + + /** + * Test dedupe declaration properties. + */ + public function test_dedupe_properties_in_rules() { + $selector = '.taggart'; + $first_declaration = array( + 'font-size' => '2rem', + ); + $overwrite_first_declaration = array( + 'font-size' => '4px', + ); + $css_rule = new WP_Style_Engine_CSS_Rule( $selector, $first_declaration ); + $css_rule->set_declarations( new WP_Style_Engine_CSS_Declarations( $overwrite_first_declaration ) ); + + $expected = '.taggart {font-size: 4px;}'; + $this->assertSame( $expected, $css_rule->get_css() ); + } + + /** + * Should set selector and rules on instantiation. + */ + public function test_set_declarations() { + // Declarations using a WP_Style_Engine_CSS_Declarations object. + $some_css_declarations = new WP_Style_Engine_CSS_Declarations( array( 'margin-top' => '10px' ) ); + // Declarations using a property => value array. + $some_more_css_declarations = array( 'font-size' => '1rem' ); + $css_rule = new WP_Style_Engine_CSS_Rule( '.hill-street-blues', $some_css_declarations ); + $css_rule->set_declarations( $some_more_css_declarations ); + + $expected = '.hill-street-blues {margin-top: 10px; font-size: 1rem;}'; + $this->assertSame( $expected, $css_rule->get_css() ); + } + + /** + * Should set selector and rules on instantiation. + */ + public function test_set_selector() { + $selector = '.taggart'; + $css_rule = new WP_Style_Engine_CSS_Rule( $selector ); + + $this->assertSame( $selector, $css_rule->get_selector() ); + + $css_rule->set_selector( '.law-and-order' ); + + $this->assertSame( '.law-and-order', $css_rule->get_selector() ); + } + + /** + * Should set selector and rules on instantiation. + */ + public function test_get_css() { + $selector = '.chips'; + $input_declarations = array( + 'margin-top' => '10px', + 'font-size' => '2rem', + ); + $css_declarations = new WP_Style_Engine_CSS_Declarations( $input_declarations ); + $css_rule = new WP_Style_Engine_CSS_Rule( $selector, $css_declarations ); + $expected = "$selector {{$css_declarations->get_declarations_string()}}"; + + $this->assertSame( $expected, $css_rule->get_css() ); + } +} diff --git a/packages/style-engine/phpunit/class-wp-style-engine-css-rules-store-test.php b/packages/style-engine/phpunit/class-wp-style-engine-css-rules-store-test.php new file mode 100644 index 00000000000000..b06c4d11d66f30 --- /dev/null +++ b/packages/style-engine/phpunit/class-wp-style-engine-css-rules-store-test.php @@ -0,0 +1,115 @@ +assertInstanceOf( 'WP_Style_Engine_CSS_Rules_Store', $new_pancakes_store ); + } + + /** + * Should return previously created store when the same selector key is passed. + */ + public function test_get_store() { + $new_fish_store = WP_Style_Engine_CSS_Rules_Store::get_store( 'fish-n-chips' ); + $selector = '.haddock'; + + $new_fish_store->add_rule( $selector )->get_selector(); + $this->assertEquals( $selector, $new_fish_store->add_rule( $selector )->get_selector() ); + + $the_same_fish_store = WP_Style_Engine_CSS_Rules_Store::get_store( 'fish-n-chips' ); + $this->assertEquals( $selector, $the_same_fish_store->add_rule( $selector )->get_selector() ); + } + + /** + * Should return a stored rule. + */ + public function test_add_rule() { + $new_pie_store = WP_Style_Engine_CSS_Rules_Store::get_store( 'meat-pie' ); + $selector = '.wp-block-sauce a:hover'; + $store_rule = $new_pie_store->add_rule( $selector ); + $expected = "$selector {}"; + $this->assertEquals( $expected, $store_rule->get_css() ); + + $pie_declarations = array( + 'color' => 'brown', + 'border-color' => 'yellow', + 'border-radius' => '10rem', + ); + $css_declarations = new WP_Style_Engine_CSS_Declarations( $pie_declarations ); + $store_rule->set_declarations( $css_declarations ); + + $store_rule = $new_pie_store->add_rule( $selector ); + $expected = "$selector {{$css_declarations->get_declarations_string()}}"; + $this->assertEquals( $expected, $store_rule->get_css() ); + } + + /** + * Should return all stored rules. + */ + public function test_get_all_rules() { + $new_pizza_store = WP_Style_Engine_CSS_Rules_Store::get_store( 'pizza-with-mozzarella' ); + $selector = '.wp-block-anchovies a:hover'; + $store_rule = $new_pizza_store->add_rule( $selector ); + $expected = array( + $selector => $store_rule, + ); + + $this->assertEquals( $expected, $new_pizza_store->get_all_rules() ); + + $pizza_declarations = array( + 'color' => 'red', + 'border-color' => 'yellow', + 'border-radius' => '10rem', + ); + $css_declarations = new WP_Style_Engine_CSS_Declarations( $pizza_declarations ); + $store_rule->set_declarations( array( $css_declarations ) ); + + $expected = array( + $selector => $store_rule, + ); + $this->assertEquals( $expected, $new_pizza_store->get_all_rules() ); + + $new_pizza_declarations = array( + 'color' => 'red', + 'border-color' => 'red', + 'font-size' => '10rem', + ); + $css_declarations = new WP_Style_Engine_CSS_Declarations( $new_pizza_declarations ); + $store_rule->set_declarations( array( $css_declarations ) ); + + $expected = array( + $selector => $store_rule, + ); + $this->assertEquals( $expected, $new_pizza_store->get_all_rules() ); + + $new_selector = '.wp-block-mushroom a:hover'; + $newer_pizza_declarations = array( + 'padding' => '100px', + ); + $new_store_rule = $new_pizza_store->add_rule( $new_selector ); + $css_declarations = new WP_Style_Engine_CSS_Declarations( $newer_pizza_declarations ); + $new_store_rule->set_declarations( array( $css_declarations ) ); + + $expected = array( + $selector => $store_rule, + $new_selector => $new_store_rule, + ); + $this->assertEquals( $expected, $new_pizza_store->get_all_rules() ); + } +} diff --git a/tools/webpack/packages.js b/tools/webpack/packages.js index c13ceea5de9ecc..2f6720d579452f 100644 --- a/tools/webpack/packages.js +++ b/tools/webpack/packages.js @@ -37,6 +37,8 @@ const bundledPackagesPhpConfig = [ to: 'build/style-engine/', replaceClasses: [ 'WP_Style_Engine_CSS_Declarations', + 'WP_Style_Engine_CSS_Rules_Store', + 'WP_Style_Engine_CSS_Rule', 'WP_Style_Engine', ], },