diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml
new file mode 100644
index 0000000..44eb499
--- /dev/null
+++ b/.github/workflows/phpunit.yml
@@ -0,0 +1,62 @@
+name: PHPUnit
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+
+permissions:
+ contents: read
+
+jobs:
+ php-matrix:
+ name: PHP matrix
+ runs-on: ubuntu-latest
+ outputs:
+ versions: ${{ steps.php-matrix.outputs.versions }}
+ steps:
+ - uses: actions/checkout@v5
+
+ - uses: typisttech/php-matrix-action@0545a7d54db2c7be158b615b1e7f458ae6e99ad3 # v3.0.2
+ id: php-matrix
+
+ phpunit:
+ name: PHPUnit (PHP ${{ matrix.php }})
+ needs: php-matrix
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ php: ${{ fromJSON(needs.php-matrix.outputs.versions) }}
+ services:
+ mariadb:
+ image: mariadb:11
+ env:
+ MARIADB_ROOT_PASSWORD: root
+ MARIADB_DATABASE: wordpress_test
+ ports:
+ - 3306:3306
+ options: >-
+ --health-cmd="healthcheck.sh --connect --innodb_initialized"
+ --health-interval=5s
+ --health-timeout=5s
+ --health-retries=10
+ env:
+ WP_TESTS_DB_HOST: 127.0.0.1
+ WP_TESTS_DB_NAME: wordpress_test
+ WP_TESTS_DB_USER: root
+ WP_TESTS_DB_PASSWORD: root
+ steps:
+ - uses: actions/checkout@v5
+
+ - uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
+ with:
+ php-version: ${{ matrix.php }}
+ extensions: mysqli
+ coverage: none
+ tools: composer
+
+ - uses: ramsey/composer-install@a8d0d959dab41457692a5e2041bd9b757a119e3f # 3.2.1
+
+ - run: composer test
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/inc/account.php b/inc/account.php
index d4c06b9..d779e97 100644
--- a/inc/account.php
+++ b/inc/account.php
@@ -34,7 +34,7 @@ function libresign_theme_get_account_url() {
function libresign_theme_get_purchase_redirect_target() {
$redirect_to = '';
- if ( isset( $_REQUEST['redirect_to'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ if ( isset( $_REQUEST['redirect_to'] ) && is_string( $_REQUEST['redirect_to'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$redirect_to = wp_sanitize_redirect( wp_unslash( $_REQUEST['redirect_to'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
}
diff --git a/inc/cpf-cnpj.php b/inc/cpf-cnpj.php
index 23353f6..7ba0266 100644
--- a/inc/cpf-cnpj.php
+++ b/inc/cpf-cnpj.php
@@ -15,7 +15,7 @@
/**
* Register the CPF/CNPJ field in the checkout address section.
*/
-add_action( 'woocommerce_init', function () {
+function libresign_theme_register_cpf_cnpj_field() {
if ( ! function_exists( 'woocommerce_register_additional_checkout_field' ) ) {
return;
}
@@ -29,11 +29,11 @@
'type' => 'text',
'attributes' => array(
'autocomplete' => 'off',
- 'placeholder' => __( 'Required for customers in Brazil', 'libresign' ),
),
)
);
-} );
+}
+add_action( 'woocommerce_init', 'libresign_theme_register_cpf_cnpj_field' );
/**
* Validate CPF — 11-digit Brazilian individual taxpayer ID.
diff --git a/inc/github-site-webhook.php b/inc/github-site-webhook.php
index d4aad5f..b142a9c 100644
--- a/inc/github-site-webhook.php
+++ b/inc/github-site-webhook.php
@@ -226,7 +226,7 @@ function libresign_theme_site_deploy_workflow_name() {
}
}
- return 'Deploy';
+ return 'pages build and deployment';
}
/**
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 @@
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..b54db4e --- /dev/null +++ b/tests/Integration/Inc/CpfCnpjTest.php @@ -0,0 +1,159 @@ +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() { + __internal_woocommerce_blocks_deregister_checkout_field( 'libresign/cpf-cnpj' ); + + libresign_theme_register_cpf_cnpj_field(); + + $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( "