From 9610fae64b9d51d9d40af560d3928fc9fcfcfced Mon Sep 17 00:00:00 2001 From: YvesCesar Date: Tue, 29 Sep 2026 17:06:12 -0400 Subject: [PATCH 1/5] test: add an integration suite running against wordpress and woocommerce Signed-off-by: YvesCesar --- .gitignore | 1 + composer.json | 37 +- composer.lock | 395 +++- phpcs.xml.dist | 1 + phpstan.neon.dist | 13 +- phpunit-integration.xml.dist | 23 + readme.txt | 15 + tests/Integration/Inc/AccountTest.php | 218 ++ tests/Integration/Inc/AssetsTest.php | 67 + tests/Integration/Inc/CheckoutTest.php | 35 + tests/Integration/Inc/CpfCnpjTest.php | 155 ++ tests/Integration/Inc/FooterFragmentTest.php | 341 +++ .../Integration/Inc/GithubSiteWebhookTest.php | 545 +++++ tests/Integration/Inc/LogoTest.php | 67 + tests/Integration/Inc/PlansTest.php | 41 + tests/Integration/Inc/ProductTest.php | 32 + tests/Integration/Inc/RegistrationTest.php | 103 + tests/Integration/Inc/ThemeSetupTest.php | 21 + .../Woocommerce/Myaccount/FormLoginTest.php | 127 ++ tests/Support/GithubApi.php | 53 + tests/Support/StaticSite.php | 62 + tests/Support/StoreFactory.php | 53 + tests/bootstrap-integration.php | 76 + tests/wp-tests-config.php | 40 + vendor-bin/phpstan/composer.json | 1 + vendor-bin/phpstan/composer.lock | 111 +- vendor-bin/phpunit/composer.json | 13 + vendor-bin/phpunit/composer.lock | 1985 +++++++++++++++++ 28 files changed, 4622 insertions(+), 9 deletions(-) create mode 100644 phpunit-integration.xml.dist create mode 100644 tests/Integration/Inc/AccountTest.php create mode 100644 tests/Integration/Inc/AssetsTest.php create mode 100644 tests/Integration/Inc/CheckoutTest.php create mode 100644 tests/Integration/Inc/CpfCnpjTest.php create mode 100644 tests/Integration/Inc/FooterFragmentTest.php create mode 100644 tests/Integration/Inc/GithubSiteWebhookTest.php create mode 100644 tests/Integration/Inc/LogoTest.php create mode 100644 tests/Integration/Inc/PlansTest.php create mode 100644 tests/Integration/Inc/ProductTest.php create mode 100644 tests/Integration/Inc/RegistrationTest.php create mode 100644 tests/Integration/Inc/ThemeSetupTest.php create mode 100644 tests/Integration/Woocommerce/Myaccount/FormLoginTest.php create mode 100644 tests/Support/GithubApi.php create mode 100644 tests/Support/StaticSite.php create mode 100644 tests/Support/StoreFactory.php create mode 100644 tests/bootstrap-integration.php create mode 100644 tests/wp-tests-config.php create mode 100644 vendor-bin/phpunit/composer.json create mode 100644 vendor-bin/phpunit/composer.lock diff --git a/.gitignore b/.gitignore index 6a4497a..70181d4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ languages/*.mo /vendor/ /vendor-bin/*/vendor/ +/.phpunit.result.cache diff --git a/composer.json b/composer.json index 78612b8..7af7114 100644 --- a/composer.json +++ b/composer.json @@ -3,24 +3,50 @@ "description": "WordPress theme for the LibreSign SaaS", "type": "wordpress-theme", "license": "GPL-2.0-or-later", + "repositories": [ + { + "type": "composer", + "url": "https://wpackagist.org", + "only": [ + "wpackagist-plugin/*" + ] + } + ], "require": { "php": ">=8.3 <8.6" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8" + "bamarni/composer-bin-plugin": "^1.8", + "composer/installers": "^2.3", + "roots/wordpress": "^7.0", + "wp-phpunit/wp-phpunit": "^7.0", + "wpackagist-plugin/woocommerce": "^11.0" + }, + "autoload-dev": { + "psr-4": { + "LibreSign\\WordPressTheme\\Tests\\": "tests/" + } }, "config": { "platform": { "php": "8.3" }, "allow-plugins": { - "bamarni/composer-bin-plugin": true + "bamarni/composer-bin-plugin": true, + "composer/installers": true, + "roots/wordpress-core-installer": true } }, "extra": { "bamarni-bin": { "bin-links": true, "forward-command": true + }, + "wordpress-install-dir": "vendor/wordpress", + "installer-paths": { + "vendor/test-plugins/{$name}/": [ + "type:wordpress-plugin" + ] } }, "scripts": { @@ -28,10 +54,15 @@ "cs": "phpcs", "cs:fix": "phpcbf", "stan": "phpstan analyse --memory-limit=2G", + "test": [ + "@test:integration" + ], + "test:integration": "phpunit -c phpunit-integration.xml.dist", "ci": [ "@lint", "@cs", - "@stan" + "@stan", + "@test" ] } } diff --git a/composer.lock b/composer.lock index 8a4e2b4..740387a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b7e5f17d904a4977efb85dde5d48edf0", + "content-hash": "100daeaa732ad02c2952242d18b7d8f1", "packages": [], "packages-dev": [ { @@ -63,6 +63,399 @@ "source": "https://github.com/bamarni/composer-bin-plugin/tree/1.9.1" }, "time": "2026-02-04T10:18:12+00:00" + }, + { + "name": "composer/installers", + "version": "v2.3.0", + "source": { + "type": "git", + "url": "https://github.com/composer/installers.git", + "reference": "12fb2dfe5e16183de69e784a7b84046c43d97e8e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/installers/zipball/12fb2dfe5e16183de69e784a7b84046c43d97e8e", + "reference": "12fb2dfe5e16183de69e784a7b84046c43d97e8e", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "composer/composer": "^1.10.27 || ^2.7", + "composer/semver": "^1.7.2 || ^3.4.0", + "phpstan/phpstan": "^1.11", + "phpstan/phpstan-phpunit": "^1", + "symfony/phpunit-bridge": "^7.1.1", + "symfony/process": "^5 || ^6 || ^7" + }, + "type": "composer-plugin", + "extra": { + "class": "Composer\\Installers\\Plugin", + "branch-alias": { + "dev-main": "2.x-dev" + }, + "plugin-modifies-install-path": true + }, + "autoload": { + "psr-4": { + "Composer\\Installers\\": "src/Composer/Installers" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kyle Robinson Young", + "email": "kyle@dontkry.com", + "homepage": "https://github.com/shama" + } + ], + "description": "A multi-framework Composer library installer", + "homepage": "https://composer.github.io/installers/", + "keywords": [ + "Dolibarr", + "Eliasis", + "Hurad", + "ImageCMS", + "Kanboard", + "Lan Management System", + "MODX Evo", + "MantisBT", + "Mautic", + "Maya", + "OXID", + "Plentymarkets", + "Porto", + "RadPHP", + "SMF", + "Starbug", + "Thelia", + "Whmcs", + "WolfCMS", + "agl", + "annotatecms", + "attogram", + "bitrix", + "cakephp", + "chef", + "cockpit", + "codeigniter", + "concrete5", + "concreteCMS", + "croogo", + "dokuwiki", + "drupal", + "eZ Platform", + "elgg", + "expressionengine", + "fuelphp", + "grav", + "installer", + "itop", + "known", + "kohana", + "laravel", + "lavalite", + "lithium", + "magento", + "majima", + "mako", + "matomo", + "mediawiki", + "miaoxing", + "modulework", + "modx", + "moodle", + "osclass", + "pantheon", + "phpbb", + "piwik", + "ppi", + "processwire", + "puppet", + "pxcms", + "reindex", + "roundcube", + "shopware", + "silverstripe", + "sydes", + "sylius", + "tastyigniter", + "wordpress", + "yawik", + "zend", + "zikula" + ], + "support": { + "issues": "https://github.com/composer/installers/issues", + "source": "https://github.com/composer/installers/tree/v2.3.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-06-24T20:46:46+00:00" + }, + { + "name": "roots/wordpress", + "version": "7.1.2", + "source": { + "type": "git", + "url": "https://github.com/roots/wordpress.git", + "reference": "eb3d108f216618a858dcaed691e00e68f0ccff9f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/roots/wordpress/zipball/eb3d108f216618a858dcaed691e00e68f0ccff9f", + "reference": "eb3d108f216618a858dcaed691e00e68f0ccff9f", + "shasum": "" + }, + "require": { + "roots/wordpress-core-installer": "^4.0", + "roots/wordpress-no-content": "self.version" + }, + "type": "metapackage", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT", + "GPL-2.0-or-later" + ], + "description": "WordPress is open source software you can use to create a beautiful website, blog, or app.", + "homepage": "https://wordpress.org/", + "keywords": [ + "blog", + "cms", + "wordpress" + ], + "support": { + "issues": "https://github.com/roots/wordpress/issues", + "source": "https://github.com/roots/wordpress/tree/7.1.2" + }, + "funding": [ + { + "url": "https://github.com/roots", + "type": "github" + } + ], + "time": "2026-03-22T13:26:54+00:00" + }, + { + "name": "roots/wordpress-core-installer", + "version": "v4.0.0", + "source": { + "type": "git", + "url": "https://github.com/roots/wordpress-core-installer.git", + "reference": "85d3589252eb93eee836facf33b54ce30c565bea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/roots/wordpress-core-installer/zipball/85d3589252eb93eee836facf33b54ce30c565bea", + "reference": "85d3589252eb93eee836facf33b54ce30c565bea", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "php": ">=7.2.24" + }, + "conflict": { + "composer/installers": "<1.0.6" + }, + "replace": { + "johnpbloch/wordpress-core-installer": "*" + }, + "require-dev": { + "composer/composer": "^2.0", + "phpunit/phpunit": "^8.5" + }, + "type": "composer-plugin", + "extra": { + "class": "Roots\\Composer\\WordPressCorePlugin" + }, + "autoload": { + "psr-4": { + "Roots\\Composer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "John P. Bloch", + "email": "me@johnpbloch.com" + }, + { + "name": "Roots", + "email": "team@roots.io" + } + ], + "description": "A Composer custom installer to handle installing WordPress as a dependency", + "keywords": [ + "wordpress" + ], + "support": { + "issues": "https://github.com/roots/wordpress-core-installer/issues", + "source": "https://github.com/roots/wordpress-core-installer/tree/v4.0.0" + }, + "funding": [ + { + "url": "https://github.com/roots", + "type": "github" + } + ], + "time": "2026-03-22T03:17:23+00:00" + }, + { + "name": "roots/wordpress-no-content", + "version": "7.1.2", + "source": { + "type": "git", + "url": "https://github.com/WordPress/WordPress.git", + "reference": "7.1.2" + }, + "dist": { + "type": "zip", + "url": "https://downloads.wordpress.org/release/wordpress-7.1.2-no-content.zip", + "reference": "7.1.2", + "shasum": "52c0250854a52621dc024651b63d1e088287ee16" + }, + "require": { + "php": ">= 7.4" + }, + "provide": { + "wordpress/core-implementation": "7.1.2" + }, + "suggest": { + "ext-curl": "Performs remote request operations.", + "ext-dom": "Used to validate Text Widget content and to automatically configuring IIS7+.", + "ext-exif": "Works with metadata stored in images.", + "ext-fileinfo": "Used to detect mimetype of file uploads.", + "ext-hash": "Used for hashing, including passwords and update packages.", + "ext-imagick": "Provides better image quality for media uploads.", + "ext-json": "Used for communications with other servers.", + "ext-libsodium": "Validates Signatures and provides securely random bytes.", + "ext-mbstring": "Used to properly handle UTF8 text.", + "ext-mysqli": "Connects to MySQL for database interactions.", + "ext-openssl": "Permits SSL-based connections to other hosts.", + "ext-pcre": "Increases performance of pattern matching in code searches.", + "ext-xml": "Used for XML parsing, such as from a third-party site.", + "ext-zip": "Used for decompressing Plugins, Themes, and WordPress update packages." + }, + "type": "wordpress-core", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "WordPress Community", + "homepage": "https://wordpress.org/about/" + } + ], + "description": "WordPress is open source software you can use to create a beautiful website, blog, or app.", + "homepage": "https://wordpress.org/", + "keywords": [ + "blog", + "cms", + "wordpress" + ], + "support": { + "docs": "https://developer.wordpress.org/", + "forum": "https://wordpress.org/support/", + "irc": "irc://irc.freenode.net/wordpress", + "issues": "https://core.trac.wordpress.org/", + "rss": "https://wordpress.org/news/feed/", + "source": "https://core.trac.wordpress.org/browser", + "wiki": "https://codex.wordpress.org/" + }, + "funding": [ + { + "url": "https://wordpressfoundation.org/donate/", + "type": "other" + } + ], + "time": "2026-09-22T13:36:10+00:00" + }, + { + "name": "wp-phpunit/wp-phpunit", + "version": "7.1.1", + "source": { + "type": "git", + "url": "https://github.com/wp-phpunit/wp-phpunit.git", + "reference": "797edc710afc958854852d859c93d9aa1de6447d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-phpunit/wp-phpunit/zipball/797edc710afc958854852d859c93d9aa1de6447d", + "reference": "797edc710afc958854852d859c93d9aa1de6447d", + "shasum": "" + }, + "type": "library", + "autoload": { + "files": [ + "__loaded.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Evan Mattson", + "email": "me@aaemnnost.tv" + }, + { + "name": "WordPress Community", + "homepage": "https://wordpress.org/about/" + } + ], + "description": "WordPress core PHPUnit library", + "homepage": "https://github.com/wp-phpunit", + "keywords": [ + "phpunit", + "test", + "wordpress" + ], + "support": { + "docs": "https://github.com/wp-phpunit/docs", + "issues": "https://github.com/wp-phpunit/issues", + "source": "https://github.com/wp-phpunit/wp-phpunit" + }, + "time": "2026-08-20T00:59:59+00:00" + }, + { + "name": "wpackagist-plugin/woocommerce", + "version": "11.1.2", + "source": { + "type": "svn", + "url": "https://plugins.svn.wordpress.org/woocommerce/", + "reference": "tags/11.1.2" + }, + "dist": { + "type": "zip", + "url": "https://downloads.wordpress.org/plugin/woocommerce.11.1.2.zip" + }, + "require": { + "composer/installers": "^1.0 || ^2.0" + }, + "type": "wordpress-plugin", + "homepage": "https://wordpress.org/plugins/woocommerce/" } ], "aliases": [], diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 0bfeb29..d9effba 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -39,6 +39,7 @@ /woocommerce/* + /tests/* diff --git a/phpstan.neon.dist b/phpstan.neon.dist index cfa98c4..f33ccd7 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,5 +1,7 @@ includes: - vendor-bin/phpstan/vendor/szepeviktor/phpstan-wordpress/extension.neon + - vendor-bin/phpstan/vendor/phpstan/phpstan-phpunit/extension.neon + - vendor-bin/phpstan/vendor/phpstan/phpstan-phpunit/rules.neon parameters: level: 5 @@ -8,16 +10,19 @@ parameters: - inc - patterns - woocommerce + - tests bootstrapFiles: - vendor-bin/phpstan/vendor/php-stubs/woocommerce-stubs/woocommerce-stubs.php + - vendor-bin/phpunit/vendor/autoload.php + scanDirectories: + - vendor/wp-phpunit/wp-phpunit/includes ignoreErrors: - - - identifier: constant.notFound - message: '#^Constant (AUTH_KEY|SECURE_AUTH_SALT|NONCE_SALT) not found\.$#' - path: inc/github-site-webhook.php - identifier: isset.property path: inc/registration.php - identifier: booleanAnd.rightAlwaysTrue path: inc/registration.php + - + identifier: assign.propertyType + path: tests/Integration/Inc/RegistrationTest.php diff --git a/phpunit-integration.xml.dist b/phpunit-integration.xml.dist new file mode 100644 index 0000000..36ace97 --- /dev/null +++ b/phpunit-integration.xml.dist @@ -0,0 +1,23 @@ + + + + + tests/Integration + + + + + functions.php + inc + woocommerce + + + diff --git a/readme.txt b/readme.txt index 486e55a..1cf7b36 100644 --- a/readme.txt +++ b/readme.txt @@ -56,8 +56,23 @@ Every check is a Composer script: - `composer lint`: php -l on every file - `composer cs`: PHPCS - `composer stan`: PHPStan +- `composer test`: PHPUnit - `composer ci`: all of the above, in this order +`composer install` brings in WordPress, the WordPress test suite and WooCommerce, +so the tests only need a MySQL/MariaDB database they are allowed to wipe on every +run. The connection comes from `WP_TESTS_DB_NAME` (`wordpress_test`), +`WP_TESTS_DB_USER` (`root`), `WP_TESTS_DB_PASSWORD` (`root`), `WP_TESTS_DB_HOST` +(`mariadb`) and `WP_TESTS_TABLE_PREFIX` (`wptests_`). + +On the local SaaS stack: + + docker exec wordpress-docker-mariadb-1 \ + mariadb -uroot -proot -e 'CREATE DATABASE IF NOT EXISTS wordpress_test;' + + docker exec -w /var/www/html/wp-content/themes/libresign \ + wordpress-docker-wordpress-1 composer test + == Development notes == - Site fragment sync/render bootstrap: `inc/footer-fragment.php` diff --git a/tests/Integration/Inc/AccountTest.php b/tests/Integration/Inc/AccountTest.php new file mode 100644 index 0000000..cf7247a --- /dev/null +++ b/tests/Integration/Inc/AccountTest.php @@ -0,0 +1,218 @@ +store = new StoreFactory(); + } + + public function tear_down() { + $_GET = array(); + $_REQUEST = array(); + $_SERVER['REQUEST_URI'] = ''; + unset( $GLOBALS['wp']->query_vars['lost-password'] ); + $this->store->empty_cart(); + add_filter( 'the_content', 'wpautop' ); + add_filter( 'the_content', 'shortcode_unautop' ); + set_current_screen( 'front' ); + + parent::tear_down(); + } + + public function test_the_account_is_the_woocommerce_my_account_page() { + $this->assertSame( wc_get_page_permalink( 'myaccount' ), libresign_theme_get_account_url() ); + } + + public function test_goes_back_to_the_page_asked_for() { + $_REQUEST['redirect_to'] = '/store/'; + + $this->assertSame( '/store/', libresign_theme_get_purchase_redirect_target() ); + } + + public function test_never_sends_to_another_site() { + $_REQUEST['redirect_to'] = 'https://evil.example/'; + + $this->assertSame( libresign_theme_get_account_url(), libresign_theme_get_purchase_redirect_target() ); + } + + public function test_goes_to_checkout_while_buying() { + $this->store->add_to_cart( $this->store->plan() ); + + $this->assertSame( wc_get_checkout_url(), libresign_theme_get_purchase_redirect_target() ); + } + + public function test_goes_to_the_account_otherwise() { + wc_load_cart(); + + $this->assertSame( libresign_theme_get_account_url(), libresign_theme_get_purchase_redirect_target() ); + } + + /** + * @dataProvider provide_lost_password_actions + */ + public function test_recognizes_the_lost_password_action( $action, $expected ) { + $_GET['action'] = $action; + + $this->assertSame( $expected, libresign_theme_is_lost_password_request() ); + } + + public static function provide_lost_password_actions() { + yield 'lost password' => array( 'lostpassword', true ); + yield 'in another letter case' => array( 'LostPassword', true ); + yield 'another action' => array( 'login', false ); + } + + public function test_recognizes_the_woocommerce_lost_password_endpoint() { + $GLOBALS['wp']->query_vars['lost-password'] = ''; + + $this->assertTrue( libresign_theme_is_lost_password_request() ); + } + + public function test_a_plain_request_is_not_a_lost_password_request() { + $this->assertFalse( libresign_theme_is_lost_password_request() ); + } + + /** + * @dataProvider provide_request_uris + */ + public function test_recognizes_the_direct_lost_password_route( $request_uri, $expected ) { + unset( $_SERVER['REQUEST_URI'] ); + if ( null !== $request_uri ) { + $_SERVER['REQUEST_URI'] = $request_uri; + } + + $this->assertSame( $expected, libresign_theme_is_direct_lost_password_route() ); + } + + public static function provide_request_uris() { + yield 'with the trailing slash' => array( '/lost-password/', true ); + yield 'without the trailing slash' => array( '/lost-password', true ); + yield 'with a query string' => array( '/lost-password/?reset-link-sent=true', true ); + yield 'under the account page' => array( '/account/lost-password/', false ); + yield 'the home page' => array( '/', false ); + yield 'no request uri' => array( null, false ); + } + + /** + * @dataProvider provide_reset_confirmations + */ + public function test_recognizes_a_password_reset_confirmation( $query, $expected ) { + $_GET = $query; + + $this->assertSame( $expected, libresign_theme_is_password_reset_confirmation() ); + } + + public static function provide_reset_confirmations() { + yield 'a reset link with the user id' => array( array( 'key' => 'abc', 'id' => '7' ), true ); + yield 'a reset link with the user login' => array( array( 'key' => 'abc', 'login' => 'ana' ), true ); + yield 'the set new password step' => array( array( 'show-reset-form' => 'true' ), true ); + yield 'the link sent step' => array( array( 'reset-link-sent' => 'true' ), true ); + yield 'a key without its user' => array( array( 'key' => 'abc' ), false ); + yield 'nothing' => array( array(), false ); + } + + public function test_the_lost_password_form_posts_to_woocommerce() { + $_SERVER['REQUEST_URI'] = '/lost-password/'; + + ob_start(); + libresign_theme_render_lost_password_form(); + $form = (string) ob_get_clean(); + + $this->assertStringContainsString( '', $form ); + $this->assertStringContainsString( 'name="woocommerce-lost-password-nonce"', $form ); + $this->assertStringContainsString( 'assertStringContainsString( 'Back to sign in', $form ); + } + + public function test_the_account_page_renders_the_woocommerce_account() { + $content = $this->account_page_content(); + + $this->assertStringContainsString( 'woocommerce-form-login', $content ); + $this->assertFalse( has_filter( 'the_content', 'wpautop' ) ); + } + + public function test_the_account_page_renders_the_lost_password_form() { + $this->assertStringContainsString( 'libresign-lost-password__card', $this->account_page_content( array( 'action' => 'lostpassword' ) ) ); + } + + public function test_the_account_page_leaves_the_reset_confirmation_to_woocommerce() { + $content = $this->account_page_content( + array( + 'action' => 'lostpassword', + 'show-reset-form' => 'true', + ) + ); + + $this->assertStringNotContainsString( 'libresign-lost-password__card', $content ); + $this->assertStringContainsString( 'woocommerce', $content ); + } + + public function test_keeps_the_content_of_other_pages() { + $this->in_the_loop_of( home_url() ); + + $this->assertSame( '

Page

', libresign_theme_prepend_saas_onboarding_to_content( '

Page

' ) ); + } + + public function test_keeps_the_shop_content_since_the_onboarding_pattern_does_not_exist() { + $shop = self::factory()->post->create( array( 'post_type' => 'page' ) ); + update_option( 'woocommerce_shop_page_id', $shop ); + $this->in_the_loop_of( get_permalink( $shop ) ); + + $this->assertSame( '

Shop

', libresign_theme_prepend_saas_onboarding_to_content( '

Shop

' ) ); + } + + public function test_keeps_the_content_outside_the_loop() { + add_filter( 'woocommerce_is_account_page', '__return_true' ); + $this->go_to( home_url() ); + + $this->assertSame( '

Page

', libresign_theme_prepend_saas_onboarding_to_content( '

Page

' ) ); + } + + public function test_keeps_the_content_in_the_admin() { + add_filter( 'woocommerce_is_account_page', '__return_true' ); + $this->in_the_loop_of( home_url() ); + set_current_screen( 'dashboard' ); + + $this->assertSame( '

Page

', libresign_theme_prepend_saas_onboarding_to_content( '

Page

' ) ); + } + + /** + * @dataProvider provide_requests_the_direct_route_leaves_alone + */ + public function test_the_direct_lost_password_route_leaves_other_requests_alone( $request_uri, $query, $account_page ) { + $_SERVER['REQUEST_URI'] = $request_uri; + $_GET = $query; + add_filter( 'woocommerce_is_account_page', $account_page ? '__return_true' : '__return_false' ); + + $this->expectOutputString( '' ); + + libresign_theme_render_direct_lost_password_route(); + } + + public static function provide_requests_the_direct_route_leaves_alone() { + yield 'another route' => array( '/store/', array(), false ); + yield 'a reset confirmation' => array( '/lost-password/', array( 'show-reset-form' => 'true' ), false ); + yield 'the route of the account' => array( '/lost-password/', array(), true ); + } + + private function account_page_content( $query = array() ) { + add_filter( 'woocommerce_is_account_page', '__return_true' ); + $this->in_the_loop_of( add_query_arg( $query, home_url( '/' ) ) ); + + return libresign_theme_prepend_saas_onboarding_to_content( '' ); + } + + private function in_the_loop_of( $url ) { + $this->go_to( $url ); + $GLOBALS['wp_query']->in_the_loop = true; + } +} diff --git a/tests/Integration/Inc/AssetsTest.php b/tests/Integration/Inc/AssetsTest.php new file mode 100644 index 0000000..04125c6 --- /dev/null +++ b/tests/Integration/Inc/AssetsTest.php @@ -0,0 +1,67 @@ +enqueue()->registered['libresign-saas']; + + $this->assertSame( get_theme_file_uri( 'assets/css/saas.css' ), $style->src ); + $this->assertSame( array( 'woocommerce-general' ), $style->deps ); + $this->assertSame( (string) filemtime( get_theme_file_path( 'assets/css/saas.css' ) ), $style->ver ); + } + + public function test_the_saas_stylesheet_stands_alone_without_the_woocommerce_styles() { + $woocommerce_general = wp_styles()->registered['woocommerce-general'] ?? null; + remove_action( 'wp_enqueue_scripts', array( 'WC_Frontend_Scripts', 'load_scripts' ) ); + wp_deregister_style( 'woocommerce-general' ); + + $deps = $this->enqueue()->registered['libresign-saas']->deps; + + if ( null !== $woocommerce_general ) { + wp_styles()->registered['woocommerce-general'] = $woocommerce_general; + } + + $this->assertSame( array(), $deps ); + } + + public function test_loads_the_assets_the_site_header_and_footer_use() { + $styles = $this->enqueue(); + + $this->assertSame( 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap-grid.min.css', $styles->registered['bootstrap-css']->src ); + $this->assertSame( 'https://cdn.lineicons.com/4.0/lineicons.css', $styles->registered['lineicons']->src ); + $this->assertSame( get_theme_file_uri( 'assets/css/header-footer.css' ), $styles->registered['libresign-header-footer']->src ); + $this->assertContains( 'libresign-header-footer', $styles->queue ); + } + + public function test_the_header_and_footer_styles_come_after_the_other_stylesheets() { + $queue = $this->enqueue()->queue; + + $this->assertSame( 'libresign-header-footer', end( $queue ) ); + } +} diff --git a/tests/Integration/Inc/CheckoutTest.php b/tests/Integration/Inc/CheckoutTest.php new file mode 100644 index 0000000..a306fe7 --- /dev/null +++ b/tests/Integration/Inc/CheckoutTest.php @@ -0,0 +1,35 @@ +assertSame( + 'I agree to the terms and privacy policy before placing the order.', + apply_filters( 'woocommerce_get_terms_and_conditions_checkbox_text', '' ) + ); + } + + public function test_refuses_an_order_without_the_policy_consent() { + $errors = $this->checkout_errors( array( 'terms' => 0 ) ); + + $this->assertContains( 'libresign_policy_consent', $errors->get_error_codes() ); + } + + public function test_accepts_an_order_with_the_policy_consent() { + $errors = $this->checkout_errors( array( 'terms' => 1 ) ); + + $this->assertNotContains( 'libresign_policy_consent', $errors->get_error_codes() ); + } +} diff --git a/tests/Integration/Inc/CpfCnpjTest.php b/tests/Integration/Inc/CpfCnpjTest.php new file mode 100644 index 0000000..db902e7 --- /dev/null +++ b/tests/Integration/Inc/CpfCnpjTest.php @@ -0,0 +1,155 @@ +assertSame( $valid, libresign_theme_validate_cpf( $cpf ) ); + } + + public static function provide_cpfs() { + yield 'digits only' => array( '52998224725', true ); + yield 'formatted' => array( '529.982.247-25', true ); + yield 'another valid one' => array( '123.456.789-09', true ); + yield 'wrong first check digit' => array( '529.982.247-35', false ); + yield 'wrong second check digit' => array( '529.982.247-24', false ); + yield 'every digit the same' => array( '111.111.111-11', false ); + yield 'too short' => array( '5299822472', false ); + yield 'too long' => array( '529982247250', false ); + yield 'empty' => array( '', false ); + } + + /** + * @dataProvider provide_cnpjs + */ + public function test_validates_a_cnpj( $cnpj, $valid ) { + $this->assertSame( $valid, libresign_theme_validate_cnpj( $cnpj ) ); + } + + public static function provide_cnpjs() { + yield 'digits only' => array( '11222333000181', true ); + yield 'formatted' => array( '11.222.333/0001-81', true ); + yield 'alphanumeric' => array( '12.ABC.345/01DE-35', true ); + yield 'alphanumeric in lowercase' => array( '12.abc.345/01de-35', true ); + yield 'wrong second check digit' => array( '11.222.333/0001-80', false ); + yield 'wrong alphanumeric check digit' => array( '12.ABC.345/01DE-34', false ); + yield 'letters in the check digits' => array( '12.ABC.345/01DE-3A', false ); + yield 'every digit the same' => array( '00.000.000/0000-00', false ); + yield 'symbols other than the formatting' => array( '12.ABC.345/01D*-35', false ); + yield 'too short' => array( '1122233300018', false ); + yield 'empty' => array( '', false ); + } + + public function test_registers_the_field_in_the_address_form() { + $field = Package::container()->get( CheckoutFields::class )->get_additional_fields()['libresign/cpf-cnpj']; + + $this->assertSame( 'CPF or CNPJ', $field['label'] ); + $this->assertSame( 'address', $field['location'] ); + $this->assertFalse( $field['required'] ); + $this->assertSame( array( 'autocomplete' => 'off' ), $field['attributes'] ); + } + + /** + * @dataProvider provide_addresses + */ + public function test_validates_the_field_of_a_brazilian_billing_address( $group, $fields, $errors ) { + $validation = new WP_Error(); + + do_action( 'woocommerce_blocks_validate_location_address_fields', $validation, $fields, $group ); + + $this->assertSame( $errors, $validation->get_error_codes() ); + } + + public static function provide_addresses() { + yield 'a shipping address is not checked' => array( 'shipping', array( 'country' => 'BR' ), array() ); + yield 'another country is not checked' => array( 'billing', array( 'country' => 'US' ), array() ); + yield 'an address without fields' => array( 'billing', null, array() ); + yield 'missing in Brazil' => array( 'billing', array( 'country' => 'BR' ), array( 'libresign_cpf_cnpj_required' ) ); + yield 'blank in Brazil' => array( 'billing', array( 'country' => 'BR', 'libresign/cpf-cnpj' => ' ' ), array( 'libresign_cpf_cnpj_required' ) ); + yield 'a valid cpf' => array( 'billing', array( 'country' => 'BR', 'libresign/cpf-cnpj' => '529.982.247-25' ), array() ); + yield 'an invalid cpf' => array( 'billing', array( 'country' => 'BR', 'libresign/cpf-cnpj' => '529.982.247-24' ), array( 'invalid_cpf' ) ); + yield 'a valid cnpj' => array( 'billing', array( 'country' => 'BR', 'libresign/cpf-cnpj' => '11.222.333/0001-81' ), array() ); + yield 'a valid alphanumeric cnpj' => array( 'billing', array( 'country' => 'BR', 'libresign/cpf-cnpj' => '12.ABC.345/01DE-35' ), array() ); + yield 'an invalid cnpj' => array( 'billing', array( 'country' => 'BR', 'libresign/cpf-cnpj' => '11.222.333/0001-80' ), array( 'invalid_cnpj' ) ); + yield 'neither a cpf nor a cnpj in length' => array( 'billing', array( 'country' => 'BR', 'libresign/cpf-cnpj' => '123456789' ), array( 'invalid_cpf_cnpj' ) ); + } + + public function test_drops_the_field_from_the_shipping_address_and_keeps_it_on_a_brazilian_billing() { + $order = $this->order_with_cpf_cnpj( 'BR' ); + + do_action( 'woocommerce_store_api_checkout_update_order_from_request', $order, new WP_REST_Request() ); + + $this->assertSame( '', $order->get_meta( '_wc_shipping/libresign/cpf-cnpj' ) ); + $this->assertSame( '529.982.247-25', $order->get_meta( '_wc_billing/libresign/cpf-cnpj' ) ); + } + + public function test_drops_the_field_from_an_order_billed_outside_brazil() { + $order = $this->order_with_cpf_cnpj( 'US' ); + + do_action( 'woocommerce_store_api_checkout_update_order_from_request', $order, new WP_REST_Request() ); + + $this->assertSame( '', $order->get_meta( '_wc_shipping/libresign/cpf-cnpj' ) ); + $this->assertSame( '', $order->get_meta( '_wc_billing/libresign/cpf-cnpj' ) ); + } + + public function test_drops_the_field_from_a_customer_billed_outside_brazil() { + $customer = new WC_Customer(); + $customer->set_billing_country( 'US' ); + $customer->update_meta_data( '_wc_billing/libresign/cpf-cnpj', '529.982.247-25' ); + + do_action( 'woocommerce_store_api_checkout_update_customer_from_request', $customer, new WP_REST_Request() ); + + $this->assertSame( '', $customer->get_meta( '_wc_billing/libresign/cpf-cnpj' ) ); + } + + public function test_ignores_what_is_not_a_woocommerce_object() { + $this->expectNotToPerformAssertions(); + + libresign_theme_strip_irrelevant_cpf_cnpj( 'not an order' ); + } + + public function test_prints_the_field_script_on_the_checkout() { + add_filter( 'woocommerce_is_checkout', '__return_true' ); + + $footer = $this->footer(); + + $this->assertStringContainsString( "var FIELD_KEY = 'billing_libresign/cpf-cnpj';", $footer ); + $this->assertStringContainsString( 'var ERROR_MSG = "Please enter your CPF or CNPJ.";', $footer ); + } + + public function test_prints_no_field_script_outside_the_checkout() { + $this->go_to( home_url() ); + + $this->assertStringNotContainsString( 'billing_libresign/cpf-cnpj', $this->footer() ); + } + + private function footer() { + $this->setExpectedDeprecated( 'the_block_template_skip_link' ); + + ob_start(); + do_action( 'wp_footer' ); + + return (string) ob_get_clean(); + } + + private function order_with_cpf_cnpj( $country ) { + $order = new WC_Order(); + $order->set_billing_country( $country ); + $order->update_meta_data( '_wc_billing/libresign/cpf-cnpj', '529.982.247-25' ); + $order->update_meta_data( '_wc_shipping/libresign/cpf-cnpj', '529.982.247-25' ); + + return $order; + } +} diff --git a/tests/Integration/Inc/FooterFragmentTest.php b/tests/Integration/Inc/FooterFragmentTest.php new file mode 100644 index 0000000..490bc96 --- /dev/null +++ b/tests/Integration/Inc/FooterFragmentTest.php @@ -0,0 +1,341 @@ +site = new StaticSite(); + } + + public function tear_down() { + foreach ( array( 'header', 'footer' ) as $fragment_type ) { + libresign_theme_site_fragment_recursive_delete( libresign_theme_site_fragment_storage_base_dir( $fragment_type ) ); + } + + parent::tear_down(); + } + + /** + * @dataProvider provide_locales + */ + public function test_normalizes_a_locale_into_a_language_tag( $locale, $tag, $storage_key ) { + $this->assertSame( $tag, libresign_theme_site_fragment_normalize_locale_tag( $locale ) ); + $this->assertSame( $storage_key, libresign_theme_site_fragment_storage_key( $locale ) ); + } + + public static function provide_locales() { + yield 'a wordpress locale' => array( 'pt_BR', 'pt-BR', 'pt-BR' ); + yield 'a language alone' => array( 'FR', 'fr', 'fr' ); + yield 'a script subtag' => array( 'zh_hant_tw', 'zh-Hant-TW', 'zh-Hant-TW' ); + yield 'a numeric region' => array( 'es-419', 'es-419', 'es-419' ); + yield 'a long variant stays as is' => array( 'de-DE-1901x', 'de-DE-1901x', 'de-DE-1901x' ); + yield 'repeated separators' => array( 'en--us', 'en-US', 'en-US' ); + yield 'surrounding whitespace' => array( ' nb_NO ', 'nb-NO', 'nb-NO' ); + yield 'only separators' => array( '-_', '', 'default' ); + yield 'empty' => array( '', '', 'default' ); + } + + /** + * @dataProvider provide_fragment_urls + */ + public function test_builds_the_fragment_url( $origin, $fragment_type, $locale, $url ) { + $this->assertSame( $url, libresign_theme_site_fragment_url( $origin, $fragment_type, $locale ) ); + } + + public static function provide_fragment_urls() { + yield 'the default header' => array( 'https://libresign.coop', 'header', '', 'https://libresign.coop/fragments/header' ); + yield 'a localized footer' => array( 'https://libresign.coop/', 'footer', 'pt_BR', 'https://libresign.coop/fragments/pt-BR/footer' ); + yield 'an origin with whitespace' => array( ' https://libresign.coop/ ', 'header', 'fr', 'https://libresign.coop/fragments/fr/header' ); + } + + public function test_stores_each_fragment_type_in_its_own_folder() { + $this->assertSame( array( 'header', 'footer' ), libresign_theme_site_fragment_supported_types() ); + $this->assertStringEndsWith( '/libresign-header', libresign_theme_site_fragment_storage_base_dir( 'header' ) ); + $this->assertStringEndsWith( '/libresign-footer', libresign_theme_site_fragment_storage_base_url( 'footer' ) ); + } + + public function test_reads_the_asset_urls_of_a_fragment() { + $this->assertSame( + array( + 'css' => 'https://libresign.coop/header.css', + 'js' => 'https://libresign.coop/header.js', + ), + libresign_theme_site_fragment_extract_asset_urls( "
" ) + ); + } + + public function test_a_fragment_without_asset_urls_is_an_error() { + $error = libresign_theme_site_fragment_extract_asset_urls( '
' ); + + $this->assertWPError( $error ); + $this->assertSame( 'libresign_theme_site_fragment_missing_assets', $error->get_error_code() ); + } + + public function test_reads_the_locales_the_header_links_to() { + $this->assertSame( + array( 'pt-BR', 'fr', '' ), + libresign_theme_site_fragment_extract_locales_from_header_html( + 'ptfrenfooter' + ) + ); + } + + public function test_falls_back_to_the_site_locale() { + $this->assertSame( array( 'en-US', '' ), libresign_theme_site_fragment_fallback_locales() ); + } + + public function test_looks_the_locale_up_from_the_most_specific_to_the_default() { + $this->assertSame( array( 'en-US', 'en', 'default' ), libresign_theme_site_fragment_locale_lookup_keys() ); + } + + public function test_drops_the_asset_attributes_after_syncing() { + $this->assertSame( + '
x
', + libresign_theme_site_fragment_strip_runtime_asset_attributes( '
x
' ) + ); + } + + /** + * @dataProvider provide_root_relative_urls + */ + public function test_points_root_relative_urls_to_the_static_site( $content, $rewritten ) { + $this->assertSame( $rewritten, libresign_theme_site_fragment_rewrite_root_relative_urls( $content, 'https://libresign.coop/' ) ); + } + + public static function provide_root_relative_urls() { + yield 'a link' => array( '', '' ); + yield 'an image with single quotes' => array( "", "" ); + yield 'a form and a video poster' => array( '
', '' ); + yield 'a relative path' => array( '', '' ); + } + + public function test_only_a_not_found_is_an_optional_error() { + $this->assertTrue( libresign_theme_site_fragment_is_optional_http_error( new WP_Error( 'x', 'x', array( 'status' => 404 ) ) ) ); + $this->assertFalse( libresign_theme_site_fragment_is_optional_http_error( new WP_Error( 'x', 'x', array( 'status' => 500 ) ) ) ); + $this->assertFalse( libresign_theme_site_fragment_is_optional_http_error( new WP_Error( 'x', 'x' ) ) ); + } + + public function test_fetches_a_url() { + $this->site->serve( '/fragments/header', '
' ); + + $this->assertSame( + array( + 'code' => 200, + 'body' => '
', + ), + libresign_theme_site_fragment_fetch_url( $this->site->origin() . '/fragments/header' ) + ); + } + + public function test_a_failing_status_is_an_error_with_the_status() { + $error = libresign_theme_site_fragment_fetch_url( $this->site->origin() . '/fragments/header' ); + + $this->assertWPError( $error ); + $this->assertSame( 'libresign_theme_site_fragment_http_status', $error->get_error_code() ); + $this->assertSame( 404, $error->get_error_data()['status'] ); + } + + public function test_a_transport_failure_is_an_error() { + $this->assertWPError( libresign_theme_site_fragment_fetch_url( 'https://libresign.coop/fragments/header' ) ); + } + + public function test_syncing_needs_an_origin() { + $error = libresign_theme_sync_site_fragments_from_origin( ' ' ); + + $this->assertWPError( $error ); + $this->assertSame( 'libresign_theme_site_fragment_origin_missing', $error->get_error_code() ); + } + + public function test_collects_every_fragment_in_every_locale_the_site_publishes() { + $this->publish_site(); + + $collected = libresign_theme_site_fragment_collect_artifacts( $this->site->origin(), array( 'header', 'footer' ), array( 'source_sha' => 'abc123' ) ); + + $this->assertSame( array( '', 'pt-BR', 'en-US' ), $collected['locales'] ); + $this->assertSame( + array( + 'header' => array( 'default', 'pt-BR' ), + 'footer' => array( 'default', 'pt-BR' ), + ), + $collected['summary'] + ); + $this->assertSame( + array( + '/fragments/header', + '/assets/header.css', + '/assets/header.js', + '/fragments/pt-BR/header', + '/fragments/en-US/header', + '/fragments/footer', + '/assets/footer.css', + '/assets/footer.js', + '/fragments/pt-BR/footer', + '/fragments/en-US/footer', + ), + $this->site->paths() + ); + + $header = $collected['artifacts'][0]; + $this->assertSame( '
pt
', $header['html'] ); + $this->assertSame( 'a{background:url(' . $this->site->origin() . '/bg.png)}', $header['css'] ); + $this->assertSame( 'console.log("header");', $header['js'] ); + $this->assertSame( 'abc123', $header['source_sha'] ); + $this->assertSame( hash( 'sha256', $header['html'] . "\n" . $header['css'] . "\n" . $header['js'] ), $header['version'] ); + } + + public function test_a_missing_default_header_stops_the_sync() { + $error = libresign_theme_site_fragment_collect_artifacts( $this->site->origin(), array( 'header' ) ); + + $this->assertWPError( $error ); + $this->assertSame( 404, $error->get_error_data()['status'] ); + } + + public function test_a_default_fragment_without_assets_stops_the_sync() { + $this->publish_site(); + $this->site->serve( '/fragments/footer', '
' ); + + $error = libresign_theme_site_fragment_collect_artifacts( $this->site->origin(), array( 'header', 'footer' ) ); + + $this->assertSame( 'libresign_theme_site_fragment_missing_assets', $error->get_error_code() ); + } + + public function test_skips_a_locale_whose_fragment_has_no_assets() { + $this->publish_site(); + $this->site->serve( '/fragments/pt-BR/footer', '
' ); + + $collected = libresign_theme_site_fragment_collect_artifacts( $this->site->origin(), array( 'footer' ) ); + + $this->assertSame( array( 'footer' => array( 'default' ) ), $collected['summary'] ); + } + + public function test_a_locale_that_fails_for_another_reason_stops_the_sync() { + $this->publish_site(); + $this->site->serve( '/fragments/pt-BR/footer', 'error', 500 ); + + $error = libresign_theme_site_fragment_collect_artifacts( $this->site->origin(), array( 'footer' ) ); + + $this->assertSame( 500, $error->get_error_data()['status'] ); + } + + public function test_a_missing_asset_stops_the_sync() { + $this->publish_site(); + $this->site->serve( '/assets/footer.js', 'gone', 404 ); + + $error = libresign_theme_site_fragment_collect_artifacts( $this->site->origin(), array( 'footer' ) ); + + $this->assertSame( 404, $error->get_error_data()['status'] ); + } + + public function test_stores_the_synced_fragments_with_their_manifest() { + $this->publish_site(); + + $result = libresign_theme_sync_site_fragments_from_origin( $this->site->origin() . '/', array(), array( 'source_url' => 'https://github.com/run/1' ) ); + + $this->assertSame( $this->site->origin(), $result['origin'] ); + $this->assertSame( array( 'default', 'pt-BR' ), $result['synced']['footer'] ); + + $folder = libresign_theme_site_fragment_storage_base_dir( 'footer' ) . '/pt-BR'; + $manifest = json_decode( (string) file_get_contents( $folder . '/manifest.json' ), true ); + + $this->assertSame( '
', file_get_contents( $folder . '/footer.html' ) ); + $this->assertSame( 'footer{}', file_get_contents( $folder . '/footer.css' ) ); + $this->assertSame( 'console.log("footer");', file_get_contents( $folder . '/footer.js' ) ); + $this->assertSame( 'pt-BR', $manifest['locale_key'] ); + $this->assertSame( 'https://github.com/run/1', $manifest['source_url'] ); + $this->assertSame( libresign_theme_site_fragment_storage_base_url( 'footer' ) . '/pt-BR', $manifest['base_url'] ); + } + + public function test_a_new_sync_replaces_the_locales_stored_before() { + wp_mkdir_p( libresign_theme_site_fragment_storage_base_dir( 'header' ) . '/fr' ); + $this->publish_site(); + + libresign_theme_sync_site_fragments_from_origin( $this->site->origin() ); + + $this->assertDirectoryDoesNotExist( libresign_theme_site_fragment_storage_base_dir( 'header' ) . '/fr' ); + $this->assertDirectoryExists( libresign_theme_site_fragment_storage_base_dir( 'header' ) . '/default' ); + } + + public function test_a_failed_sync_keeps_the_stored_fragments() { + wp_mkdir_p( libresign_theme_site_fragment_storage_base_dir( 'header' ) . '/fr' ); + + $this->assertWPError( libresign_theme_sync_site_fragments_from_origin( $this->site->origin() ) ); + $this->assertDirectoryExists( libresign_theme_site_fragment_storage_base_dir( 'header' ) . '/fr' ); + } + + public function test_renders_and_enqueues_the_stored_fragments_in_place_of_the_template_parts() { + $this->store( + array( + array( 'header', 'default', '
stored
' ), + array( 'footer', 'default', '
en
' ), + array( 'footer', 'en', '
en-generic
' ), + array( 'footer', 'en-US', '
en-US
' ), + ) + ); + + do_action( 'wp_enqueue_scripts' ); + + $this->assertSame( '
stored
', apply_filters( 'render_block_core/template-part', '
theme
', array( 'attrs' => array( 'slug' => 'header' ) ) ) ); + $this->assertSame( '
en-US
', apply_filters( 'render_block_core/template-part', '
theme
', array( 'attrs' => array( 'slug' => 'footer' ) ) ) ); + $this->assertSame( '', apply_filters( 'render_block_core/template-part', '', array( 'attrs' => array( 'slug' => 'sidebar' ) ) ) ); + $this->assertSame( libresign_theme_site_fragment_storage_base_url( 'header' ) . '/default/header.css', wp_styles()->registered['libresign-site-header-fragment']->src ); + $this->assertSame( libresign_theme_site_fragment_storage_base_url( 'footer' ) . '/en-US/footer.js', wp_scripts()->registered['libresign-site-footer-fragment']->src ); + $this->assertSame( 'module', wp_scripts()->get_data( 'libresign-site-header-fragment', 'type' ) ); + } + + private function store( $fragments ) { + $artifacts = array(); + + foreach ( $fragments as list( $fragment_type, $locale_key, $html ) ) { + $artifacts[] = array( + 'fragment_type' => $fragment_type, + 'locale' => 'default' === $locale_key ? '' : $locale_key, + 'locale_key' => $locale_key, + 'fragment_url' => '', + 'css_url' => '', + 'js_url' => '', + 'generated_at' => '', + 'source_sha' => '', + 'source_url' => '', + 'html' => $html, + 'css' => '', + 'js' => '', + 'version' => '1', + ); + } + + libresign_theme_site_fragment_persist_artifacts( + array( + 'artifacts' => $artifacts, + 'summary' => array(), + ) + ); + } + + private function publish_site() { + $origin = $this->site->origin(); + $header = '
pt
'; + $footer = '
'; + + $this->site->serve( '/fragments/header', $header ); + $this->site->serve( '/fragments/pt-BR/header', $header ); + $this->site->serve( '/fragments/footer', $footer ); + $this->site->serve( '/fragments/pt-BR/footer', str_replace( '