diff --git a/.github/workflows/build-plugin-zip.yml b/.github/workflows/build-plugin-zip.yml index 5163a343541ca7..43e65749baae9c 100644 --- a/.github/workflows/build-plugin-zip.yml +++ b/.github/workflows/build-plugin-zip.yml @@ -183,7 +183,7 @@ jobs: NO_CHECKS: 'true' - name: Upload artifact - uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 + uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 with: name: gutenberg-plugin path: ./gutenberg.zip @@ -206,7 +206,7 @@ jobs: - name: Upload release notes artifact if: ${{ needs.bump-version.outputs.new_version }} - uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 + uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 with: name: release-notes path: ./release-notes.txt @@ -270,12 +270,12 @@ jobs: run: echo "version=$(echo $VERSION | cut -d / -f 3 | sed 's/-rc./ RC/' )" >> $GITHUB_OUTPUT - name: Download Plugin Zip Artifact - uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 + uses: actions/download-artifact@6b208ae046db98c579e8a3aa621ab581ff575935 # v4.1.1 with: name: gutenberg-plugin - name: Download Release Notes Artifact - uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 + uses: actions/download-artifact@6b208ae046db98c579e8a3aa621ab581ff575935 # v4.1.1 with: name: release-notes diff --git a/.github/workflows/end2end-test.yml b/.github/workflows/end2end-test.yml index 362aec73cd6241..f4e470eb177f93 100644 --- a/.github/workflows/end2end-test.yml +++ b/.github/workflows/end2end-test.yml @@ -41,7 +41,7 @@ jobs: npx wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --cacheDirectory="$HOME/.jest-cache" - name: Archive debug artifacts (screenshots, HTML snapshots) - uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 + uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 if: always() with: name: failures-artifacts @@ -49,7 +49,7 @@ jobs: if-no-files-found: ignore - name: Archive flaky tests report - uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 + uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 if: always() with: name: flaky-tests-report @@ -92,7 +92,7 @@ jobs: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test:e2e:playwright -- --shard=${{ matrix.part }}/${{ matrix.totalParts }} - name: Archive debug artifacts (screenshots, traces) - uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 + uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 if: always() with: name: failures-artifacts @@ -100,7 +100,7 @@ jobs: if-no-files-found: ignore - name: Archive flaky tests report - uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 + uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 if: always() with: name: flaky-tests-report @@ -120,7 +120,7 @@ jobs: ref: trunk show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - - uses: actions/download-artifact@v4.1.0 + - uses: actions/download-artifact@v4.1.1 id: download_artifact # Don't fail the job if there isn't any flaky tests report. continue-on-error: true diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index bc457758b8385b..ade7ecd3e9a060 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -9,4 +9,4 @@ jobs: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - - uses: gradle/wrapper-validation-action@v1 + - uses: gradle/wrapper-validation-action@v2 diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index 12063c0eb7d496..bef55a431cea97 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -86,7 +86,7 @@ jobs: - name: Archive performance results if: success() - uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 + uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 with: name: performance-results path: ${{ env.WP_ARTIFACTS_PATH }}/*.performance-results*.json @@ -100,7 +100,7 @@ jobs: ./bin/log-performance-results.js $CODEHEALTH_PROJECT_TOKEN trunk $GITHUB_SHA b61dde2e5ec29d98801e623de968bfb286c5be3f $COMMITTED_AT - name: Archive debug artifacts (screenshots, HTML snapshots) - uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 + uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 if: failure() with: name: failures-artifacts diff --git a/.github/workflows/php-changes-detection.yml b/.github/workflows/php-changes-detection.yml index fc0ba83b95dbc4..6a13d4d014fc69 100644 --- a/.github/workflows/php-changes-detection.yml +++ b/.github/workflows/php-changes-detection.yml @@ -17,7 +17,7 @@ jobs: - name: Get changed PHP files id: changed-files-php - uses: tj-actions/changed-files@716b1e13042866565e00e85fd4ec490e186c4a2f # v41.0.1 + uses: tj-actions/changed-files@90a06d6ba9543371ab4df8eeca0be07ca6054959 # v42.0.2 with: files: | *.{php} @@ -38,7 +38,7 @@ jobs: echo "formatted_change_list=$formatted_change_list" >> $GITHUB_OUTPUT - name: Find Comment - uses: peter-evans/find-comment@v2 + uses: peter-evans/find-comment@v3 id: find-comment with: issue-number: ${{ github.event.pull_request.number }} @@ -47,7 +47,7 @@ jobs: - name: Create comment if: steps.find-comment.outputs.comment-id == '' && steps.changed-files-php.outputs.any_changed == 'true' - uses: peter-evans/create-or-update-comment@v3 + uses: peter-evans/create-or-update-comment@v4 with: issue-number: ${{ github.event.pull_request.number }} body: | @@ -67,7 +67,7 @@ jobs: - name: Update comment if: steps.find-comment.outputs.comment-id != '' && steps.changed-files-php.outputs.any_changed == 'true' - uses: peter-evans/create-or-update-comment@v3 + uses: peter-evans/create-or-update-comment@v4 with: comment-id: ${{ steps.find-comment.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} @@ -89,7 +89,7 @@ jobs: - name: Update comment if: steps.find-comment.outputs.comment-id != '' && steps.changed-files-php.outputs.any_changed != 'true' - uses: peter-evans/create-or-update-comment@v3 + uses: peter-evans/create-or-update-comment@v4 with: comment-id: ${{ steps.find-comment.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} diff --git a/.github/workflows/props-bot.yml b/.github/workflows/props-bot.yml new file mode 100644 index 00000000000000..0f21f47ef14f99 --- /dev/null +++ b/.github/workflows/props-bot.yml @@ -0,0 +1,88 @@ +name: Props Bot + +on: + # This event runs anytime a PR is (re)opened, updated, marked ready for review, or labeled. + # GitHub does not allow filtering the `labeled` event by a specific label. + # However, the logic below will short-circuit the workflow when the `props-bot` label is not the one being added. + # Note: The pull_request_target event is used instead of pull_request because this workflow needs permission to comment + # on the pull request. Because this event grants extra permissions to `GITHUB_TOKEN`, any code changes within the PR + # should be considered untrusted. See https://securitylab.github.com/research/github-actions-preventing-pwn-requests/. + pull_request_target: + types: + - opened + - synchronize + - reopened + - labeled + - ready_for_review + # This event runs anytime a comment is added or deleted. + # You cannot filter this event for PR comments only. + # However, the logic below does short-circuit the workflow for issues. + issue_comment: + type: + - created + # This event will run everytime a new PR review is initially submitted. + pull_request_review: + types: + - submitted + # This event runs anytime a PR review comment is created or deleted. + pull_request_review_comment: + types: + - created + +# Cancels all previous workflow runs for pull requests that have not completed. +concurrency: + # The concurrency group contains the workflow name and the branch name for pull requests + # or the commit hash for any other events. + group: ${{ github.workflow }}-${{ contains( fromJSON( '["pull_request_target", "pull_request_review", "pull_request_review_comment"]' ), github.event_name ) && github.head_ref || github.sha }} + cancel-in-progress: true + +# Disable permissions for all available scopes by default. +# Any needed permissions should be configured at the job level. +permissions: {} + +jobs: + # Compiles a list of props for a pull request. + # + # Performs the following steps: + # - Collects a list of contributor props and leaves a comment. + # - Removes the props-bot label, if necessary. + props-bot: + name: Generate a list of props + runs-on: ubuntu-latest + permissions: + # The action needs permission `write` permission for PRs in order to add a comment. + pull-requests: write + contents: read + timeout-minutes: 20 + # The job will run when pull requests are open, ready for review and: + # + # - A comment is added to the pull request. + # - A review is created or commented on. + # - The pull request is opened, synchronized, marked ready for review, or reopened. + # - The `props-bot` label is added to the pull request. + if: | + ( + github.event_name == 'issue_comment' && github.event.issue.pull_request || + contains( fromJSON( '["pull_request_review", "pull_request_review_comment"]' ), github.event_name ) || + github.event_name == 'pull_request_target' && github.event.action != 'labeled' || + 'props-bot' == github.event.label.name + ) && + ( ! github.event.pull_request.draft && github.event.pull_request.state == 'open' || ! github.event.issue.draft && github.event.issue.state == 'open' ) + + steps: + - name: Gather a list of contributors + uses: WordPress/props-bot-action@trunk + + - name: Remove the props-bot label + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + if: ${{ github.event.action == 'labeled' && 'props-bot' == github.event.label.name }} + with: + retries: 2 + retry-exempt-status-codes: 418 + script: | + github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: '${{ github.event.number }}', + name: 'props-bot' + }); diff --git a/.github/workflows/pull-request-automation.yml b/.github/workflows/pull-request-automation.yml index d0b8778a1d3c7c..85c71199d28e14 100644 --- a/.github/workflows/pull-request-automation.yml +++ b/.github/workflows/pull-request-automation.yml @@ -24,7 +24,7 @@ jobs: check-latest: true - name: Cache NPM packages - uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 + uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 with: # npm cache files are stored in `~/.npm` on Linux/macOS path: ~/.npm diff --git a/.github/workflows/rnmobile-android-runner.yml b/.github/workflows/rnmobile-android-runner.yml index e0a3b9639cf389..7f7e641e2d2224 100644 --- a/.github/workflows/rnmobile-android-runner.yml +++ b/.github/workflows/rnmobile-android-runner.yml @@ -37,7 +37,7 @@ jobs: uses: ./.github/setup-node - name: Restore tests setup cache - uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 + uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 with: path: | ~/.appium @@ -47,10 +47,10 @@ jobs: run: npm run native test:e2e:setup - name: Gradle cache - uses: gradle/gradle-build-action@982da8e78c05368c70dac0351bb82647a9e9a5d2 # v2.11.1 + uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0 - name: AVD cache - uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 + uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 id: avd-cache with: path: | @@ -60,7 +60,7 @@ jobs: - name: Create AVD and generate snapshot for caching if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@99a4aac18b4df9b3af66c4a1f04c1f23fa10c270 # v2.29.0 + uses: reactivecircus/android-emulator-runner@6b0df4b0efb23bb0ec63d881db79aefbc976e4b2 # v2.30.1 with: api-level: ${{ matrix.api-level }} force-avd-creation: false @@ -71,7 +71,7 @@ jobs: script: echo "Generated AVD snapshot for caching." - name: Run tests - uses: reactivecircus/android-emulator-runner@99a4aac18b4df9b3af66c4a1f04c1f23fa10c270 # v2.29.0 + uses: reactivecircus/android-emulator-runner@6b0df4b0efb23bb0ec63d881db79aefbc976e4b2 # v2.30.1 with: api-level: ${{ matrix.api-level }} force-avd-creation: false @@ -81,13 +81,13 @@ jobs: profile: Nexus 6 script: npm run native test:e2e:android:local ${{ matrix.native-test-name }} - - uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 + - uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 if: always() with: name: android-screen-recordings path: packages/react-native-editor/android-screen-recordings - - uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 + - uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 if: always() with: name: appium-logs diff --git a/.github/workflows/rnmobile-ios-runner.yml b/.github/workflows/rnmobile-ios-runner.yml index 9ead788343b8dc..220874d7649195 100644 --- a/.github/workflows/rnmobile-ios-runner.yml +++ b/.github/workflows/rnmobile-ios-runner.yml @@ -37,7 +37,7 @@ jobs: uses: ./.github/setup-node - name: Restore tests setup cache - uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 + uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 with: path: | ~/.appium @@ -50,7 +50,7 @@ jobs: run: find package-lock.json packages/react-native-editor/ios packages/react-native-aztec/ios packages/react-native-bridge/ios -type f -print0 | sort -z | xargs -0 shasum | tee ios-checksums.txt - name: Restore build cache - uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 + uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 with: path: | packages/react-native-editor/ios/build/GutenbergDemo/Build/Products/Release-iphonesimulator/GutenbergDemo.app @@ -58,7 +58,7 @@ jobs: key: ${{ runner.os }}-ios-build-${{ matrix.xcode }}-${{ matrix.device }}-${{ hashFiles('ios-checksums.txt') }} - name: Restore pods cache - uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 + uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 with: path: | packages/react-native-editor/ios/Pods @@ -84,13 +84,13 @@ jobs: rm packages/react-native-editor/ios/build/GutenbergDemo/Build/Products/Release-iphonesimulator/GutenbergDemo.app/main.jsbundle rm -rf packages/react-native-editor/ios/build/GutenbergDemo/Build/Products/Release-iphonesimulator/GutenbergDemo.app/assets - - uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 + - uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 if: always() with: name: ios-screen-recordings path: packages/react-native-editor/ios-screen-recordings - - uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 + - uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 if: always() with: name: appium-logs diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index b6d5465ab43a6c..523418a79ef49a 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -125,7 +125,7 @@ jobs: # dependency versions are installed and cached. ## - name: Set up PHP - uses: shivammathur/setup-php@e6f75134d35752277f093989e72e140eaa222f35 # v2.28.0 + uses: shivammathur/setup-php@6d7209f44a25a59e904b1ee9f3b0c33ab2cd888d # v2.29.0 with: php-version: '${{ matrix.php }}' ini-file: development @@ -225,7 +225,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Set up PHP - uses: shivammathur/setup-php@e6f75134d35752277f093989e72e140eaa222f35 # v2.28.0 + uses: shivammathur/setup-php@6d7209f44a25a59e904b1ee9f3b0c33ab2cd888d # v2.29.0 with: php-version: '7.4' coverage: none @@ -238,7 +238,7 @@ jobs: run: echo "date=$(/bin/date -u --date='last Mon' "+%F")" >> $GITHUB_OUTPUT - name: Cache PHPCS scan cache - uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 + uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 with: path: .cache/phpcs.json key: ${{ runner.os }}-date-${{ steps.get-date.outputs.date }}-phpcs-cache-${{ hashFiles('**/composer.json', 'phpcs.xml.dist') }} diff --git a/.github/workflows/upload-release-to-plugin-repo.yml b/.github/workflows/upload-release-to-plugin-repo.yml index d6d5f8da1102bb..e71bade9d0cd87 100644 --- a/.github/workflows/upload-release-to-plugin-repo.yml +++ b/.github/workflows/upload-release-to-plugin-repo.yml @@ -147,7 +147,7 @@ jobs: fi - name: Upload Changelog artifact - uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 + uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 with: name: changelog ${{ matrix.label }} path: ./changelog.txt @@ -189,7 +189,7 @@ jobs: sed -i "s/$STABLE_TAG_PLACEHOLDER/Stable tag: $VERSION/g" ./trunk/readme.txt - name: Download Changelog Artifact - uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 + uses: actions/download-artifact@6b208ae046db98c579e8a3aa621ab581ff575935 # v4.1.1 with: name: changelog trunk path: trunk @@ -247,7 +247,7 @@ jobs: sed -i "s/$STABLE_TAG_PLACEHOLDER/Stable tag: $VERSION/g" "$VERSION/readme.txt" - name: Download Changelog Artifact - uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 + uses: actions/download-artifact@6b208ae046db98c579e8a3aa621ab581ff575935 # v4.1.1 with: name: changelog trunk path: ${{ github.event.release.name }} diff --git a/bin/packages/check-build-type-declaration-files.js b/bin/packages/check-build-type-declaration-files.js index fff0b51e32fc24..74c7cfa5e3c2ac 100644 --- a/bin/packages/check-build-type-declaration-files.js +++ b/bin/packages/check-build-type-declaration-files.js @@ -69,13 +69,16 @@ async function getDecFile( packagePath ) { async function typecheckDeclarations( file ) { return new Promise( ( resolve, reject ) => { - exec( `npx tsc --noEmit ${ file }`, ( error, stdout, stderr ) => { - if ( error ) { - reject( { file, error, stderr, stdout } ); - } else { - resolve( { file, stdout } ); + exec( + `npx tsc --target esnext --moduleResolution node --noEmit ${ file }`, + ( error, stdout, stderr ) => { + if ( error ) { + reject( { file, error, stderr, stdout } ); + } else { + resolve( { file, stdout } ); + } } - } ); + ); } ); } diff --git a/docs/contributors/versions-in-wordpress.md b/docs/contributors/versions-in-wordpress.md index ce9eb458e73723..a385c1ced9e43c 100644 --- a/docs/contributors/versions-in-wordpress.md +++ b/docs/contributors/versions-in-wordpress.md @@ -6,6 +6,7 @@ If anything looks incorrect here, please bring it up in #core-editor in [WordPre | Gutenberg Versions | WordPress Version | | ------------------ | ----------------- | +| 16.2-16.7 | 6.4.3 | | 16.2-16.7 | 6.4.2 | | 16.2-16.7 | 6.4.1 | | 16.2-16.7 | 6.4 | diff --git a/docs/getting-started/README.md b/docs/getting-started/README.md index b45a740c2a58cd..f1b5999ae31472 100644 --- a/docs/getting-started/README.md +++ b/docs/getting-started/README.md @@ -1,44 +1,37 @@ # Getting Started -Welcome! Let's get started building with blocks. Blocks are at the core of extending WordPress. You can create custom blocks, your own block patterns, or combine them together to build a block theme. +Welcome to the Getting Started documentation. From setting up your development environment and building your first block to understanding the fundamentals, this section is the perfect starting point if you are new to block development or want to improve your skills. ## Navigating this chapter -For those starting with block development, this section is the perfect starting point as it provides the knowledge you need to start creating your own custom blocks. +Use the following links to locate a topic within this chapter. If you have never built a block before, consider reading through the documentation in the order listed. -- [**Block Development Environment**](https://developer.wordpress.org/block-editor/getting-started/devenv/) - Set up the right development environment to create blocks and get introduced to basic tools for block development such as [`wp-env`](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-wp-env/), [`create-block`](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-create-block/) and [`wp-scripts`](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-create-block/) -- [**Quick Start Guide**](https://developer.wordpress.org/block-editor/getting-started/quick-start-guide/) - Get a block up and running in less than 1 min. -- [**Tutorial: Build your first block**](https://developer.wordpress.org/block-editor/getting-started/tutorial/) - The tutorial will guide you, step by step, through the complete process of creating a fully functional custom block. -- [**Fundamentals of Block Development**](https://developer.wordpress.org/block-editor/getting-started/fundamentals/) - This section provides an introduction to the most relevant concepts in Block Development. -- [**Glossary**](https://developer.wordpress.org/block-editor/getting-started/glossary/) - Glossary of terms related to the Block Editor and Full Site Editing -- [**Frequently Asked Questions**](https://developer.wordpress.org/block-editor/getting-started/faq/) - Set of questions (and answers) that have come up from the last few years of Gutenberg development. +- **[Block Development Environment](https://developer.wordpress.org/block-editor/getting-started/devenv/):** Set up the right development environment to create blocks and get introduced to basic tools for block development such as [`wp-env`](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-wp-env/), [`create-block`](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-create-block/), and [`wp-scripts`](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-create-block/) +- **[Quick Start Guide](https://developer.wordpress.org/block-editor/getting-started/quick-start-guide/):** Get a custom block up and running in less than one minute. +- **[Tutorial: Build your first block](https://developer.wordpress.org/block-editor/getting-started/tutorial/):** Learn how to build a fully functional custom block from the ground up. +- **[Fundamentals of Block Development](https://developer.wordpress.org/block-editor/getting-started/fundamentals/):** Learn the most relevant concepts in block development. +- **[Glossary](https://developer.wordpress.org/block-editor/getting-started/glossary/):** A glossary of common terms you will encounter when working with the Block Editor. +- **[Frequently Asked Questions](https://developer.wordpress.org/block-editor/getting-started/faq/):** Common questions (and answers) that have come up from the last few years of Gutenberg's development. -## Getting Started on the WordPress project and Gutenberg +## Keeping up with the WordPress project -At a high level, here are a few ways to begin your journey but read on to explore more: +Once you have finished reviewing this chapter, you will have a solid understanding of blocks and how to develop for the Block Editor, but what's next? -- Learn more about where this work is going by [reviewing the long term roadmap](https://wordpress.org/about/roadmap/). -- Explore the [GitHub repo](https://github.com/WordPress/gutenberg/) to see the latest issues and PRs folks are working on, especially [Good First Issues](https://github.com/WordPress/gutenberg/issues?q=is%3Aopen+is%3Aissue+label%3A%22Good+First+Issue%22). -- Join the [Slack community](https://make.wordpress.org/chat/) to join meetings, ongoing conversations, and more. -- Take courses on how to use the block editor and more on [Learn WordPress](https://learn.wordpress.org/). -- Expand your knowledge by reviewing more developer docs at the overall [developer.wordpress.org resource](https://developer.wordpress.org/). -- Subscribe to [updates on Make Core](https://make.wordpress.org/core/), the main site where ongoing project updates happen. +The WordPress project, and Gutenberg in particular, iterates quickly. Staying up-to-date on all the changes can be challenging. So, here are a few essential developer resources you should be aware of. Each person will have their own unique needs in keeping up with a project of this scale, so choose what's right for you. -### Ways to Stay Informed +- **[WordPress Roadmap](https://wordpress.org/about/roadmap/):** The high-level roadmap for WordPress and Gutenberg. +- **[Make Core](https://make.wordpress.org/core/):** The primary blog for WordPress Core where all major project updates are posted. +- **[WordPress Slack](https://make.wordpress.org/chat/):** The official Slack community for all WordPress contributors is the hub for team meetings, ongoing conversations, and more. Make sure to join the `#core` and `#core-editor` channels. +- **[Gutenberg GitHub repository](https://github.com/WordPress/gutenberg/):** This is where all Block Editor development happens. Keeping a close eye on the repository will give you a real-time understanding of what’s being worked on by fellow contributors. +- **[Keeping up with Gutenberg](https://make.wordpress.org/core/handbook/references/keeping-up-with-gutenberg-index/):** A compilation of Gutenberg-related posts from the many [Make teams](https://make.wordpress.org/), including Core, Design, Meta, and Themes. +- **["What's new in Gutenberg?"](https://make.wordpress.org/core/tag/gutenberg-new/):** Biweekly posts published on Make Core with each Gutenberg release. They are a great way to review the most relevant new features and the full changelog. +- **["What's new for developers?"](https://developer.wordpress.org/news/):** Monthly posts on the WordPress Developer Blog that showcase the most important developer-related changes that happened in WordPress the previous month. -New features and changes are important to keep up to date on as the Gutenberg project continues. Each person will have their own unique needs in keeping up with a project of this scale. What follows is more of a catalogue of ways to keep up rather than a recommendation for how to do so. +## Additional resources -- [Keeping up with Gutenberg](https://make.wordpress.org/core/handbook/references/keeping-up-with-gutenberg-index/) - compilation of Gutenberg-related team posts of Core, Core-Editor, Core-js, Core-css, Design, Meta, and Themes, and other teams. -- [“What’s New In Gutenberg?” release posts](https://make.wordpress.org/core/tag/gutenberg-new/). These updates are wrangled by the Core Editor team and focus on what’s been released in each biweekly Gutenberg release. They include the most relevant features released and a full changelog. -- [Core Editor meetings](https://make.wordpress.org/core/tag/core-editor-summary/). These meetings are wrangled by volunteer members in the #core-editor Slack channel. [Agendas](https://make.wordpress.org/core/tag/core-editor-summary/) and [summaries](https://make.wordpress.org/core/tag/core-editor-summary/) are shared on the [Make Core blog](https://make.wordpress.org/core/). They focus on task coordination and relevant discussions around Gutenberg releases. There is an Open Floor period in each chat where people can suggest topics to discuss. -- Checking in on [issues](https://github.com/WordPress/gutenberg/issues) and [PRs](https://github.com/WordPress/gutenberg/pulls) on GitHub. This will give you a nearly real-time understanding of what’s being worked on by the developers and designers. +For more resources on block development and extending the Block Editor, review the additional sections here in the Block Editor Handbook. Further practical examples are also available in the [block-development-examples](https://github.com/wptrainingteam/block-development-examples) GitHub repository. - -## Additional Resources - -The [block-development-examples](https://github.com/wptrainingteam/block-development-examples) repo is the central hub of examples for block development referenced from this handbook. - -At [Learn WordPress](https://learn.wordpress.org/), you can find [tutorials](https://learn.wordpress.org/tutorials/), [courses](https://learn.wordpress.org/courses/), and [online workshops](https://learn.wordpress.org/online-workshops/) to learn more about developing for the Block Editor. Here is a selection of current offerings: +If you are looking for more educational content, check out [Learn WordPress](https://learn.wordpress.org/), where you can find [tutorials](https://learn.wordpress.org/tutorials/), [courses](https://learn.wordpress.org/courses/), and [online workshops](https://learn.wordpress.org/online-workshops/). Here is a selection of current offerings: - [Intro to Block Development: Build Your First Custom Block](https://learn.wordpress.org/course/introduction-to-block-development-build-your-first-custom-block/) - [Converting a Shortcode to a Block](https://learn.wordpress.org/course/converting-a-shortcode-to-a-block/) diff --git a/docs/getting-started/devenv/README.md b/docs/getting-started/devenv/README.md index b9acda48eab7fb..1c2b9ed9070d30 100644 --- a/docs/getting-started/devenv/README.md +++ b/docs/getting-started/devenv/README.md @@ -2,17 +2,19 @@ This guide will help you set up the right development environment to create blocks and other plugins that extend and modify the Block Editor in WordPress. -To contribute to the Gutenberg project itself, refer to the additional documentation in the [code contribution guide](/docs/contributors/code/getting-started-with-code-contribution.md). - A block development environment includes the tools you need on your computer to successfully develop for the Block Editor. The three essential requirements are: 1. [Code editor](#code-editor) 2. [Node.js development tools](#node-js-development-tools) 3. [Local WordPress environment (site)](#local-wordpress-environment) +
wp-scripts
includes a css-loader chained with postcss-loader and sass-loader that allows it to process CSS, SASS or SCSS files. Check Default webpack config for more info
+ The webpack configuration used internally by wp-scripts
includes a css-loader chained with postcss-loader and sass-loader that allows it to process CSS, SASS or SCSS files. Check Default webpack config for more info
webpack-src-dir
and output-path
option of wp-scripts
build commands to customize the entry and output points
+This transformation process includes minification, transpilation from modern JavaScript to a version compatible with a wider range of browsers, and bundling of assets for efficient loading. WordPress ultimately enqueues and uses the `build` folder's contents to render the block in the Block Editor and on the front end.
+
+webpack-src-dir
and output-path
option of wp-scripts
build commands to customize the entry and output points.
${ name }
`,
+ table: { type: { summary: undefined } },
+ },
+ };
+ },
+ },
+ },
+};
+export default meta;
+
+export const Default: StoryFn< typeof Composite > = ( { ...initialState } ) => {
+ const rtl = isRTL();
+ const store = useCompositeStore( { rtl, ...initialState } );
+
+ return (
+ test
body { color: red; }' ), + ) + ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_custom_css_illegal_markup', $response, 400 ); } /** @@ -170,9 +506,36 @@ public function test_prepare_item() { } /** - * @doesNotPerformAssertions + * @covers WP_REST_Global_Styles_Controller_Gutenberg::get_item_schema */ public function test_get_item_schema() { - // Covered by the core. + $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/global-styles/' . self::$global_styles_id ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $properties = $data['schema']['properties']; + $this->assertCount( 4, $properties, 'Schema properties array does not have exactly 4 elements' ); + $this->assertArrayHasKey( 'id', $properties, 'Schema properties array does not have "id" key' ); + $this->assertArrayHasKey( 'styles', $properties, 'Schema properties array does not have "styles" key' ); + $this->assertArrayHasKey( 'settings', $properties, 'Schema properties array does not have "settings" key' ); + $this->assertArrayHasKey( 'title', $properties, 'Schema properties array does not have "title" key' ); + } + + /** + * @covers WP_REST_Global_Styles_Controller_Gutenberg::get_available_actions + */ + public function test_assign_edit_css_action_admin() { + wp_set_current_user( self::$admin_id ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/global-styles/' . self::$global_styles_id ); + $request->set_param( 'context', 'edit' ); + $response = rest_do_request( $request ); + $links = $response->get_links(); + + // Admins can only edit css on single site. + if ( is_multisite() ) { + $this->assertArrayNotHasKey( 'https://api.w.org/action-edit-css', $links ); + } else { + $this->assertArrayHasKey( 'https://api.w.org/action-edit-css', $links ); + } } } diff --git a/phpunit/class-wp-theme-json-resolver-test.php b/phpunit/class-wp-theme-json-resolver-test.php index eda8caede7bfec..e2742e1c3a8ed0 100644 --- a/phpunit/class-wp-theme-json-resolver-test.php +++ b/phpunit/class-wp-theme-json-resolver-test.php @@ -4,6 +4,8 @@ * Test WP_Theme_JSON_Resolver_Gutenberg class. * * @package Gutenberg + * + * @since 5.8.0 */ class WP_Theme_JSON_Resolver_Gutenberg_Test extends WP_UnitTestCase { @@ -16,39 +18,43 @@ class WP_Theme_JSON_Resolver_Gutenberg_Test extends WP_UnitTestCase { protected static $administrator_id; /** - * WP_Theme_JSON_Resolver::$blocks_cache property. + * WP_Theme_JSON_Resolver_Gutenberg::$blocks_cache property. * * @var ReflectionProperty */ private static $property_blocks_cache; /** - * Original value of the WP_Theme_JSON_Resolver::$blocks_cache property. + * Original value of the WP_Theme_JSON_Resolver_Gutenberg::$blocks_cache property. * * @var array */ private static $property_blocks_cache_orig_value; /** - * WP_Theme_JSON_Resolver::$core property. + * WP_Theme_JSON_Resolver_Gutenberg::$core property. * * @var ReflectionProperty */ private static $property_core; /** - * Original value of the WP_Theme_JSON_Resolver::$core property. + * Original value of the WP_Theme_JSON_Resolver_Gutenberg::$core property. * - * @var WP_Theme_JSON + * @var WP_Theme_JSON_Gutenberg */ private static $property_core_orig_value; /** + * Theme root directory. + * * @var string|null */ private $theme_root; /** + * Original theme directory. + * * @var array|null */ private $orig_theme_dir; @@ -77,6 +83,12 @@ public static function set_up_before_class() { static::$property_core_orig_value = static::$property_core->getValue(); } + public static function tear_down_after_class() { + static::$property_blocks_cache->setValue( null, static::$property_blocks_cache_orig_value ); + static::$property_core->setValue( null, static::$property_core_orig_value ); + parent::tear_down_after_class(); + } + public function set_up() { parent::set_up(); $this->theme_root = realpath( __DIR__ . '/data/themedir1' ); @@ -99,6 +111,9 @@ public function tear_down() { $GLOBALS['wp_theme_directories'] = $this->orig_theme_dir; wp_clean_themes_cache(); unset( $GLOBALS['wp_themes'] ); + + // Reset data between tests. + _gutenberg_clean_theme_json_caches(); parent::tear_down(); } @@ -110,29 +125,24 @@ public function filter_set_locale_to_polish() { return 'pl_PL'; } - public function filter_db_query( $query ) { - if ( preg_match( '#post_type = \'wp_global_styles\'#', $query ) ) { - $this->queries[] = $query; - } - return $query; - } - public function test_translations_are_applied() { add_filter( 'locale', array( $this, 'filter_set_locale_to_polish' ) ); load_textdomain( 'block-theme', realpath( __DIR__ . '/data/languages/themes/block-theme-pl_PL.mo' ) ); switch_theme( 'block-theme' ); - $actual = WP_Theme_JSON_Resolver_Gutenberg::get_theme_data(); + $theme_data = WP_Theme_JSON_Resolver_Gutenberg::get_theme_data(); + $style_variations = WP_Theme_JSON_Resolver_Gutenberg::get_style_variations(); unload_textdomain( 'block-theme' ); remove_filter( 'locale', array( $this, 'filter_set_locale_to_polish' ) ); - $this->assertSame( wp_get_theme()->get( 'TextDomain' ), 'block-theme' ); + $this->assertSame( 'block-theme', wp_get_theme()->get( 'TextDomain' ) ); + $this->assertSame( 'Motyw blokowy', $theme_data->get_data()['title'] ); $this->assertSame( array( 'color' => array( 'custom' => false, - 'customGradient' => true, + 'customGradient' => false, 'palette' => array( 'theme' => array( array( @@ -147,14 +157,42 @@ public function test_translations_are_applied() { ), ), ), + 'gradients' => array( + 'theme' => array( + array( + 'name' => 'Custom gradient', + 'gradient' => 'linear-gradient(135deg,rgba(0,0,0) 0%,rgb(0,0,0) 100%)', + 'slug' => 'custom-gradient', + ), + ), + ), + 'duotone' => array( + 'theme' => array( + array( + 'colors' => array( '#333333', '#aaaaaa' ), + 'slug' => 'custom-duotone', + 'name' => 'Custom Duotone', + ), + ), + ), ), 'typography' => array( - 'customFontSize' => true, - 'lineHeight' => false, + 'customFontSize' => false, + 'lineHeight' => true, + 'fontSizes' => array( + 'theme' => array( + array( + 'name' => 'Custom', + 'slug' => 'custom', + 'size' => '100px', + ), + ), + ), ), 'spacing' => array( - 'units' => false, - 'padding' => false, + 'units' => array( 'rem' ), + 'padding' => true, + 'blockGap' => true, ), 'blocks' => array( 'core/paragraph' => array( @@ -172,28 +210,227 @@ public function test_translations_are_applied() { ), ), ), - $actual->get_settings() + $theme_data->get_settings() ); + + $custom_templates = $theme_data->get_custom_templates(); + $this->assertArrayHasKey( 'page-home', $custom_templates ); $this->assertSame( - $actual->get_custom_templates(), array( - 'page-home' => array( - 'title' => 'Szablon strony głównej', - 'postTypes' => array( 'page' ), - ), - ) + 'title' => 'Szablon strony głównej', + 'postTypes' => array( 'page' ), + ), + $custom_templates['page-home'] ); - $this->assertSame( - $actual->get_template_parts(), + + $this->assertSameSets( array( 'small-header' => array( 'title' => 'Mały nagłówek', 'area' => 'header', ), - ) + ), + $theme_data->get_template_parts() + ); + + $this->assertSame( + 'Wariant motywu blokowego', + $style_variations[2]['title'] + ); + } + + private function get_registered_block_names( $hard_reset = false ) { + static $expected_block_names; + + if ( ! $hard_reset && ! empty( $expected_block_names ) ) { + return $expected_block_names; + } + + $expected_block_names = array(); + $resolver = WP_Block_Type_Registry::get_instance(); + $blocks = $resolver->get_all_registered(); + foreach ( array_keys( $blocks ) as $block_name ) { + $expected_block_names[ $block_name ] = true; + } + + return $expected_block_names; + } + + /** + * Tests when WP_Theme_JSON_Resolver_Gutenberg::$blocks_cache is empty or + * does not match the all registered blocks. + * + * Though this is a non-public method, it is vital to other functionality. + * Therefore, tests are provided to validate it functions as expected. + * + * @dataProvider data_has_same_registered_blocks_when_all_blocks_not_cached + * + * @param string $origin The origin to test. + */ + public function test_has_same_registered_blocks_when_all_blocks_not_cached( $origin, array $cache = array() ) { + $has_same_registered_blocks = new ReflectionMethod( WP_Theme_JSON_Resolver_Gutenberg::class, 'has_same_registered_blocks' ); + $has_same_registered_blocks->setAccessible( true ); + $expected_cache = $this->get_registered_block_names(); + + // Set up the blocks cache for the origin. + $blocks_cache = static::$property_blocks_cache->getValue(); + $blocks_cache[ $origin ] = $cache; + static::$property_blocks_cache->setValue( null, $blocks_cache ); + + $this->assertFalse( $has_same_registered_blocks->invoke( null, $origin ), 'WP_Theme_JSON_Resolver_Gutenberg::has_same_registered_blocks() should return false when same blocks are not cached' ); + $blocks_cache = static::$property_blocks_cache->getValue(); + $this->assertSameSets( $expected_cache, $blocks_cache[ $origin ], 'WP_Theme_JSON_Resolver_Gutenberg::$blocks_cache should contain all expected block names for the given origin' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_has_same_registered_blocks_when_all_blocks_not_cached() { + return array( + 'origin: core; cache: empty' => array( + 'origin' => 'core', + ), + 'origin: blocks; cache: empty' => array( + 'origin' => 'blocks', + ), + 'origin: theme; cache: empty' => array( + 'origin' => 'theme', + ), + 'origin: user; cache: empty' => array( + 'origin' => 'user', + ), + 'origin: core; cache: not empty' => array( + 'origin' => 'core', + 'cache' => array( + 'core/block' => true, + ), + ), + 'origin: blocks; cache: not empty' => array( + 'origin' => 'blocks', + 'cache' => array( + 'core/block' => true, + 'core/comments' => true, + ), + ), + 'origin: theme; cache: not empty' => array( + 'origin' => 'theme', + 'cache' => array( + 'core/cover' => true, + ), + ), + 'origin: user; cache: not empty' => array( + 'origin' => 'user', + 'cache' => array( + 'core/gallery' => true, + ), + ), + ); + } + + /** + * Tests when WP_Theme_JSON_Resolver_Gutenberg::$blocks_cache is empty or + * does not match the all registered blocks. + * + * Though this is a non-public method, it is vital to other functionality. + * Therefore, tests are provided to validate it functions as expected. + * + * @dataProvider data_has_same_registered_blocks_when_all_blocks_are_cached + * + * @param string $origin The origin to test. + */ + public function test_has_same_registered_blocks_when_all_blocks_are_cached( $origin ) { + $has_same_registered_blocks = new ReflectionMethod( WP_Theme_JSON_Resolver_Gutenberg::class, 'has_same_registered_blocks' ); + $has_same_registered_blocks->setAccessible( true ); + $expected_cache = $this->get_registered_block_names(); + + // Set up the cache with all registered blocks. + $blocks_cache = static::$property_blocks_cache->getValue(); + $blocks_cache[ $origin ] = $this->get_registered_block_names(); + static::$property_blocks_cache->setValue( null, $blocks_cache ); + + $this->assertTrue( $has_same_registered_blocks->invoke( null, $origin ), 'WP_Theme_JSON_Resolver_Gutenberg::has_same_registered_blocks() should return true when using the cache' ); + $this->assertSameSets( $expected_cache, $blocks_cache[ $origin ], 'WP_Theme_JSON_Resolver_Gutenberg::$blocks_cache should contain all expected block names for the given origin' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_has_same_registered_blocks_when_all_blocks_are_cached() { + return array( + 'core' => array( 'core' ), + 'blocks' => array( 'blocks' ), + 'theme' => array( 'theme' ), + 'user' => array( 'user' ), + ); + } + + /** + * @dataProvider data_get_core_data + * @covers WP_Theme_JSON_Resolver_Gutenberg::get_core_data + */ + public function test_get_core_data( $should_fire_filter, $core_is_cached, $blocks_are_cached ) { + _gutenberg_clean_theme_json_caches(); + + // If should cache core, then fire the method to cache it before running the tests. + if ( $core_is_cached ) { + WP_Theme_JSON_Resolver_Gutenberg::get_core_data(); + } + + // If should cache registered blocks, then set them up before running the tests. + if ( $blocks_are_cached ) { + $blocks_cache = static::$property_blocks_cache->getValue(); + $blocks_cache['core'] = $this->get_registered_block_names(); + static::$property_blocks_cache->setValue( null, $blocks_cache ); + } + + $expected_filter_count = did_filter( 'wp_theme_json_data_default' ); + $actual = WP_Theme_JSON_Resolver_Gutenberg::get_core_data(); + if ( $should_fire_filter ) { + ++$expected_filter_count; + } + + $this->assertSame( $expected_filter_count, did_filter( 'wp_theme_json_data_default' ), 'The filter "wp_theme_json_data_default" should fire the given number of times' ); + $this->assertInstanceOf( WP_Theme_JSON_Gutenberg::class, $actual, 'WP_Theme_JSON_Resolver_Gutenberg::get_core_data() should return instance of WP_Theme_JSON_Gutenberg' ); + $this->assertSame( static::$property_core->getValue(), $actual, 'WP_Theme_JSON_Resolver_Gutenberg::$core property should be the same object as returned from WP_Theme_JSON_Resolver_Gutenberg::get_core_data()' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_get_core_data() { + return array( + 'When both caches are empty' => array( + 'should_fire_filter' => true, + 'core_is_cached' => false, + 'blocks_are_cached' => false, + ), + 'When the blocks_cache is not empty and matches' => array( + 'should_fire_filter' => true, + 'core_is_cached' => false, + 'blocks_are_cached' => true, + ), + 'When blocks_cache is empty but core cache is not' => array( + 'should_fire_filter' => true, + 'core_is_cached' => true, + 'blocks_are_cached' => false, + ), + 'When both caches are not empty' => array( + 'should_fire_filter' => true, + 'core_is_cached' => true, + 'blocks_are_cached' => false, + ), ); } + /** + * @covers ::add_theme_support + */ public function test_add_theme_supports_are_loaded_for_themes_without_theme_json() { switch_theme( 'default' ); $color_palette = array( @@ -215,27 +452,32 @@ public function test_add_theme_supports_are_loaded_for_themes_without_theme_json ); add_theme_support( 'editor-color-palette', $color_palette ); add_theme_support( 'custom-line-height' ); + add_theme_support( 'appearance-tools' ); $settings = WP_Theme_JSON_Resolver_Gutenberg::get_theme_data()->get_settings(); remove_theme_support( 'custom-line-height' ); remove_theme_support( 'editor-color-palette' ); + remove_theme_support( 'appearance-tools' ); $this->assertFalse( wp_theme_has_theme_json() ); $this->assertTrue( $settings['typography']['lineHeight'] ); $this->assertSame( $color_palette, $settings['color']['palette']['theme'] ); + $this->assertTrue( $settings['border']['color'], 'Support for "appearance-tools" was not added.' ); } /** - * Recursively applies ksort to an array. + * Tests that classic themes still get core default settings such as color palette and duotone. */ - private static function recursive_ksort( &$input_array ) { - foreach ( $input_array as &$value ) { - if ( is_array( $value ) ) { - self::recursive_ksort( $value ); - } - } - ksort( $input_array ); + public function test_core_default_settings_are_loaded_for_themes_without_theme_json() { + switch_theme( 'default' ); + + $settings = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( 'theme' )->get_settings(); + + $this->assertFalse( wp_theme_has_theme_json() ); + $this->assertTrue( $settings['color']['defaultPalette'] ); + $this->assertTrue( $settings['color']['defaultDuotone'] ); + $this->assertTrue( $settings['color']['defaultGradients'] ); } public function test_merges_child_theme_json_into_parent_theme_json() { @@ -245,7 +487,25 @@ public function test_merges_child_theme_json_into_parent_theme_json() { $expected_settings = array( 'color' => array( 'custom' => false, - 'customGradient' => true, + 'customGradient' => false, + 'duotone' => array( + 'theme' => array( + array( + 'colors' => array( '#333333', '#aaaaaa' ), + 'name' => 'Custom Duotone', + 'slug' => 'custom-duotone', + ), + ), + ), + 'gradients' => array( + 'theme' => array( + array( + 'name' => 'Custom gradient', + 'gradient' => 'linear-gradient(135deg,rgba(0,0,0) 0%,rgb(0,0,0) 100%)', + 'slug' => 'custom-gradient', + ), + ), + ), 'palette' => array( 'theme' => array( array( @@ -268,12 +528,22 @@ public function test_merges_child_theme_json_into_parent_theme_json() { 'link' => true, ), 'typography' => array( - 'customFontSize' => true, - 'lineHeight' => false, + 'customFontSize' => false, + 'lineHeight' => true, + 'fontSizes' => array( + 'theme' => array( + array( + 'name' => 'Custom', + 'slug' => 'custom', + 'size' => '100px', + ), + ), + ), ), 'spacing' => array( - 'units' => false, - 'padding' => false, + 'blockGap' => true, + 'units' => array( 'rem' ), + 'padding' => true, ), 'blocks' => array( 'core/paragraph' => array( @@ -304,8 +574,8 @@ public function test_merges_child_theme_json_into_parent_theme_json() { ), ), ); - self::recursive_ksort( $actual_settings ); - self::recursive_ksort( $expected_settings ); + wp_recursive_ksort( $actual_settings ); + wp_recursive_ksort( $expected_settings ); // Should merge settings. $this->assertSame( @@ -314,30 +584,44 @@ public function test_merges_child_theme_json_into_parent_theme_json() { ); $this->assertSame( - WP_Theme_JSON_Resolver_Gutenberg::get_theme_data()->get_custom_templates(), array( - 'page-home' => array( + 'page-home' => array( 'title' => 'Homepage', 'postTypes' => array( 'page' ), ), - ) + 'custom-single-post-template' => array( + 'title' => 'Custom Single Post template', + 'postTypes' => array( 'post' ), + ), + ), + WP_Theme_JSON_Resolver_Gutenberg::get_theme_data()->get_custom_templates() ); } + /** + * @covers WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles + */ public function test_get_user_data_from_wp_global_styles_does_not_use_uncached_queries() { // Switch to a theme that does have support. switch_theme( 'block-theme' ); wp_set_current_user( self::$administrator_id ); $theme = wp_get_theme(); WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( $theme ); - add_filter( 'query', array( $this, 'filter_db_query' ) ); - $query_count = count( $this->queries ); + $global_styles_query_count = 0; + add_filter( + 'query', + static function ( $query ) use ( &$global_styles_query_count ) { + if ( preg_match( '#post_type = \'wp_global_styles\'#', $query ) ) { + $global_styles_query_count++; + } + return $query; + } + ); for ( $i = 0; $i < 3; $i++ ) { WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( $theme ); - WP_Theme_JSON_Resolver_Gutenberg::clean_cached_data(); + _gutenberg_clean_theme_json_caches(); } - $query_count = count( $this->queries ) - $query_count; - $this->assertSame( 0, $query_count, 'Unexpected SQL queries detected for the wp_global_style post type prior to creation.' ); + $this->assertSame( 0, $global_styles_query_count, 'Unexpected SQL queries detected for the wp_global_style post type prior to creation.' ); $user_cpt = WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( $theme ); $this->assertEmpty( $user_cpt, 'User CPT is expected to be empty.' ); @@ -345,29 +629,29 @@ public function test_get_user_data_from_wp_global_styles_does_not_use_uncached_q $user_cpt = WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( $theme, true ); $this->assertNotEmpty( $user_cpt, 'User CPT is expected not to be empty.' ); - $query_count = count( $this->queries ); + $global_styles_query_count = 0; for ( $i = 0; $i < 3; $i++ ) { $new_user_cpt = WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( $theme ); - WP_Theme_JSON_Resolver_Gutenberg::clean_cached_data(); + _gutenberg_clean_theme_json_caches(); $this->assertSameSets( $user_cpt, $new_user_cpt, "User CPTs do not match on run {$i}." ); } - $query_count = count( $this->queries ) - $query_count; - $this->assertSame( 1, $query_count, 'Unexpected SQL queries detected for the wp_global_style post type after creation.' ); + $this->assertSame( 1, $global_styles_query_count, 'Unexpected SQL queries detected for the wp_global_style post type after creation.' ); } /** - * @covers WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles + * @covers WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles */ public function test_get_user_data_from_wp_global_styles_does_not_use_uncached_queries_for_logged_out_users() { + // Switch to a theme that does have support. + switch_theme( 'block-theme' ); $theme = wp_get_theme(); WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( $theme ); - add_filter( 'query', array( $this, 'filter_db_query' ) ); - $query_count = count( $this->queries ); + $query_count = get_num_queries(); for ( $i = 0; $i < 3; $i++ ) { WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( $theme ); - WP_Theme_JSON_Resolver_Gutenberg::clean_cached_data(); + _gutenberg_clean_theme_json_caches(); } - $query_count = count( $this->queries ) - $query_count; + $query_count = get_num_queries() - $query_count; $this->assertSame( 0, $query_count, 'Unexpected SQL queries detected for the wp_global_style post type prior to creation.' ); $user_cpt = WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( $theme ); @@ -375,21 +659,142 @@ public function test_get_user_data_from_wp_global_styles_does_not_use_uncached_q } /** - * Test that get_merged_data returns the data merged up to the proper origin. + * @covers WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles + */ + public function test_get_user_data_from_wp_global_styles_does_not_run_for_theme_without_support() { + // The 'default' theme does not support theme.json. + switch_theme( 'default' ); + wp_set_current_user( self::$administrator_id ); + $theme = wp_get_theme(); + + $start_queries = get_num_queries(); + + // When theme.json is not supported, the method should not run a query and always return an empty result. + $user_cpt = WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( $theme ); + $this->assertEmpty( $user_cpt, 'User CPT is expected to be empty.' ); + $this->assertSame( 0, get_num_queries() - $start_queries, 'Unexpected SQL query detected for theme without theme.json support.' ); + + $user_cpt = WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( $theme, true ); + $this->assertEmpty( $user_cpt, 'User CPT is expected to be empty.' ); + $this->assertSame( 0, get_num_queries() - $start_queries, 'Unexpected SQL query detected for theme without theme.json support.' ); + } + + /** + * @covers WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles + */ + public function test_get_user_data_from_wp_global_styles_does_exist() { + // Switch to a theme that does have support. + switch_theme( 'block-theme' ); + $theme = wp_get_theme(); + $post1 = WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( $theme, true ); + $this->assertIsArray( $post1 ); + $this->assertArrayHasKey( 'ID', $post1 ); + wp_delete_post( $post1['ID'], true ); + $post2 = WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( $theme, true ); + $this->assertIsArray( $post2 ); + $this->assertArrayHasKey( 'ID', $post2 ); + } + + /** + * @covers WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles + */ + public function test_get_user_data_from_wp_global_styles_create_post() { + // Switch to a theme that does have support. + switch_theme( 'block-theme' ); + $theme = wp_get_theme( 'testing' ); + $post1 = WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( $theme ); + $this->assertIsArray( $post1 ); + $this->assertSameSets( array(), $post1 ); + $post2 = WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( $theme ); + $this->assertIsArray( $post2 ); + $this->assertSameSets( array(), $post2 ); + $post3 = WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( $theme, true ); + $this->assertIsArray( $post3 ); + $this->assertArrayHasKey( 'ID', $post3 ); + } + + /** + * @covers WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles + */ + public function test_get_user_data_from_wp_global_styles_filter_state() { + // Switch to a theme that does have support. + switch_theme( 'block-theme' ); + $theme = wp_get_theme( 'foo' ); + $post1 = WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( $theme, true, array( 'publish' ) ); + $this->assertIsArray( $post1 ); + $this->assertArrayHasKey( 'ID', $post1 ); + $post2 = WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( $theme, false, array( 'draft' ) ); + $this->assertIsArray( $post2 ); + $this->assertSameSets( array(), $post2 ); + } + + /** + * @covers WP_Theme_JSON_Resolver_Gutenberg::get_theme_data + */ + public function test_get_theme_data_theme_supports_overrides_theme_json() { + switch_theme( 'default' ); + + // Test that get_theme_data() returns a WP_Theme_JSON_Gutenberg object. + $theme_json_resolver = new WP_Theme_JSON_Resolver_Gutenberg(); + $theme_json_resolver->get_merged_data(); + $theme_data = $theme_json_resolver->get_theme_data(); + $this->assertInstanceOf( 'WP_Theme_JSON_Gutenberg', $theme_data, 'Theme data should be an instance of WP_Theme_JSON_Gutenberg.' ); + + // Test that wp_theme_json_data_theme filter has been called. + $this->assertGreaterThan( 0, did_filter( 'wp_theme_json_data_default' ), 'The filter "wp_theme_json_data_default" should fire.' ); + + // Test that data from theme.json is backfilled from existing theme supports. + $previous_settings = $theme_data->get_settings(); + $previous_line_height = $previous_settings['typography']['lineHeight']; + $this->assertFalse( $previous_line_height, 'lineHeight setting from theme.json should be false.' ); + + add_theme_support( 'custom-line-height' ); + $current_settings = $theme_json_resolver->get_theme_data()->get_settings(); + $line_height = $current_settings['typography']['lineHeight']; + $this->assertTrue( $line_height, 'lineHeight setting after add_theme_support() should be true.' ); + remove_theme_support( 'custom-line-height' ); + } + + /** + * @covers WP_Theme_JSON_Resolver_Gutenberg::get_theme_data + */ + public function test_get_theme_data_does_not_parse_theme_json_if_not_present() { + // The 'default' theme does not support theme.json. + switch_theme( 'default' ); + + $theme_json_resolver = new WP_Theme_JSON_Resolver_Gutenberg(); + + // Force-unset $i18n_schema property to "unload" translation schema. + $property = new ReflectionProperty( $theme_json_resolver, 'i18n_schema' ); + $property->setAccessible( true ); + $property->setValue( null, null ); + + // A completely empty theme.json data set still has the 'version' key when parsed. + $empty_theme_json = array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA ); + + // Call using 'with_supports' set to false, so that the method only considers theme.json. + $theme_data = $theme_json_resolver->get_theme_data( array(), array( 'with_supports' => false ) ); + $this->assertInstanceOf( 'WP_Theme_JSON_Gutenberg', $theme_data, 'Theme data should be an instance of WP_Theme_JSON_Gutenberg.' ); + $this->assertSame( $empty_theme_json, $theme_data->get_raw_data(), 'Theme data should be empty without theme support.' ); + $this->assertNull( $property->getValue(), 'Theme i18n schema should not have been loaded without theme support.' ); + } + + /** + * Tests that get_merged_data returns the data merged up to the proper origin. * - * @covers WP_Theme_JSON_Resolver::get_merged_data + * @covers WP_Theme_JSON_Resolver_Gutenberg::get_merged_data * * @dataProvider data_get_merged_data_returns_origin * - * @param string $origin What origin to get data from. - * @param bool $core_palette Whether the core palette is present. - * @param string $core_palette_text Message. - * @param string $block_styles Whether the block styles are present. - * @param string $block_styles_text Message. + * @param string $origin What origin to get data from. + * @param bool $core_palette Whether the core palette is present. + * @param string $core_palette_text Message. + * @param string $block_styles Whether the block styles are present. + * @param string $block_styles_text Message. * @param bool $theme_palette Whether the theme palette is present. * @param string $theme_palette_text Message. - * @param bool $user_palette Whether the user palette is present. - * @param string $user_palette_text Message. + * @param bool $user_palette Whether the user palette is present. + * @param string $user_palette_text Message. */ public function test_get_merged_data_returns_origin( $origin, $core_palette, $core_palette_text, $block_styles, $block_styles_text, $theme_palette, $theme_palette_text, $user_palette, $user_palette_text ) { // Make sure there is data from the blocks origin. @@ -435,24 +840,64 @@ public function test_get_merged_data_returns_origin( $origin, $core_palette, $co $theme_json = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( $origin ); $settings = $theme_json->get_settings(); $styles = $theme_json->get_styles_block_nodes(); - $this->assertSame( $core_palette, isset( $settings['color']['palette']['default'] ), $core_palette_text ); - $styles = array_filter( + $styles = array_filter( $styles, static function ( $element ) { return isset( $element['name'] ) && 'my/block-with-styles' === $element['name']; } ); + unregister_block_type( 'my/block-with-styles' ); + + $this->assertSame( $core_palette, isset( $settings['color']['palette']['default'] ), $core_palette_text ); $this->assertSame( $block_styles, count( $styles ) === 1, $block_styles_text ); $this->assertSame( $theme_palette, isset( $settings['color']['palette']['theme'] ), $theme_palette_text ); $this->assertSame( $user_palette, isset( $settings['color']['palette']['custom'] ), $user_palette_text ); + } - unregister_block_type( 'my/block-with-styles' ); + /** + * Tests that get_merged_data returns the data merged up to the proper origin + * and that the core values have the proper data. + * + * @covers WP_Theme_JSON_Resolver_Gutenberg::get_merged_data + */ + public function test_get_merged_data_returns_origin_proper() { + // Make sure the theme has a theme.json + // though it doesn't have any data for styles.spacing.padding. + switch_theme( 'block-theme' ); + + // Make sure the user defined some data for styles.spacing.padding. + wp_set_current_user( self::$administrator_id ); + $user_cpt = WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( wp_get_theme(), true ); + $config = json_decode( $user_cpt['post_content'], true ); + $config['styles']['spacing']['padding'] = array( + 'top' => '23px', + 'left' => '23px', + 'bottom' => '23px', + 'right' => '23px', + ); + $user_cpt['post_content'] = wp_json_encode( $config ); + wp_update_post( $user_cpt, true, false ); + + // Query data from the user origin and then for the theme origin. + $theme_json_user = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( 'custom' ); + $padding_user = $theme_json_user->get_raw_data()['styles']['spacing']['padding']; + $theme_json_theme = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( 'theme' ); + $padding_theme = $theme_json_theme->get_raw_data()['styles']['spacing']['padding']; + + $this->assertSame( '23px', $padding_user['top'] ); + $this->assertSame( '23px', $padding_user['right'] ); + $this->assertSame( '23px', $padding_user['bottom'] ); + $this->assertSame( '23px', $padding_user['left'] ); + $this->assertSame( '0px', $padding_theme['top'] ); + $this->assertSame( '0px', $padding_theme['right'] ); + $this->assertSame( '0px', $padding_theme['bottom'] ); + $this->assertSame( '0px', $padding_theme['left'] ); } /** - * Data provider for test_get_merged_data_returns_origin + * Data provider for test_get_merged_data_returns_origin. * - * @return array + * @return array[] */ public function data_get_merged_data_returns_origin() { return array( @@ -503,13 +948,12 @@ public function data_get_merged_data_returns_origin() { ); } - /** - * Test that get_style_variations returns all variations, including parent theme variations if the theme is a child, + * Tests that get_style_variations returns all variations, including parent theme variations if the theme is a child, * and that the child variation overwrites the parent variation of the same name. * - * @covers WP_Theme_JSON_Resolver::get_style_variations - **/ + * @covers WP_Theme_JSON_Resolver_Gutenberg::get_style_variations + */ public function test_get_style_variations_returns_all_variations() { // Switch to a child theme. switch_theme( 'block-theme-child' ); @@ -518,7 +962,7 @@ public function test_get_style_variations_returns_all_variations() { $actual_settings = WP_Theme_JSON_Resolver_Gutenberg::get_style_variations(); $expected_settings = array( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'title' => 'variation-a', 'settings' => array( 'blocks' => array( @@ -539,7 +983,7 @@ public function test_get_style_variations_returns_all_variations() { ), ), array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'title' => 'variation-b', 'settings' => array( 'blocks' => array( @@ -548,9 +992,9 @@ public function test_get_style_variations_returns_all_variations() { 'palette' => array( 'theme' => array( array( - 'slug' => 'light', - 'name' => 'Light', - 'color' => '#f1f1f1', + 'slug' => 'dark', + 'name' => 'Dark', + 'color' => '#010101', ), ), ), @@ -559,9 +1003,36 @@ public function test_get_style_variations_returns_all_variations() { ), ), ), + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'title' => 'Block theme variation', + 'settings' => array( + 'color' => array( + 'palette' => array( + 'theme' => array( + array( + 'slug' => 'foreground', + 'name' => 'Foreground', + 'color' => '#3F67C6', + ), + ), + ), + ), + ), + 'styles' => array( + 'blocks' => array( + 'core/post-title' => array( + 'typography' => array( + 'fontWeight' => '700', + ), + ), + ), + ), + ), ); - self::recursive_ksort( $actual_settings ); - self::recursive_ksort( $expected_settings ); + + wp_recursive_ksort( $actual_settings ); + wp_recursive_ksort( $expected_settings ); $this->assertSame( $expected_settings, diff --git a/phpunit/class-wp-theme-json-schema-test.php b/phpunit/class-wp-theme-json-schema-test.php new file mode 100644 index 00000000000000..9c1103197d72e5 --- /dev/null +++ b/phpunit/class-wp-theme-json-schema-test.php @@ -0,0 +1,182 @@ + 1, + 'settings' => array( + 'color' => array( + 'palette' => array( + array( + 'name' => 'Pale Pink', + 'slug' => 'pale-pink', + 'color' => '#f78da7', + ), + array( + 'name' => 'Vivid Red', + 'slug' => 'vivid-red', + 'color' => '#cf2e2e', + ), + ), + 'custom' => false, + 'link' => true, + ), + 'border' => array( + 'color' => false, + 'customRadius' => false, + 'style' => false, + 'width' => false, + ), + 'typography' => array( + 'fontStyle' => false, + 'fontWeight' => false, + 'letterSpacing' => false, + 'textDecoration' => false, + 'textTransform' => false, + ), + 'blocks' => array( + 'core/group' => array( + 'border' => array( + 'color' => true, + 'customRadius' => true, + 'style' => true, + 'width' => true, + ), + 'typography' => array( + 'fontStyle' => true, + 'fontWeight' => true, + 'letterSpacing' => true, + 'textDecoration' => true, + 'textTransform' => true, + ), + ), + ), + ), + 'styles' => array( + 'color' => array( + 'background' => 'purple', + ), + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'background' => 'red', + ), + 'spacing' => array( + 'padding' => array( + 'top' => '10px', + ), + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'yellow', + ), + ), + ), + ), + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'red', + ), + ), + ), + ), + ); + + $actual = WP_Theme_JSON_Schema_Gutenberg::migrate( $theme_json_v1 ); + + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'palette' => array( + array( + 'name' => 'Pale Pink', + 'slug' => 'pale-pink', + 'color' => '#f78da7', + ), + array( + 'name' => 'Vivid Red', + 'slug' => 'vivid-red', + 'color' => '#cf2e2e', + ), + ), + 'custom' => false, + 'link' => true, + ), + 'border' => array( + 'color' => false, + 'radius' => false, + 'style' => false, + 'width' => false, + ), + 'typography' => array( + 'fontStyle' => false, + 'fontWeight' => false, + 'letterSpacing' => false, + 'textDecoration' => false, + 'textTransform' => false, + ), + 'blocks' => array( + 'core/group' => array( + 'border' => array( + 'color' => true, + 'radius' => true, + 'style' => true, + 'width' => true, + ), + 'typography' => array( + 'fontStyle' => true, + 'fontWeight' => true, + 'letterSpacing' => true, + 'textDecoration' => true, + 'textTransform' => true, + ), + ), + ), + ), + 'styles' => array( + 'color' => array( + 'background' => 'purple', + ), + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'background' => 'red', + ), + 'spacing' => array( + 'padding' => array( + 'top' => '10px', + ), + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'yellow', + ), + ), + ), + ), + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'red', + ), + ), + ), + ), + ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } +} diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 141a1caedcc33a..3018ea1a15b8e5 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -4,6 +4,8 @@ * Test WP_Theme_JSON_Gutenberg class. * * @package Gutenberg + * + * @covers WP_Theme_JSON_Gutenberg */ class WP_Theme_JSON_Gutenberg_Test extends WP_UnitTestCase { @@ -37,171 +39,341 @@ public static function set_up_before_class() { static::$user_id = self::factory()->user->create(); } - public function test_get_stylesheet_generates_layout_styles() { + public function test_get_settings() { $theme_json = new WP_Theme_JSON_Gutenberg( array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( - 'spacing' => array( - 'blockGap' => true, + 'color' => array( + 'custom' => false, + ), + 'layout' => array( + 'contentSize' => 'value', + 'invalid/key' => 'value', + ), + 'invalid/key' => 'value', + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'custom' => false, + ), + 'invalid/key' => 'value', + ), ), ), 'styles' => array( - 'spacing' => array( - 'blockGap' => '1em', + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => '#111', + ), + ), ), ), - ), - 'default' + ) ); - // Results also include root site blocks styles. - $this->assertEquals( - 'body { margin: 0;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: 1em; margin-block-end: 0; }:where(.wp-site-blocks) > :first-child:first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child:last-child { margin-block-end: 0; }body { --wp--style--block-gap: 1em; }:where(body .is-layout-flow) > :first-child:first-child{margin-block-start: 0;}:where(body .is-layout-flow) > :last-child:last-child{margin-block-end: 0;}:where(body .is-layout-flow) > *{margin-block-start: 1em;margin-block-end: 0;}:where(body .is-layout-constrained) > :first-child:first-child{margin-block-start: 0;}:where(body .is-layout-constrained) > :last-child:last-child{margin-block-end: 0;}:where(body .is-layout-constrained) > *{margin-block-start: 1em;margin-block-end: 0;}:where(body .is-layout-flex) {gap: 1em;}:where(body .is-layout-grid) {gap: 1em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}', - $theme_json->get_stylesheet( array( 'styles' ) ) + $actual = $theme_json->get_settings(); + + $expected = array( + 'color' => array( + 'custom' => false, + ), + 'layout' => array( + 'contentSize' => 'value', + ), + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'custom' => false, + ), + ), + ), ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); } - public function test_get_stylesheet_generates_valid_block_gap_values_and_skips_null_or_false_values() { - $theme_json = new WP_Theme_JSON_Gutenberg( + public function test_get_settings_presets_are_keyed_by_origin() { + $default_origin = new WP_Theme_JSON_Gutenberg( array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( - 'spacing' => array( - 'blockGap' => true, + 'color' => array( + 'palette' => array( + array( + 'slug' => 'white', + 'color' => 'white', + ), + ), + ), + 'invalid/key' => 'value', + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'palette' => array( + array( + 'slug' => 'white', + 'color' => 'white', + ), + ), + ), + ), ), ), - 'styles' => array( - 'spacing' => array( - 'blockGap' => '1rem', + ), + 'default' + ); + $no_origin = new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'palette' => array( + array( + 'slug' => 'black', + 'color' => 'black', + ), + ), ), - 'blocks' => array( - 'core/post-content' => array( + 'invalid/key' => 'value', + 'blocks' => array( + 'core/group' => array( 'color' => array( - 'text' => 'gray', // This value should not render block layout styles. + 'palette' => array( + array( + 'slug' => 'black', + 'color' => 'black', + ), + ), ), ), - 'core/social-links' => array( - 'spacing' => array( - 'blockGap' => '0', // This value should render block layout gap as zero. + ), + ), + ) + ); + + $actual_default = $default_origin->get_raw_data(); + $actual_no_origin = $no_origin->get_raw_data(); + + $expected_default = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'palette' => array( + 'default' => array( + array( + 'slug' => 'white', + 'color' => 'white', ), ), - 'core/buttons' => array( - 'spacing' => array( - 'blockGap' => 0, // This value should render block layout gap as zero. + ), + ), + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'palette' => array( + 'default' => array( + array( + 'slug' => 'white', + 'color' => 'white', + ), + ), ), ), - 'core/columns' => array( - 'spacing' => array( - 'blockGap' => false, // This value should be ignored. The block will use the global layout value. + ), + ), + ), + ); + $expected_no_origin = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'palette' => array( + 'theme' => array( + array( + 'slug' => 'black', + 'color' => 'black', + ), + ), + ), + ), + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'palette' => array( + 'theme' => array( + array( + 'slug' => 'black', + 'color' => 'black', + ), + ), ), ), ), ), ), - 'default' ); - $this->assertEquals( - 'body { margin: 0;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: 1rem; margin-block-end: 0; }:where(.wp-site-blocks) > :first-child:first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child:last-child { margin-block-end: 0; }body { --wp--style--block-gap: 1rem; }:where(body .is-layout-flow) > :first-child:first-child{margin-block-start: 0;}:where(body .is-layout-flow) > :last-child:last-child{margin-block-end: 0;}:where(body .is-layout-flow) > *{margin-block-start: 1rem;margin-block-end: 0;}:where(body .is-layout-constrained) > :first-child:first-child{margin-block-start: 0;}:where(body .is-layout-constrained) > :last-child:last-child{margin-block-end: 0;}:where(body .is-layout-constrained) > *{margin-block-start: 1rem;margin-block-end: 0;}:where(body .is-layout-flex) {gap: 1rem;}:where(body .is-layout-grid) {gap: 1rem;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}' . - '.wp-block-post-content{color: gray;}.wp-block-social-links-is-layout-flow > :first-child:first-child{margin-block-start: 0;}.wp-block-social-links-is-layout-flow > :last-child:last-child{margin-block-end: 0;}.wp-block-social-links-is-layout-flow > *{margin-block-start: 0;margin-block-end: 0;}.wp-block-social-links-is-layout-constrained > :first-child:first-child{margin-block-start: 0;}.wp-block-social-links-is-layout-constrained > :last-child:last-child{margin-block-end: 0;}.wp-block-social-links-is-layout-constrained > *{margin-block-start: 0;margin-block-end: 0;}.wp-block-social-links-is-layout-flex{gap: 0;}.wp-block-social-links-is-layout-grid{gap: 0;}.wp-block-buttons-is-layout-flow > :first-child:first-child{margin-block-start: 0;}.wp-block-buttons-is-layout-flow > :last-child:last-child{margin-block-end: 0;}.wp-block-buttons-is-layout-flow > *{margin-block-start: 0;margin-block-end: 0;}.wp-block-buttons-is-layout-constrained > :first-child:first-child{margin-block-start: 0;}.wp-block-buttons-is-layout-constrained > :last-child:last-child{margin-block-end: 0;}.wp-block-buttons-is-layout-constrained > *{margin-block-start: 0;margin-block-end: 0;}.wp-block-buttons-is-layout-flex{gap: 0;}.wp-block-buttons-is-layout-grid{gap: 0;}', - $theme_json->get_stylesheet() - ); + $this->assertEqualSetsWithIndex( $expected_default, $actual_default ); + $this->assertEqualSetsWithIndex( $expected_no_origin, $actual_no_origin ); } - public function test_get_stylesheet_generates_layout_styles_with_spacing_presets() { + public function test_get_settings_appearance_true_opts_in() { $theme_json = new WP_Theme_JSON_Gutenberg( array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( - 'spacing' => array( - 'blockGap' => true, + 'appearanceTools' => true, + 'spacing' => array( + 'blockGap' => false, // This should override appearanceTools. ), - ), - 'styles' => array( - 'spacing' => array( - 'blockGap' => 'var:preset|spacing|60', + 'blocks' => array( + 'core/paragraph' => array( + 'typography' => array( + 'lineHeight' => false, + ), + ), + 'core/group' => array( + 'appearanceTools' => true, + 'typography' => array( + 'lineHeight' => false, // This should override appearanceTools. + ), + 'spacing' => array( + 'blockGap' => null, + ), + ), ), ), - ), - 'default' - ); - - // Results also include root site blocks styles. - $this->assertEquals( - 'body { margin: 0;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: var(--wp--preset--spacing--60); margin-block-end: 0; }:where(.wp-site-blocks) > :first-child:first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child:last-child { margin-block-end: 0; }body { --wp--style--block-gap: var(--wp--preset--spacing--60); }:where(body .is-layout-flow) > :first-child:first-child{margin-block-start: 0;}:where(body .is-layout-flow) > :last-child:last-child{margin-block-end: 0;}:where(body .is-layout-flow) > *{margin-block-start: var(--wp--preset--spacing--60);margin-block-end: 0;}:where(body .is-layout-constrained) > :first-child:first-child{margin-block-start: 0;}:where(body .is-layout-constrained) > :last-child:last-child{margin-block-end: 0;}:where(body .is-layout-constrained) > *{margin-block-start: var(--wp--preset--spacing--60);margin-block-end: 0;}:where(body .is-layout-flex) {gap: var(--wp--preset--spacing--60);}:where(body .is-layout-grid) {gap: var(--wp--preset--spacing--60);}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}', - $theme_json->get_stylesheet( array( 'styles' ) ) + ) ); - } - public function test_get_stylesheet_generates_fallback_gap_layout_styles() { - $theme_json = new WP_Theme_JSON_Gutenberg( - array( - 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, - 'settings' => array( - 'spacing' => array( - 'blockGap' => null, + $actual = $theme_json->get_settings(); + $expected = array( + 'background' => array( + 'backgroundImage' => true, + 'backgroundSize' => true, + ), + 'border' => array( + 'width' => true, + 'style' => true, + 'radius' => true, + 'color' => true, + ), + 'color' => array( + 'link' => true, + 'heading' => true, + 'button' => true, + 'caption' => true, + ), + 'dimensions' => array( + 'aspectRatio' => true, + 'minHeight' => true, + ), + 'position' => array( + 'sticky' => true, + 'fixed' => true, + ), + 'spacing' => array( + 'blockGap' => false, + 'margin' => true, + 'padding' => true, + ), + 'typography' => array( + 'lineHeight' => true, + ), + 'blocks' => array( + 'core/paragraph' => array( + 'typography' => array( + 'lineHeight' => false, ), ), - 'styles' => array( - 'spacing' => array( - 'blockGap' => '1em', + 'core/group' => array( + 'background' => array( + 'backgroundImage' => true, + 'backgroundSize' => true, + ), + 'border' => array( + 'width' => true, + 'style' => true, + 'radius' => true, + 'color' => true, + ), + 'color' => array( + 'link' => true, + 'heading' => true, + 'button' => true, + 'caption' => true, + ), + 'dimensions' => array( + 'aspectRatio' => true, + 'minHeight' => true, + ), + 'position' => array( + 'sticky' => true, + 'fixed' => true, + ), + 'spacing' => array( + 'blockGap' => false, + 'margin' => true, + 'padding' => true, + ), + 'typography' => array( + 'lineHeight' => false, ), ), ), - 'default' ); - $stylesheet = $theme_json->get_stylesheet( array( 'styles' ) ); - // Results also include root site blocks styles. - $this->assertEquals( - 'body { margin: 0;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}', - $stylesheet - ); + $this->assertEqualSetsWithIndex( $expected, $actual ); } - public function test_get_stylesheet_generates_base_fallback_gap_layout_styles() { + public function test_get_settings_appearance_false_does_not_opt_in() { $theme_json = new WP_Theme_JSON_Gutenberg( array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( - 'spacing' => array( - 'blockGap' => null, + 'appearanceTools' => false, + 'border' => array( + 'width' => true, + ), + 'blocks' => array( + 'core/paragraph' => array( + 'typography' => array( + 'lineHeight' => false, + ), + ), + 'core/group' => array( + 'typography' => array( + 'lineHeight' => false, + ), + ), ), ), - ), - 'default' - ); - $stylesheet = $theme_json->get_stylesheet( array( 'base-layout-styles' ) ); - - // Note the `base-layout-styles` includes a fallback gap for the Columns block for backwards compatibility. - $this->assertEquals( - ':where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}:where(.wp-block-columns.is-layout-flex){gap: 2em;}:where(.wp-block-columns.is-layout-grid){gap: 2em;}:where(.wp-block-post-template.is-layout-flex){gap: 1.25em;}:where(.wp-block-post-template.is-layout-grid){gap: 1.25em;}', - $stylesheet + ) ); - } - public function test_get_stylesheet_skips_layout_styles() { - add_theme_support( 'disable-layout-styles' ); - $theme_json = new WP_Theme_JSON_Gutenberg( - array( - 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, - 'settings' => array( - 'spacing' => array( - 'blockGap' => null, + $actual = $theme_json->get_settings(); + $expected = array( + 'appearanceTools' => false, + 'border' => array( + 'width' => true, + ), + 'blocks' => array( + 'core/paragraph' => array( + 'typography' => array( + 'lineHeight' => false, + ), + ), + 'core/group' => array( + 'typography' => array( + 'lineHeight' => false, ), ), ), - 'default' ); - $stylesheet = $theme_json->get_stylesheet( array( 'base-layout-styles' ) ); - remove_theme_support( 'disable-layout-styles' ); - // All Layout styles should be skipped. - $this->assertEquals( - '', - $stylesheet - ); + $this->assertEqualSetsWithIndex( $expected, $actual ); } public function test_get_stylesheet() { @@ -210,13 +382,27 @@ public function test_get_stylesheet() { 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( 'color' => array( - 'text' => 'value', - 'palette' => array( + 'text' => 'value', + 'palette' => array( array( 'slug' => 'grey', 'color' => 'grey', ), ), + 'gradients' => array( + array( + 'gradient' => 'linear-gradient(135deg,rgba(0,0,0) 0%,rgb(0,0,0) 100%)', + 'name' => 'Custom gradient', + 'slug' => 'custom-gradient', + ), + ), + 'duotone' => array( + array( + 'colors' => array( '#333333', '#aaaaaa' ), + 'name' => 'Custom Duotone', + 'slug' => 'custom-duotone', + ), + ), ), 'typography' => array( 'fontFamilies' => array( @@ -257,15 +443,26 @@ public function test_get_stylesheet() { ), 'misc' => 'value', 'elements' => array( - 'link' => array( + 'link' => array( 'color' => array( 'text' => '#111', 'background' => '#333', ), ), + 'button' => array( + 'shadow' => '10px 10px 5px 0px rgba(0,0,0,0.66)', + ), ), 'blocks' => array( + 'core/cover' => array( + 'dimensions' => array( + 'aspectRatio' => '16/9', + ), + ), 'core/group' => array( + 'color' => array( + 'gradient' => 'var:preset|gradient|custom-gradient', + ), 'border' => array( 'radius' => '10px', ), @@ -329,44 +526,59 @@ public function test_get_stylesheet() { 'bottom' => '30px', ), ), + 'filter' => array( + 'duotone' => 'var:preset|duotone|custom-duotone', + ), ), ), + 'spacing' => array( + 'blockGap' => '24px', + ), ), 'misc' => 'value', ) ); - $variables = 'body{--wp--preset--color--grey: grey;--wp--preset--font-size--small: 14px;--wp--preset--font-size--big: 41px;--wp--preset--font-family--arial: Arial, serif;}.wp-block-group{--wp--custom--base-font: 16;--wp--custom--line-height--small: 1.2;--wp--custom--line-height--medium: 1.4;--wp--custom--line-height--large: 1.8;}'; - $styles = 'body { margin: 0;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}body{color: var(--wp--preset--color--grey);}a:where(:not(.wp-element-button)){background-color: #333;color: #111;}.wp-block-group{border-radius: 10px;min-height: 50vh;padding: 24px;}.wp-block-group a:where(:not(.wp-element-button)){color: #111;}.wp-block-heading{color: #123456;}.wp-block-heading a:where(:not(.wp-element-button)){background-color: #333;color: #111;font-size: 60px;}.wp-block-post-date{color: #123456;}.wp-block-post-date a:where(:not(.wp-element-button)){background-color: #777;color: #555;}.wp-block-post-excerpt{column-count: 2;}.wp-block-image{margin-bottom: 30px;}.wp-block-image img, .wp-block-image .wp-block-image__crop-area, .wp-block-image .components-placeholder{border-top-left-radius: 10px;border-bottom-right-radius: 1em;}'; - $presets = '.has-grey-color{color: var(--wp--preset--color--grey) !important;}.has-grey-background-color{background-color: var(--wp--preset--color--grey) !important;}.has-grey-border-color{border-color: var(--wp--preset--color--grey) !important;}.has-small-font-size{font-size: var(--wp--preset--font-size--small) !important;}.has-big-font-size{font-size: var(--wp--preset--font-size--big) !important;}.has-arial-font-family{font-family: var(--wp--preset--font-family--arial) !important;}'; - - $all = $variables . $styles . $presets; + $variables = 'body{--wp--preset--color--grey: grey;--wp--preset--gradient--custom-gradient: linear-gradient(135deg,rgba(0,0,0) 0%,rgb(0,0,0) 100%);--wp--preset--font-size--small: 14px;--wp--preset--font-size--big: 41px;--wp--preset--font-family--arial: Arial, serif;}.wp-block-group{--wp--custom--base-font: 16;--wp--custom--line-height--small: 1.2;--wp--custom--line-height--medium: 1.4;--wp--custom--line-height--large: 1.8;}'; + $styles = 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}body{color: var(--wp--preset--color--grey);}a:where(:not(.wp-element-button)){background-color: #333;color: #111;}.wp-element-button, .wp-block-button__link{box-shadow: 10px 10px 5px 0px rgba(0,0,0,0.66);}.wp-block-cover{min-height: unset;aspect-ratio: 16/9;}.wp-block-group{background: var(--wp--preset--gradient--custom-gradient);border-radius: 10px;min-height: 50vh;padding: 24px;}.wp-block-group a:where(:not(.wp-element-button)){color: #111;}.wp-block-heading{color: #123456;}.wp-block-heading a:where(:not(.wp-element-button)){background-color: #333;color: #111;font-size: 60px;}.wp-block-post-date{color: #123456;}.wp-block-post-date a:where(:not(.wp-element-button)){background-color: #777;color: #555;}.wp-block-post-excerpt{column-count: 2;}.wp-block-image{margin-bottom: 30px;}.wp-block-image img, .wp-block-image .wp-block-image__crop-area, .wp-block-image .components-placeholder{border-top-left-radius: 10px;border-bottom-right-radius: 1em;}.wp-block-image img, .wp-block-image .components-placeholder{filter: var(--wp--preset--duotone--custom-duotone);}'; + $presets = '.has-grey-color{color: var(--wp--preset--color--grey) !important;}.has-grey-background-color{background-color: var(--wp--preset--color--grey) !important;}.has-grey-border-color{border-color: var(--wp--preset--color--grey) !important;}.has-custom-gradient-gradient-background{background: var(--wp--preset--gradient--custom-gradient) !important;}.has-small-font-size{font-size: var(--wp--preset--font-size--small) !important;}.has-big-font-size{font-size: var(--wp--preset--font-size--big) !important;}.has-arial-font-family{font-family: var(--wp--preset--font-family--arial) !important;}'; + $all = $variables . $styles . $presets; - $this->assertEquals( $all, $theme_json->get_stylesheet() ); - $this->assertEquals( $styles, $theme_json->get_stylesheet( array( 'styles' ) ) ); - $this->assertEquals( $presets, $theme_json->get_stylesheet( array( 'presets' ) ) ); - $this->assertEquals( $variables, $theme_json->get_stylesheet( array( 'variables' ) ) ); + $this->assertSame( $variables, $theme_json->get_stylesheet( array( 'variables' ) ) ); + $this->assertSame( $styles, $theme_json->get_stylesheet( array( 'styles' ) ) ); + $this->assertSame( $presets, $theme_json->get_stylesheet( array( 'presets' ) ) ); + $this->assertSame( $all, $theme_json->get_stylesheet() ); } - /** - * Tests generating the spacing presets array based on the spacing scale provided. - * - * @dataProvider data_set_spacing_sizes_when_invalid - */ - public function test_shadow_preset_styles() { + public function test_get_stylesheet_support_for_shorthand_and_longhand_values() { $theme_json = new WP_Theme_JSON_Gutenberg( array( - 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, - 'settings' => array( - 'shadow' => array( - 'presets' => array( - array( - 'slug' => 'natural', - 'shadow' => '5px 5px 5px 0 black', + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/group' => array( + 'border' => array( + 'radius' => '10px', ), - array( - 'slug' => 'sharp', - 'shadow' => '5px 5px black', + 'spacing' => array( + 'padding' => '24px', + 'margin' => '1em', + ), + ), + 'core/image' => array( + 'border' => array( + 'radius' => array( + 'topLeft' => '10px', + 'bottomRight' => '1em', + ), + ), + 'spacing' => array( + 'padding' => array( + 'top' => '15px', + ), + 'margin' => array( + 'bottom' => '30px', + ), ), ), ), @@ -374,48 +586,209 @@ public function test_shadow_preset_styles() { ) ); - $styles = 'body{--wp--preset--shadow--natural: 5px 5px 5px 0 black;--wp--preset--shadow--sharp: 5px 5px black;}'; - $this->assertEquals( $styles, $theme_json->get_stylesheet() ); - $this->assertEquals( $styles, $theme_json->get_stylesheet( array( 'variables' ) ) ); + $styles = 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}.wp-block-group{border-radius: 10px;margin: 1em;padding: 24px;}.wp-block-image{margin-bottom: 30px;padding-top: 15px;}.wp-block-image img, .wp-block-image .wp-block-image__crop-area, .wp-block-image .components-placeholder{border-top-left-radius: 10px;border-bottom-right-radius: 1em;}'; + $this->assertSame( $styles, $theme_json->get_stylesheet() ); + $this->assertSame( $styles, $theme_json->get_stylesheet( array( 'styles' ) ) ); } - public function test_get_shadow_styles_for_blocks() { + public function test_get_stylesheet_skips_disabled_protected_properties() { $theme_json = new WP_Theme_JSON_Gutenberg( array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( - 'shadow' => array( - 'presets' => array( - array( - 'slug' => 'natural', - 'shadow' => '5px 5px 0 0 black', + 'spacing' => array( + 'blockGap' => null, + ), + ), + 'styles' => array( + 'spacing' => array( + 'blockGap' => '1em', + ), + 'blocks' => array( + 'core/columns' => array( + 'spacing' => array( + 'blockGap' => '24px', ), ), ), ), + ) + ); + + $expected = 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}:where(.wp-block-columns.is-layout-flex){gap: 2em;}:where(.wp-block-columns.is-layout-grid){gap: 2em;}'; + $this->assertSame( $expected, $theme_json->get_stylesheet() ); + $this->assertSame( $expected, $theme_json->get_stylesheet( array( 'styles' ) ) ); + } + + public function test_get_stylesheet_renders_enabled_protected_properties() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'spacing' => array( + 'blockGap' => true, + ), + ), 'styles' => array( - 'blocks' => array( - 'core/paragraph' => array( - 'shadow' => 'var(--wp--preset--shadow--natural)', + 'spacing' => array( + 'blockGap' => '1em', + ), + ), + ) + ); + + $expected = 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: 1em; margin-block-end: 0; }:where(.wp-site-blocks) > :first-child:first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child:last-child { margin-block-end: 0; }body { --wp--style--block-gap: 1em; }:where(body .is-layout-flow) > :first-child:first-child{margin-block-start: 0;}:where(body .is-layout-flow) > :last-child:last-child{margin-block-end: 0;}:where(body .is-layout-flow) > *{margin-block-start: 1em;margin-block-end: 0;}:where(body .is-layout-constrained) > :first-child:first-child{margin-block-start: 0;}:where(body .is-layout-constrained) > :last-child:last-child{margin-block-end: 0;}:where(body .is-layout-constrained) > *{margin-block-start: 1em;margin-block-end: 0;}:where(body .is-layout-flex) {gap: 1em;}:where(body .is-layout-grid) {gap: 1em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; + $this->assertSame( $expected, $theme_json->get_stylesheet() ); + $this->assertSame( $expected, $theme_json->get_stylesheet( array( 'styles' ) ) ); + } + + public function test_get_stylesheet_preset_classes_work_with_compounded_selectors() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'blocks' => array( + 'core/heading' => array( + 'color' => array( + 'palette' => array( + array( + 'slug' => 'white', + 'color' => '#fff', + ), + ), + ), ), ), - 'elements' => array( - 'button' => array( - 'shadow' => 'var:preset|shadow|natural', + ), + ) + ); + + $this->assertSame( + '.wp-block-heading.has-white-color{color: var(--wp--preset--color--white) !important;}.wp-block-heading.has-white-background-color{background-color: var(--wp--preset--color--white) !important;}.wp-block-heading.has-white-border-color{border-color: var(--wp--preset--color--white) !important;}', + $theme_json->get_stylesheet( array( 'presets' ) ) + ); + } + + public function test_get_stylesheet_preset_rules_come_after_block_rules() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'palette' => array( + array( + 'slug' => 'grey', + 'color' => 'grey', + ), + ), + ), ), - 'link' => array( - 'shadow' => array( 'ref' => 'styles.elements.button.shadow' ), + ), + ), + 'styles' => array( + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'text' => 'red', + ), + ), + ), + ), + ) + ); + + $styles = 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}.wp-block-group{color: red;}'; + $presets = '.wp-block-group.has-grey-color{color: var(--wp--preset--color--grey) !important;}.wp-block-group.has-grey-background-color{background-color: var(--wp--preset--color--grey) !important;}.wp-block-group.has-grey-border-color{border-color: var(--wp--preset--color--grey) !important;}'; + $variables = '.wp-block-group{--wp--preset--color--grey: grey;}'; + + $all = $variables . $styles . $presets; + + $this->assertSame( $all, $theme_json->get_stylesheet() ); + $this->assertSame( $styles, $theme_json->get_stylesheet( array( 'styles' ) ) ); + $this->assertSame( $presets, $theme_json->get_stylesheet( array( 'presets' ) ) ); + $this->assertSame( $variables, $theme_json->get_stylesheet( array( 'variables' ) ) ); + } + + public function test_get_stylesheet_generates_proper_classes_and_css_vars_from_slugs() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'palette' => array( + array( + 'slug' => 'grey', + 'color' => 'grey', + ), + array( + 'slug' => 'dark grey', + 'color' => 'grey', + ), + array( + 'slug' => 'light-grey', + 'color' => 'grey', + ), + array( + 'slug' => 'white2black', + 'color' => 'grey', + ), ), ), + 'custom' => array( + 'white2black' => 'value', + ), ), ) ); - $global_styles = 'body{--wp--preset--shadow--natural: 5px 5px 0 0 black;}body { margin: 0;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; - $element_styles = 'a:where(:not(.wp-element-button)){box-shadow: var(--wp--preset--shadow--natural);}.wp-element-button, .wp-block-button__link{box-shadow: var(--wp--preset--shadow--natural);}p{box-shadow: var(--wp--preset--shadow--natural);}'; - $styles = $global_styles . $element_styles; + $this->assertSame( + '.has-grey-color{color: var(--wp--preset--color--grey) !important;}.has-dark-grey-color{color: var(--wp--preset--color--dark-grey) !important;}.has-light-grey-color{color: var(--wp--preset--color--light-grey) !important;}.has-white-2-black-color{color: var(--wp--preset--color--white-2-black) !important;}.has-grey-background-color{background-color: var(--wp--preset--color--grey) !important;}.has-dark-grey-background-color{background-color: var(--wp--preset--color--dark-grey) !important;}.has-light-grey-background-color{background-color: var(--wp--preset--color--light-grey) !important;}.has-white-2-black-background-color{background-color: var(--wp--preset--color--white-2-black) !important;}.has-grey-border-color{border-color: var(--wp--preset--color--grey) !important;}.has-dark-grey-border-color{border-color: var(--wp--preset--color--dark-grey) !important;}.has-light-grey-border-color{border-color: var(--wp--preset--color--light-grey) !important;}.has-white-2-black-border-color{border-color: var(--wp--preset--color--white-2-black) !important;}', + $theme_json->get_stylesheet( array( 'presets' ) ) + ); + $this->assertSame( + 'body{--wp--preset--color--grey: grey;--wp--preset--color--dark-grey: grey;--wp--preset--color--light-grey: grey;--wp--preset--color--white-2-black: grey;--wp--custom--white-2-black: value;}', + $theme_json->get_stylesheet( array( 'variables' ) ) + ); + } + + public function test_get_stylesheet_preset_values_are_marked_as_important() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'palette' => array( + array( + 'slug' => 'grey', + 'color' => 'grey', + ), + ), + ), + ), + 'styles' => array( + 'blocks' => array( + 'core/paragraph' => array( + 'color' => array( + 'text' => 'red', + 'background' => 'blue', + ), + 'typography' => array( + 'fontSize' => '12px', + 'lineHeight' => '1.3', + ), + ), + ), + ), + ), + 'default' + ); - $this->assertEquals( $styles, $theme_json->get_stylesheet() ); + $this->assertSame( + 'body{--wp--preset--color--grey: grey;}body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}p{background-color: blue;color: red;font-size: 12px;line-height: 1.3;}.has-grey-color{color: var(--wp--preset--color--grey) !important;}.has-grey-background-color{background-color: var(--wp--preset--color--grey) !important;}.has-grey-border-color{border-color: var(--wp--preset--color--grey) !important;}', + $theme_json->get_stylesheet() + ); } public function test_get_stylesheet_handles_whitelisted_element_pseudo_selectors() { @@ -451,14 +824,14 @@ public function test_get_stylesheet_handles_whitelisted_element_pseudo_selectors ) ); - $base_styles = 'body { margin: 0;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; + $base_styles = 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; $element_styles = 'a:where(:not(.wp-element-button)){background-color: red;color: green;}a:where(:not(.wp-element-button)):hover{background-color: green;color: red;font-size: 10em;text-transform: uppercase;}a:where(:not(.wp-element-button)):focus{background-color: black;color: yellow;}'; $expected = $base_styles . $element_styles; - $this->assertEquals( $expected, $theme_json->get_stylesheet() ); - $this->assertEquals( $expected, $theme_json->get_stylesheet( array( 'styles' ) ) ); + $this->assertSame( $expected, $theme_json->get_stylesheet() ); + $this->assertSame( $expected, $theme_json->get_stylesheet( array( 'styles' ) ) ); } public function test_get_stylesheet_handles_only_pseudo_selector_rules_for_given_property() { @@ -490,14 +863,14 @@ public function test_get_stylesheet_handles_only_pseudo_selector_rules_for_given ) ); - $base_styles = 'body { margin: 0;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; + $base_styles = 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; $element_styles = 'a:where(:not(.wp-element-button)):hover{background-color: green;color: red;font-size: 10em;text-transform: uppercase;}a:where(:not(.wp-element-button)):focus{background-color: black;color: yellow;}'; $expected = $base_styles . $element_styles; - $this->assertEquals( $expected, $theme_json->get_stylesheet() ); - $this->assertEquals( $expected, $theme_json->get_stylesheet( array( 'styles' ) ) ); + $this->assertSame( $expected, $theme_json->get_stylesheet() ); + $this->assertSame( $expected, $theme_json->get_stylesheet( array( 'styles' ) ) ); } public function test_get_stylesheet_ignores_pseudo_selectors_on_non_whitelisted_elements() { @@ -529,14 +902,14 @@ public function test_get_stylesheet_ignores_pseudo_selectors_on_non_whitelisted_ ) ); - $base_styles = 'body { margin: 0;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; + $base_styles = 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; $element_styles = 'h4{background-color: red;color: green;}'; $expected = $base_styles . $element_styles; - $this->assertEquals( $expected, $theme_json->get_stylesheet() ); - $this->assertEquals( $expected, $theme_json->get_stylesheet( array( 'styles' ) ) ); + $this->assertSame( $expected, $theme_json->get_stylesheet() ); + $this->assertSame( $expected, $theme_json->get_stylesheet( array( 'styles' ) ) ); } public function test_get_stylesheet_ignores_non_whitelisted_pseudo_selectors() { @@ -568,14 +941,14 @@ public function test_get_stylesheet_ignores_non_whitelisted_pseudo_selectors() { ) ); - $base_styles = 'body { margin: 0;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; + $base_styles = 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; $element_styles = 'a:where(:not(.wp-element-button)){background-color: red;color: green;}a:where(:not(.wp-element-button)):hover{background-color: green;color: red;}'; $expected = $base_styles . $element_styles; - $this->assertEquals( $expected, $theme_json->get_stylesheet() ); - $this->assertEquals( $expected, $theme_json->get_stylesheet( array( 'styles' ) ) ); + $this->assertSame( $expected, $theme_json->get_stylesheet() ); + $this->assertSame( $expected, $theme_json->get_stylesheet( array( 'styles' ) ) ); $this->assertStringNotContainsString( 'a:levitate{', $theme_json->get_stylesheet( array( 'styles' ) ) ); } @@ -616,14 +989,14 @@ public function test_get_stylesheet_handles_priority_of_elements_vs_block_elemen ) ); - $base_styles = 'body { margin: 0;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; + $base_styles = 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; $element_styles = '.wp-block-group a:where(:not(.wp-element-button)){background-color: red;color: green;}.wp-block-group a:where(:not(.wp-element-button)):hover{background-color: green;color: red;font-size: 10em;text-transform: uppercase;}.wp-block-group a:where(:not(.wp-element-button)):focus{background-color: black;color: yellow;}'; $expected = $base_styles . $element_styles; - $this->assertEquals( $expected, $theme_json->get_stylesheet() ); - $this->assertEquals( $expected, $theme_json->get_stylesheet( array( 'styles' ) ) ); + $this->assertSame( $expected, $theme_json->get_stylesheet() ); + $this->assertSame( $expected, $theme_json->get_stylesheet( array( 'styles' ) ) ); } public function test_get_stylesheet_handles_whitelisted_block_level_element_pseudo_selectors() { @@ -663,14 +1036,14 @@ public function test_get_stylesheet_handles_whitelisted_block_level_element_pseu ) ); - $base_styles = 'body { margin: 0;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; + $base_styles = 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; $element_styles = 'a:where(:not(.wp-element-button)){background-color: red;color: green;}a:where(:not(.wp-element-button)):hover{background-color: green;color: red;}.wp-block-group a:where(:not(.wp-element-button)):hover{background-color: black;color: yellow;}'; $expected = $base_styles . $element_styles; - $this->assertEquals( $expected, $theme_json->get_stylesheet() ); - $this->assertEquals( $expected, $theme_json->get_stylesheet( array( 'styles' ) ) ); + $this->assertSame( $expected, $theme_json->get_stylesheet() ); + $this->assertSame( $expected, $theme_json->get_stylesheet( array( 'styles' ) ) ); } /** @@ -726,7 +1099,7 @@ public function test_get_stylesheet_with_deprecated_feature_level_selectors() { ) ); - $base_styles = 'body{--wp--preset--color--green: green;}body { margin: 0;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; + $base_styles = 'body{--wp--preset--color--green: green;}body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; $block_styles = '.wp-block-test, .wp-block-test__wrapper{color: green;}.wp-block-test .inner, .wp-block-test__wrapper .inner{border-radius: 9999px;padding: 20px;}.wp-block-test .sub-heading, .wp-block-test__wrapper .sub-heading{font-size: 3em;}'; $preset_styles = '.has-green-color{color: var(--wp--preset--color--green) !important;}.has-green-background-color{background-color: var(--wp--preset--color--green) !important;}.has-green-border-color{border-color: var(--wp--preset--color--green) !important;}'; $expected = $base_styles . $block_styles . $preset_styles; @@ -788,7 +1161,7 @@ public function test_get_stylesheet_with_block_json_selectors() { ) ); - $base_styles = 'body{--wp--preset--color--green: green;}body { margin: 0;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; + $base_styles = 'body{--wp--preset--color--green: green;}body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; $block_styles = '.custom-root-selector{background-color: grey;padding: 20px;}.custom-root-selector img{border-radius: 9999px;}.custom-root-selector > figcaption{color: navy;font-size: 3em;}'; $preset_styles = '.has-green-color{color: var(--wp--preset--color--green) !important;}.has-green-background-color{background-color: var(--wp--preset--color--green) !important;}.has-green-border-color{border-color: var(--wp--preset--color--green) !important;}'; $expected = $base_styles . $block_styles . $preset_styles; @@ -796,227 +1169,193 @@ public function test_get_stylesheet_with_block_json_selectors() { $this->assertEquals( $expected, $theme_json->get_stylesheet() ); } - public function test_allow_indirect_properties() { - $actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties( + public function test_get_stylesheet_generates_layout_styles() { + $theme_json = new WP_Theme_JSON_Gutenberg( array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, - 'styles' => array( - 'blocks' => array( - 'core/social-links' => array( - 'spacing' => array( - 'blockGap' => array( - 'top' => '1em', - 'left' => '2em', - ), - ), - ), - ), + 'settings' => array( 'spacing' => array( - 'blockGap' => '3em', + 'blockGap' => true, ), ), - 'settings' => array( - 'layout' => array( - 'contentSize' => '800px', - 'wideSize' => '1000px', + 'styles' => array( + 'spacing' => array( + 'blockGap' => '1em', ), ), - ) + ), + 'default' ); - $expected = array( - 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, - 'styles' => array( - 'blocks' => array( - 'core/social-links' => array( - 'spacing' => array( - 'blockGap' => array( - 'top' => '1em', - 'left' => '2em', - ), - ), + // Results also include root site blocks styles. + $this->assertSame( + 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: 1em; margin-block-end: 0; }:where(.wp-site-blocks) > :first-child:first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child:last-child { margin-block-end: 0; }body { --wp--style--block-gap: 1em; }:where(body .is-layout-flow) > :first-child:first-child{margin-block-start: 0;}:where(body .is-layout-flow) > :last-child:last-child{margin-block-end: 0;}:where(body .is-layout-flow) > *{margin-block-start: 1em;margin-block-end: 0;}:where(body .is-layout-constrained) > :first-child:first-child{margin-block-start: 0;}:where(body .is-layout-constrained) > :last-child:last-child{margin-block-end: 0;}:where(body .is-layout-constrained) > *{margin-block-start: 1em;margin-block-end: 0;}:where(body .is-layout-flex) {gap: 1em;}:where(body .is-layout-grid) {gap: 1em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}', + $theme_json->get_stylesheet( array( 'styles' ) ) + ); + } + + public function test_get_stylesheet_generates_layout_styles_with_spacing_presets() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'spacing' => array( + 'blockGap' => true, ), ), - 'spacing' => array( - 'blockGap' => '3em', - ), - ), - 'settings' => array( - 'layout' => array( - 'contentSize' => '800px', - 'wideSize' => '1000px', + 'styles' => array( + 'spacing' => array( + 'blockGap' => 'var:preset|spacing|60', + ), ), ), + 'default' ); - $this->assertEqualSetsWithIndex( $expected, $actual ); + // Results also include root site blocks styles. + $this->assertSame( + 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: var(--wp--preset--spacing--60); margin-block-end: 0; }:where(.wp-site-blocks) > :first-child:first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child:last-child { margin-block-end: 0; }body { --wp--style--block-gap: var(--wp--preset--spacing--60); }:where(body .is-layout-flow) > :first-child:first-child{margin-block-start: 0;}:where(body .is-layout-flow) > :last-child:last-child{margin-block-end: 0;}:where(body .is-layout-flow) > *{margin-block-start: var(--wp--preset--spacing--60);margin-block-end: 0;}:where(body .is-layout-constrained) > :first-child:first-child{margin-block-start: 0;}:where(body .is-layout-constrained) > :last-child:last-child{margin-block-end: 0;}:where(body .is-layout-constrained) > *{margin-block-start: var(--wp--preset--spacing--60);margin-block-end: 0;}:where(body .is-layout-flex) {gap: var(--wp--preset--spacing--60);}:where(body .is-layout-grid) {gap: var(--wp--preset--spacing--60);}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}', + $theme_json->get_stylesheet( array( 'styles' ) ) + ); } - public function test_remove_invalid_element_pseudo_selectors() { - $actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties( + public function test_get_stylesheet_generates_fallback_gap_layout_styles() { + $theme_json = new WP_Theme_JSON_Gutenberg( array( - 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, - 'styles' => array( - 'elements' => array( - 'link' => array( - 'color' => array( - 'text' => 'hotpink', - 'background' => 'yellow', - ), - ':hover' => array( - 'color' => array( - 'text' => 'red', - 'background' => 'blue', - ), - ), - ':seen' => array( - 'color' => array( - 'background' => 'ivory', - ), - ), - ), + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'spacing' => array( + 'blockGap' => null, ), ), - ), - true - ); - - $expected = array( - 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, - 'styles' => array( - 'elements' => array( - 'link' => array( - 'color' => array( - 'text' => 'hotpink', - 'background' => 'yellow', - ), - ':hover' => array( - 'color' => array( - 'text' => 'red', - 'background' => 'blue', - ), - ), + 'styles' => array( + 'spacing' => array( + 'blockGap' => '1em', ), ), ), + 'default' ); + $stylesheet = $theme_json->get_stylesheet( array( 'styles' ) ); - $this->assertEqualSetsWithIndex( $expected, $actual ); + // Results also include root site blocks styles. + $this->assertSame( + 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}', + $stylesheet + ); } - public function test_remove_invalid_font_family_settings() { - $actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties( + public function test_get_stylesheet_generates_base_fallback_gap_layout_styles() { + $theme_json = new WP_Theme_JSON_Gutenberg( array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( - 'typography' => array( - 'fontFamilies' => array( - 'custom' => array( - array( - 'name' => 'Open Sans', - 'slug' => 'open-sans', - 'fontFamily' => '"Open Sans", sans-serif', - ), - array( - 'name' => 'Arial', - 'slug' => 'arial', - 'fontFamily' => 'Arial, serif', - ), - ), - ), + 'spacing' => array( + 'blockGap' => null, ), ), ), - true + 'default' ); + $stylesheet = $theme_json->get_stylesheet( array( 'base-layout-styles' ) ); - $expected = array( - 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, - 'settings' => array( - 'typography' => array( - 'fontFamilies' => array( - 'custom' => array( - array( - 'name' => 'Arial', - 'slug' => 'arial', - 'fontFamily' => 'Arial, serif', - ), - ), + // Note the `base-layout-styles` includes a fallback gap for the Columns block for backwards compatibility. + $this->assertSame( + ':where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}:where(.wp-block-columns.is-layout-flex){gap: 2em;}:where(.wp-block-columns.is-layout-grid){gap: 2em;}:where(.wp-block-post-template.is-layout-flex){gap: 1.25em;}:where(.wp-block-post-template.is-layout-grid){gap: 1.25em;}', + $stylesheet + ); + } + + public function test_get_stylesheet_skips_layout_styles() { + add_theme_support( 'disable-layout-styles' ); + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'spacing' => array( + 'blockGap' => null, ), ), ), + 'default' ); + $stylesheet = $theme_json->get_stylesheet( array( 'base-layout-styles' ) ); + remove_theme_support( 'disable-layout-styles' ); - $this->assertEqualSetsWithIndex( $expected, $actual ); - } - - public function test_get_element_class_name_button() { - $expected = 'wp-element-button'; - $actual = WP_Theme_JSON_Gutenberg::get_element_class_name( 'button' ); - - $this->assertEquals( $expected, $actual ); - } - - public function test_get_element_class_name_invalid() { - $expected = ''; - $actual = WP_Theme_JSON_Gutenberg::get_element_class_name( 'unknown-element' ); - - $this->assertEquals( $expected, $actual ); + // All Layout styles should be skipped. + $this->assertSame( + '', + $stylesheet + ); } - /** - * Testing that dynamic properties in theme.json return the value they reference, e.g. - * array( 'ref' => 'styles.color.background' ) => "#ffffff". - */ - public function test_get_property_value_valid() { + public function test_get_stylesheet_generates_valid_block_gap_values_and_skips_null_or_false_values() { $theme_json = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, - 'styles' => array( - 'color' => array( - 'background' => '#ffffff', - 'text' => '#000000', + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'spacing' => array( + 'blockGap' => true, ), - 'elements' => array( - 'button' => array( + ), + 'styles' => array( + 'spacing' => array( + 'blockGap' => '1rem', + ), + 'blocks' => array( + 'core/post-content' => array( 'color' => array( - 'background' => array( 'ref' => 'styles.color.text' ), - 'text' => array( 'ref' => 'styles.color.background' ), + 'text' => 'gray', // This value should not render block layout styles. + ), + ), + 'core/social-links' => array( + 'spacing' => array( + 'blockGap' => '0', // This value should render block layout gap as zero. + ), + ), + 'core/buttons' => array( + 'spacing' => array( + 'blockGap' => 0, // This value should render block layout gap as zero. + ), + ), + 'core/columns' => array( + 'spacing' => array( + 'blockGap' => false, // This value should be ignored. The block will use the global layout value. ), ), ), ), - ) + ), + 'default' ); - $base_styles = 'body { margin: 0;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; - $color_styles = 'body{background-color: #ffffff;color: #000000;}.wp-element-button, .wp-block-button__link{background-color: #000000;color: #ffffff;}'; - - $expected = $base_styles . $color_styles; - $this->assertEquals( $expected, $theme_json->get_stylesheet() ); + $this->assertSame( + 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: 1rem; margin-block-end: 0; }:where(.wp-site-blocks) > :first-child:first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child:last-child { margin-block-end: 0; }body { --wp--style--block-gap: 1rem; }:where(body .is-layout-flow) > :first-child:first-child{margin-block-start: 0;}:where(body .is-layout-flow) > :last-child:last-child{margin-block-end: 0;}:where(body .is-layout-flow) > *{margin-block-start: 1rem;margin-block-end: 0;}:where(body .is-layout-constrained) > :first-child:first-child{margin-block-start: 0;}:where(body .is-layout-constrained) > :last-child:last-child{margin-block-end: 0;}:where(body .is-layout-constrained) > *{margin-block-start: 1rem;margin-block-end: 0;}:where(body .is-layout-flex) {gap: 1rem;}:where(body .is-layout-grid) {gap: 1rem;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}' . + '.wp-block-post-content{color: gray;}.wp-block-social-links-is-layout-flow > :first-child:first-child{margin-block-start: 0;}.wp-block-social-links-is-layout-flow > :last-child:last-child{margin-block-end: 0;}.wp-block-social-links-is-layout-flow > *{margin-block-start: 0;margin-block-end: 0;}.wp-block-social-links-is-layout-constrained > :first-child:first-child{margin-block-start: 0;}.wp-block-social-links-is-layout-constrained > :last-child:last-child{margin-block-end: 0;}.wp-block-social-links-is-layout-constrained > *{margin-block-start: 0;margin-block-end: 0;}.wp-block-social-links-is-layout-flex{gap: 0;}.wp-block-social-links-is-layout-grid{gap: 0;}.wp-block-buttons-is-layout-flow > :first-child:first-child{margin-block-start: 0;}.wp-block-buttons-is-layout-flow > :last-child:last-child{margin-block-end: 0;}.wp-block-buttons-is-layout-flow > *{margin-block-start: 0;margin-block-end: 0;}.wp-block-buttons-is-layout-constrained > :first-child:first-child{margin-block-start: 0;}.wp-block-buttons-is-layout-constrained > :last-child:last-child{margin-block-end: 0;}.wp-block-buttons-is-layout-constrained > *{margin-block-start: 0;margin-block-end: 0;}.wp-block-buttons-is-layout-flex{gap: 0;}.wp-block-buttons-is-layout-grid{gap: 0;}', + $theme_json->get_stylesheet() + ); } - /** - * Testing that dynamic properties in theme.json that - * refer to other dynamic properties in a loop - * then they should be left untouched. - * - * @expectedIncorrectUsage get_property_value - */ - public function test_get_property_value_loop() { + public function test_get_stylesheet_returns_outline_styles() { $theme_json = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'styles' => array( - 'color' => array( - 'background' => '#ffffff', - 'text' => array( 'ref' => 'styles.elements.button.color.background' ), - ), 'elements' => array( 'button' => array( - 'color' => array( - 'background' => array( 'ref' => 'styles.color.text' ), - 'text' => array( 'ref' => 'styles.color.background' ), + 'outline' => array( + 'offset' => '3px', + 'width' => '3px', + 'style' => 'dashed', + 'color' => 'red', + ), + ':hover' => array( + 'outline' => array( + 'offset' => '3px', + 'width' => '3px', + 'style' => 'solid', + 'color' => 'blue', + ), ), ), ), @@ -1024,514 +1363,613 @@ public function test_get_property_value_loop() { ) ); - $base_styles = 'body { margin: 0;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; - $color_styles = 'body{background-color: #ffffff;}.wp-element-button, .wp-block-button__link{color: #ffffff;}'; + $base_styles = 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; - $expected = $base_styles . $color_styles; + $element_styles = '.wp-element-button, .wp-block-button__link{outline-color: red;outline-offset: 3px;outline-style: dashed;outline-width: 3px;}.wp-element-button:hover, .wp-block-button__link:hover{outline-color: blue;outline-offset: 3px;outline-style: solid;outline-width: 3px;}'; + + $expected = $base_styles . $element_styles; $this->assertSame( $expected, $theme_json->get_stylesheet() ); } - /** - * Testing that dynamic properties in theme.json that - * refer to other dynamic properties then they should be left unprocessed. - * - * @expectedIncorrectUsage get_property_value - */ - public function test_get_property_value_recursion() { + public function test_get_stylesheet_custom_root_selector() { $theme_json = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'styles' => array( - 'color' => array( - 'background' => '#ffffff', - 'text' => array( 'ref' => 'styles.color.background' ), - ), - 'elements' => array( - 'button' => array( - 'color' => array( - 'background' => array( 'ref' => 'styles.color.text' ), - 'text' => array( 'ref' => 'styles.color.background' ), - ), - ), + 'color' => array( + 'text' => 'teal', ), ), - ) + ), + 'default' ); - $base_styles = 'body { margin: 0;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; - $color_styles = 'body{background-color: #ffffff;color: #ffffff;}.wp-element-button, .wp-block-button__link{color: #ffffff;}'; + $options = array( 'root_selector' => '.custom' ); + $actual = $theme_json->get_stylesheet( array( 'styles' ), null, $options ); - $expected = $base_styles . $color_styles; - $this->assertEquals( $expected, $theme_json->get_stylesheet() ); + // Results also include root site blocks styles which hard code + // `body { margin: 0; }`. + $this->assertSame( + 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }.custom{color: teal;}', + $actual + ); } - /** - * Testing that dynamic properties in theme.json that - * refer to themselves then they should be left unprocessed. - * - * @expectedIncorrectUsage get_property_value - */ - public function test_get_property_value_self() { - $theme_json = new WP_Theme_JSON_Gutenberg( + public function test_allow_indirect_properties() { + $actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties( array( - 'version' => 2, - 'styles' => array( - 'color' => array( - 'background' => '#ffffff', - 'text' => array( 'ref' => 'styles.color.text' ), + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/social-links' => array( + 'spacing' => array( + 'blockGap' => array( + 'top' => '1em', + 'left' => '2em', + ), + ), + ), + ), + 'spacing' => array( + 'blockGap' => '3em', + ), + ), + 'settings' => array( + 'layout' => array( + 'contentSize' => '800px', + 'wideSize' => '1000px', ), ), ) ); - $base_styles = 'body { margin: 0;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; - $color_styles = 'body{background-color: #ffffff;}'; - - $expected = $base_styles . $color_styles; - $this->assertEquals( $expected, $theme_json->get_stylesheet() ); + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/social-links' => array( + 'spacing' => array( + 'blockGap' => array( + 'top' => '1em', + 'left' => '2em', + ), + ), + ), + ), + 'spacing' => array( + 'blockGap' => '3em', + ), + ), + 'settings' => array( + 'layout' => array( + 'contentSize' => '800px', + 'wideSize' => '1000px', + ), + ), + ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); } - /** - * Tests generating the spacing presets array based on the spacing scale provided. - * - * @dataProvider data_generate_spacing_scale_fixtures - */ - public function test_set_spacing_sizes( $spacing_scale, $expected_output ) { + public function test_merge_incoming_data() { $theme_json = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( - 'spacing' => array( - 'spacingScale' => $spacing_scale, + 'color' => array( + 'custom' => false, + 'palette' => array( + array( + 'slug' => 'red', + 'color' => 'red', + ), + array( + 'slug' => 'green', + 'color' => 'green', + ), + ), + ), + 'blocks' => array( + 'core/paragraph' => array( + 'color' => array( + 'custom' => false, + ), + ), + ), + ), + 'styles' => array( + 'typography' => array( + 'fontSize' => '12', ), ), ) ); - $theme_json->set_spacing_sizes(); - $this->assertSame( $expected_output, _wp_array_get( $theme_json->get_raw_data(), array( 'settings', 'spacing', 'spacingSizes', 'default' ) ) ); - } - - /** - * Data provider for spacing scale tests. - * - * @return array - */ - public function data_generate_spacing_scale_fixtures() { - return array( - 'one_step_spacing_scale' => array( - 'spacingScale' => array( - 'operator' => '+', - 'increment' => 1.5, - 'steps' => 1, - 'mediumStep' => 4, - 'unit' => 'rem', + $add_new_block = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'blocks' => array( + 'core/list' => array( + 'color' => array( + 'custom' => false, + ), + ), ), - 'expected_output' => array( - array( - 'name' => '1', - 'slug' => '50', - 'size' => '4rem', + ), + 'styles' => array( + 'blocks' => array( + 'core/list' => array( + 'typography' => array( + 'fontSize' => '12', + ), + 'color' => array( + 'background' => 'brown', + ), ), ), ), + ); - 'two_step_spacing_scale_should_add_step_above_medium' => array( - 'spacingScale' => array( - 'operator' => '+', - 'increment' => 1.5, - 'steps' => 2, - 'mediumStep' => 4, - 'unit' => 'rem', - ), - 'expected_output' => array( - array( - 'name' => '1', - 'slug' => '50', - 'size' => '4rem', - ), - array( - 'name' => '2', - 'slug' => '60', - 'size' => '5.5rem', - ), + $add_key_in_settings = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'customGradient' => true, ), ), + ); - 'three_step_spacing_scale_should_add_step_above_and_below_medium' => array( - 'spacingScale' => array( - 'operator' => '+', - 'increment' => 1.5, - 'steps' => 3, - 'mediumStep' => 4, - 'unit' => 'rem', + $update_key_in_settings = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'custom' => true, ), - 'expected_output' => array( - array( - 'name' => '1', - 'slug' => '40', - 'size' => '2.5rem', - ), - array( - 'name' => '2', - 'slug' => '50', - 'size' => '4rem', + ), + ); + + $add_styles = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/group' => array( + 'spacing' => array( + 'padding' => array( + 'top' => '12px', + ), + ), ), - array( - 'name' => '3', - 'slug' => '60', - 'size' => '5.5rem', + ), + ), + ); + + $add_key_in_styles = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/group' => array( + 'spacing' => array( + 'padding' => array( + 'bottom' => '12px', + ), + ), ), ), ), + ); - 'even_step_spacing_scale_steps_should_add_extra_step_above_medium' => array( - 'spacingScale' => array( - 'operator' => '+', - 'increment' => 1.5, - 'steps' => 4, - 'mediumStep' => 4, - 'unit' => 'rem', + $add_invalid_context = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/para' => array( + 'typography' => array( + 'lineHeight' => '12', + ), + ), ), - 'expected_output' => array( - array( - 'name' => '1', - 'slug' => '40', - 'size' => '2.5rem', + ), + ); + + $update_presets = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'palette' => array( + array( + 'slug' => 'blue', + 'color' => 'blue', + ), ), - array( - 'name' => '2', - 'slug' => '50', - 'size' => '4rem', + 'gradients' => array( + array( + 'slug' => 'gradient', + 'gradient' => 'gradient', + ), ), - array( - 'name' => '3', - 'slug' => '60', - 'size' => '5.5rem', + ), + 'typography' => array( + 'fontSizes' => array( + array( + 'slug' => 'fontSize', + 'size' => 'fontSize', + ), ), - array( - 'name' => '4', - 'slug' => '70', - 'size' => '7rem', + 'fontFamilies' => array( + array( + 'slug' => 'fontFamily', + 'fontFamily' => 'fontFamily', + ), ), ), ), + ); - 'if_bottom_end_will_go_below_zero_should_add_extra_steps_above_medium_instead' => array( - 'spacingScale' => array( - 'operator' => '+', - 'increment' => 2.5, - 'steps' => 5, - 'mediumStep' => 5, - 'unit' => 'rem', - ), - 'expected_output' => array( - array( - 'name' => '1', - 'slug' => '40', - 'size' => '2.5rem', + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'custom' => true, + 'customGradient' => true, + 'palette' => array( + 'theme' => array( + array( + 'slug' => 'blue', + 'color' => 'blue', + ), + ), ), - array( - 'name' => '2', - 'slug' => '50', - 'size' => '5rem', + 'gradients' => array( + 'theme' => array( + array( + 'slug' => 'gradient', + 'gradient' => 'gradient', + ), + ), ), - array( - 'name' => '3', - 'slug' => '60', - 'size' => '7.5rem', + ), + 'typography' => array( + 'fontSizes' => array( + 'theme' => array( + array( + 'slug' => 'fontSize', + 'size' => 'fontSize', + ), + ), ), - array( - 'name' => '4', - 'slug' => '70', - 'size' => '10rem', + 'fontFamilies' => array( + 'theme' => array( + array( + 'slug' => 'fontFamily', + 'fontFamily' => 'fontFamily', + ), + ), ), - array( - 'name' => '5', - 'slug' => '80', - 'size' => '12.5rem', + ), + 'blocks' => array( + 'core/paragraph' => array( + 'color' => array( + 'custom' => false, + ), + ), + 'core/list' => array( + 'color' => array( + 'custom' => false, + ), ), ), ), - - 'multiplier_should_correctly_calculate_above_and_below_medium' => array( - 'spacingScale' => array( - 'operator' => '*', - 'increment' => 1.5, - 'steps' => 5, - 'mediumStep' => 1.5, - 'unit' => 'rem', + 'styles' => array( + 'typography' => array( + 'fontSize' => '12', ), - 'expected_output' => array( - array( - 'name' => '1', - 'slug' => '30', - 'size' => '0.67rem', + 'blocks' => array( + 'core/group' => array( + 'spacing' => array( + 'padding' => array( + 'top' => '12px', + 'bottom' => '12px', + ), + ), ), - array( - 'name' => '2', - 'slug' => '40', - 'size' => '1rem', - ), - array( - 'name' => '3', - 'slug' => '50', - 'size' => '1.5rem', - ), - array( - 'name' => '4', - 'slug' => '60', - 'size' => '2.25rem', - ), - array( - 'name' => '5', - 'slug' => '70', - 'size' => '3.38rem', - ), - ), - ), - - 'increment_<_1_combined_with_*_operator_should_act_as_divisor_to_calculate_above_and_below_medium' => array( - 'spacingScale' => array( - 'operator' => '*', - 'increment' => 0.25, - 'steps' => 5, - 'mediumStep' => 1.5, - 'unit' => 'rem', - ), - 'expected_output' => array( - array( - 'name' => '1', - 'slug' => '30', - 'size' => '0.09rem', - ), - array( - 'name' => '2', - 'slug' => '40', - 'size' => '0.38rem', - ), - array( - 'name' => '3', - 'slug' => '50', - 'size' => '1.5rem', - ), - array( - 'name' => '4', - 'slug' => '60', - 'size' => '6rem', - ), - array( - 'name' => '5', - 'slug' => '70', - 'size' => '24rem', + 'core/list' => array( + 'typography' => array( + 'fontSize' => '12', + ), + 'color' => array( + 'background' => 'brown', + ), ), ), ), ); - } - /** - * Tests generating the spacing presets array based on the spacing scale provided. - * - * @dataProvider data_set_spacing_sizes_when_invalid - */ - public function test_set_spacing_sizes_when_invalid( $spacing_scale, $expected_output ) { - $this->expectException( Exception::class ); - $this->expectExceptionMessage( 'Some of the theme.json settings.spacing.spacingScale values are invalid' ); + $theme_json->merge( new WP_Theme_JSON_Gutenberg( $add_new_block ) ); + $theme_json->merge( new WP_Theme_JSON_Gutenberg( $add_key_in_settings ) ); + $theme_json->merge( new WP_Theme_JSON_Gutenberg( $update_key_in_settings ) ); + $theme_json->merge( new WP_Theme_JSON_Gutenberg( $add_styles ) ); + $theme_json->merge( new WP_Theme_JSON_Gutenberg( $add_key_in_styles ) ); + $theme_json->merge( new WP_Theme_JSON_Gutenberg( $add_invalid_context ) ); + $theme_json->merge( new WP_Theme_JSON_Gutenberg( $update_presets ) ); + $actual = $theme_json->get_raw_data(); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + public function test_merge_incoming_data_empty_presets() { $theme_json = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( - 'spacing' => array( - 'spacingScale' => $spacing_scale, + 'color' => array( + 'duotone' => array( + array( + 'slug' => 'value', + 'colors' => array( 'red', 'green' ), + ), + ), + 'gradients' => array( + array( + 'slug' => 'gradient', + 'gradient' => 'gradient', + ), + ), + 'palette' => array( + array( + 'slug' => 'red', + 'color' => 'red', + ), + ), + ), + 'spacing' => array( + 'units' => array( 'px', 'em' ), + ), + 'typography' => array( + 'fontSizes' => array( + array( + 'slug' => 'size', + 'value' => 'size', + ), + ), ), ), ) ); - // Ensure PHPUnit 10 compatibility. - set_error_handler( - static function ( $errno, $errstr ) { - restore_error_handler(); - throw new Exception( $errstr, $errno ); - }, - E_ALL + $theme_json->merge( + new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'duotone' => array(), + 'gradients' => array(), + 'palette' => array(), + ), + 'spacing' => array( + 'units' => array(), + ), + 'typography' => array( + 'fontSizes' => array(), + ), + ), + ) + ) ); - $theme_json->set_spacing_sizes(); - $this->assertSame( $expected_output, _wp_array_get( $theme_json->get_raw_data(), array( 'settings', 'spacing', 'spacingSizes', 'default' ) ) ); - } - - /** - * Data provider for spacing scale tests. - * - * @return array - */ - public function data_set_spacing_sizes_when_invalid() { - return array( - - 'invalid_spacing_scale_values_missing_operator' => array( - 'spacingScale' => array( - 'operator' => '', - 'increment' => 1.5, - 'steps' => 1, - 'mediumStep' => 4, - 'unit' => 'rem', - ), - 'expected_output' => null, - ), - - 'invalid_spacing_scale_values_non_numeric_increment' => array( - 'spacingScale' => array( - 'operator' => '+', - 'increment' => 'add two to previous value', - 'steps' => 1, - 'mediumStep' => 4, - 'unit' => 'rem', - ), - 'expected_output' => null, - ), - - 'invalid_spacing_scale_values_non_numeric_steps' => array( - 'spacingScale' => array( - 'operator' => '+', - 'increment' => 1.5, - 'steps' => 'spiral staircase preferred', - 'mediumStep' => 4, - 'unit' => 'rem', + $actual = $theme_json->get_raw_data(); + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'duotone' => array( + 'theme' => array(), + ), + 'gradients' => array( + 'theme' => array(), + ), + 'palette' => array( + 'theme' => array(), + ), ), - 'expected_output' => null, - ), - - 'invalid_spacing_scale_values_non_numeric_medium_step' => array( - 'spacingScale' => array( - 'operator' => '+', - 'increment' => 1.5, - 'steps' => 5, - 'mediumStep' => 'That which is just right', - 'unit' => 'rem', + 'spacing' => array( + 'units' => array(), ), - 'expected_output' => null, - ), - - 'invalid_spacing_scale_values_missing_unit' => array( - 'spacingScale' => array( - 'operator' => '+', - 'increment' => 1.5, - 'steps' => 5, - 'mediumStep' => 4, + 'typography' => array( + 'fontSizes' => array( + 'theme' => array(), + ), ), - 'expected_output' => null, ), ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); } - public function test_get_styles_for_block_with_padding_aware_alignments() { + public function test_merge_incoming_data_null_presets() { $theme_json = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, - 'styles' => array( - 'spacing' => array( - 'padding' => array( - 'top' => '10px', - 'right' => '12px', - 'bottom' => '10px', - 'left' => '12px', + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'duotone' => array( + array( + 'slug' => 'value', + 'colors' => array( 'red', 'green' ), + ), + ), + 'gradients' => array( + array( + 'slug' => 'gradient', + 'gradient' => 'gradient', + ), + ), + 'palette' => array( + array( + 'slug' => 'red', + 'color' => 'red', + ), + ), + ), + 'spacing' => array( + 'units' => array( 'px', 'em' ), + ), + 'typography' => array( + 'fontSizes' => array( + array( + 'slug' => 'size', + 'value' => 'size', + ), ), ), - ), - 'settings' => array( - 'useRootPaddingAwareAlignments' => true, ), ) ); - $metadata = array( - 'path' => array( - '0' => 'styles', - ), - 'selector' => 'body', - ); - - $expected = 'body { margin: 0;}.wp-site-blocks { padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom); }.has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }.has-global-padding :where(.has-global-padding:not(.wp-block-block)) { padding-right: 0; padding-left: 0; }.has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); }.has-global-padding :where(.has-global-padding:not(.wp-block-block)) > .alignfull { margin-right: 0; margin-left: 0; }.has-global-padding > .alignfull:where(:not(.has-global-padding):not(.is-layout-flex):not(.is-layout-grid)) > :where([class*="wp-block-"]:not(.alignfull):not([class*="__"]),.wp-block:not(.alignfull),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }.has-global-padding :where(.has-global-padding) > .alignfull:where(:not(.has-global-padding)) > :where([class*="wp-block-"]:not(.alignfull):not([class*="__"]),.wp-block:not(.alignfull),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: 0; padding-left: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}body{--wp--style--root--padding-top: 10px;--wp--style--root--padding-right: 12px;--wp--style--root--padding-bottom: 10px;--wp--style--root--padding-left: 12px;}'; - $root_rules = $theme_json->get_root_layout_rules( WP_Theme_JSON::ROOT_BLOCK_SELECTOR, $metadata ); - $style_rules = $theme_json->get_styles_for_block( $metadata ); - $this->assertEquals( $expected, $root_rules . $style_rules ); - } - - public function test_get_styles_for_block_without_padding_aware_alignments() { - $theme_json = new WP_Theme_JSON_Gutenberg( - array( - 'version' => 2, - 'styles' => array( - 'spacing' => array( - 'padding' => array( - 'top' => '10px', - 'right' => '12px', - 'bottom' => '10px', - 'left' => '12px', + $theme_json->merge( + new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'custom' => false, + ), + 'spacing' => array( + 'margin' => false, + ), + 'typography' => array( + 'lineHeight' => false, ), ), - ), + ) ) ); - $metadata = array( - 'path' => array( - '0' => 'styles', + $actual = $theme_json->get_raw_data(); + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'custom' => false, + 'duotone' => array( + 'theme' => array( + array( + 'slug' => 'value', + 'colors' => array( 'red', 'green' ), + ), + ), + ), + 'gradients' => array( + 'theme' => array( + array( + 'slug' => 'gradient', + 'gradient' => 'gradient', + ), + ), + ), + 'palette' => array( + 'theme' => array( + array( + 'slug' => 'red', + 'color' => 'red', + ), + ), + ), + ), + 'spacing' => array( + 'margin' => false, + 'units' => array( 'px', 'em' ), + ), + 'typography' => array( + 'lineHeight' => false, + 'fontSizes' => array( + 'theme' => array( + array( + 'slug' => 'size', + 'value' => 'size', + ), + ), + ), + ), ), - 'selector' => 'body', ); - $expected = 'body { margin: 0;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}body{padding-top: 10px;padding-right: 12px;padding-bottom: 10px;padding-left: 12px;}'; - $root_rules = $theme_json->get_root_layout_rules( WP_Theme_JSON::ROOT_BLOCK_SELECTOR, $metadata ); - $style_rules = $theme_json->get_styles_for_block( $metadata ); - $this->assertEquals( $expected, $root_rules . $style_rules ); + $this->assertEqualSetsWithIndex( $expected, $actual ); } - public function test_get_styles_for_block_with_content_width() { - $theme_json = new WP_Theme_JSON_Gutenberg( + public function test_merge_incoming_data_color_presets_with_same_slugs_as_default_are_removed() { + $defaults = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( - 'layout' => array( - 'contentSize' => '800px', - 'wideSize' => '1000px', - ), - ), - ) - ); - - $metadata = array( - 'path' => array( - '0' => 'settings', + 'color' => array( + 'defaultPalette' => true, + 'palette' => array( + array( + 'slug' => 'red', + 'color' => 'red', + 'name' => 'Red', + ), + array( + 'slug' => 'green', + 'color' => 'green', + 'name' => 'Green', + ), + ), + ), + 'blocks' => array( + 'core/paragraph' => array( + 'color' => array( + 'palette' => array( + array( + 'slug' => 'blue', + 'color' => 'blue', + 'name' => 'Blue', + ), + ), + ), + ), + ), + ), ), - 'selector' => 'body', + 'default' ); - - $expected = 'body { margin: 0;--wp--style--global--content-size: 800px;--wp--style--global--wide-size: 1000px;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; - $root_rules = $theme_json->get_root_layout_rules( WP_Theme_JSON::ROOT_BLOCK_SELECTOR, $metadata ); - $style_rules = $theme_json->get_styles_for_block( $metadata ); - $this->assertEquals( $expected, $root_rules . $style_rules ); - } - - public function test_sanitize_for_unregistered_style_variations() { - $theme_json = new WP_Theme_JSON_Gutenberg( + $theme = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, - 'styles' => array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'palette' => array( + array( + 'slug' => 'pink', + 'color' => 'pink', + 'name' => 'Pink', + ), + array( + 'slug' => 'green', + 'color' => 'green', + 'name' => 'Greenish', + ), + ), + ), 'blocks' => array( - 'core/quote' => array( - 'variations' => array( - 'unregisteredVariation' => array( - 'color' => array( - 'background' => 'hotpink', + 'core/paragraph' => array( + 'color' => array( + 'palette' => array( + array( + 'slug' => 'blue', + 'color' => 'blue', + 'name' => 'Bluish', ), - ), - 'plain' => array( - 'color' => array( - 'background' => 'hotpink', + array( + 'slug' => 'yellow', + 'color' => 'yellow', + 'name' => 'Yellow', + ), + array( + 'slug' => 'green', + 'color' => 'green', + 'name' => 'Block Green', ), ), ), @@ -1541,16 +1979,50 @@ public function test_sanitize_for_unregistered_style_variations() { ) ); - $sanitized_theme_json = $theme_json->get_raw_data(); - $expected = array( - 'version' => 2, - 'styles' => array( + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'palette' => array( + 'default' => array( + array( + 'slug' => 'red', + 'color' => 'red', + 'name' => 'Red', + ), + array( + 'slug' => 'green', + 'color' => 'green', + 'name' => 'Green', + ), + ), + 'theme' => array( + array( + 'slug' => 'pink', + 'color' => 'pink', + 'name' => 'Pink', + ), + ), + ), + 'defaultPalette' => true, + ), 'blocks' => array( - 'core/quote' => array( - 'variations' => array( - 'plain' => array( - 'color' => array( - 'background' => 'hotpink', + 'core/paragraph' => array( + 'color' => array( + 'palette' => array( + 'default' => array( + array( + 'slug' => 'blue', + 'color' => 'blue', + 'name' => 'Blue', + ), + ), + 'theme' => array( + array( + 'slug' => 'yellow', + 'color' => 'yellow', + 'name' => 'Yellow', + ), ), ), ), @@ -1558,58 +2030,41 @@ public function test_sanitize_for_unregistered_style_variations() { ), ), ); - $this->assertSameSetsWithIndex( $expected, $sanitized_theme_json, 'Sanitized theme.json styles does not match' ); - } - /** - * @dataProvider data_sanitize_for_block_with_style_variations - * - * @param array $theme_json_variations Theme.json variations to test. - * @param array $expected_sanitized Expected results after sanitizing. - */ - public function test_sanitize_for_block_with_style_variations( $theme_json_variations, $expected_sanitized ) { - $theme_json = new WP_Theme_JSON_Gutenberg( - array( - 'version' => 2, - 'styles' => array( - 'blocks' => array( - 'core/quote' => $theme_json_variations, - ), - ), - ) - ); + $defaults->merge( $theme ); + $actual = $defaults->get_raw_data(); - // Validate structure is sanitized. - $sanitized_theme_json = $theme_json->get_raw_data(); - $this->assertIsArray( $sanitized_theme_json, 'Sanitized theme.json is not an array data type' ); - $this->assertArrayHasKey( 'styles', $sanitized_theme_json, 'Sanitized theme.json does not have an "styles" key' ); - $this->assertSameSetsWithIndex( $expected_sanitized, $sanitized_theme_json['styles'], 'Sanitized theme.json styles does not match' ); + $this->assertEqualSetsWithIndex( $expected, $actual ); } - /** - * Data provider. - * - * @return array - */ - public function data_sanitize_for_block_with_style_variations() { - return array( - '1 variation with 1 valid property' => array( - 'theme_json_variations' => array( - 'variations' => array( - 'plain' => array( - 'color' => array( - 'background' => 'hotpink', + public function test_merge_incoming_data_color_presets_with_same_slugs_as_default_are_not_removed_if_defaults_are_disabled() { + $defaults = new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'defaultPalette' => true, // Emulate the defaults from core theme.json. + 'palette' => array( + array( + 'slug' => 'red', + 'color' => 'red', + 'name' => 'Red', + ), + array( + 'slug' => 'green', + 'color' => 'green', + 'name' => 'Green', ), ), ), - ), - 'expected_sanitized' => array( 'blocks' => array( - 'core/quote' => array( - 'variations' => array( - 'plain' => array( - 'color' => array( - 'background' => 'hotpink', + 'core/paragraph' => array( + 'color' => array( + 'palette' => array( + array( + 'slug' => 'blue', + 'color' => 'blue', + 'name' => 'Blue', ), ), ), @@ -1617,473 +2072,2671 @@ public function data_sanitize_for_block_with_style_variations() { ), ), ), - '1 variation with 2 invalid properties' => array( - 'theme_json_variations' => array( - 'variations' => array( - 'plain' => array( - 'color' => array( - 'background' => 'hotpink', + 'default' + ); + $theme = new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'defaultPalette' => false, + 'palette' => array( + array( + 'slug' => 'pink', + 'color' => 'pink', + 'name' => 'Pink', + ), + array( + 'slug' => 'green', + 'color' => 'green', + 'name' => 'Greenish', ), - 'invalidProperty1' => 'value1', - 'invalidProperty2' => 'value2', ), ), - ), - 'expected_sanitized' => array( 'blocks' => array( - 'core/quote' => array( - 'variations' => array( - 'plain' => array( - 'color' => array( - 'background' => 'hotpink', + 'core/paragraph' => array( + 'color' => array( + 'palette' => array( + array( + 'slug' => 'blue', + 'color' => 'blue', + 'name' => 'Bluish', + ), + array( + 'slug' => 'yellow', + 'color' => 'yellow', + 'name' => 'Yellow', + ), + array( + 'slug' => 'green', + 'color' => 'green', + 'name' => 'Block Green', ), ), ), ), ), ), - ), + ) ); - } - public function test_sanitize_indexed_arrays() { - $theme_json = new WP_Theme_JSON_Gutenberg( - array( - 'version' => '2', - 'badKey2' => 'I am Evil!!!!', - 'settings' => array( - 'badKey3' => 'I am Evil!!!!', - 'typography' => array( - 'badKey4' => 'I am Evil!!!!', - 'fontFamilies' => array( - 'custom' => array( - array( - 'badKey4' => 'I am Evil!!!!', - 'name' => 'Arial', - 'slug' => 'arial', - 'fontFamily' => 'Arial, sans-serif', - ), + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'defaultPalette' => false, + 'palette' => array( + 'default' => array( + array( + 'slug' => 'red', + 'color' => 'red', + 'name' => 'Red', ), - 'theme' => array( - array( - 'badKey5' => 'I am Evil!!!!', - 'name' => 'Piazzolla', - 'slug' => 'piazzolla', - 'fontFamily' => 'Piazzolla', - 'fontFace' => array( - array( - 'badKey6' => 'I am Evil!!!!', - 'fontFamily' => 'Piazzolla', - 'fontStyle' => 'italic', - 'fontWeight' => '400', - 'src' => 'https://example.com/font.ttf', - ), - array( - 'badKey7' => 'I am Evil!!!!', - 'fontFamily' => 'Piazzolla', - 'fontStyle' => 'italic', - 'fontWeight' => '400', - 'src' => 'https://example.com/font.ttf', - ), + array( + 'slug' => 'green', + 'color' => 'green', + 'name' => 'Green', + ), + ), + 'theme' => array( + array( + 'slug' => 'pink', + 'color' => 'pink', + 'name' => 'Pink', + ), + array( + 'slug' => 'green', + 'color' => 'green', + 'name' => 'Greenish', + ), + ), + ), + ), + 'blocks' => array( + 'core/paragraph' => array( + 'color' => array( + 'palette' => array( + 'default' => array( + array( + 'slug' => 'blue', + 'color' => 'blue', + 'name' => 'Blue', ), ), - array( - 'badKey8' => 'I am Evil!!!!', - 'name' => 'Inter', - 'slug' => 'Inter', - 'fontFamily' => 'Inter', - 'fontFace' => array( - array( - 'badKey9' => 'I am Evil!!!!', - 'fontFamily' => 'Inter', - 'fontStyle' => 'italic', - 'fontWeight' => '400', - 'src' => 'https://example.com/font.ttf', - ), - array( - 'badKey10' => 'I am Evil!!!!', - 'fontFamily' => 'Inter', - 'fontStyle' => 'italic', - 'fontWeight' => '400', - 'src' => 'https://example.com/font.ttf', - ), + 'theme' => array( + array( + 'slug' => 'blue', + 'color' => 'blue', + 'name' => 'Bluish', + ), + array( + 'slug' => 'yellow', + 'color' => 'yellow', + 'name' => 'Yellow', + ), + array( + 'slug' => 'green', + 'color' => 'green', + 'name' => 'Block Green', ), ), ), ), ), ), - ) + ), ); - $expected_sanitized = array( - 'version' => '2', + $defaults->merge( $theme ); + $actual = $defaults->get_raw_data(); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + + public function test_merge_incoming_data_presets_use_default_names() { + $defaults = new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'typography' => array( + 'fontSizes' => array( + array( + 'name' => 'Small', + 'slug' => 'small', + 'size' => '12px', + ), + array( + 'name' => 'Large', + 'slug' => 'large', + 'size' => '20px', + ), + ), + ), + ), + ), + 'default' + ); + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'typography' => array( + 'fontSizes' => array( + array( + 'slug' => 'small', + 'size' => '1.1rem', + ), + array( + 'slug' => 'large', + 'size' => '1.75rem', + ), + array( + 'name' => 'Huge', + 'slug' => 'huge', + 'size' => '3rem', + ), + ), + ), + ), + ), + 'theme' + ); + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( 'typography' => array( - 'fontFamilies' => array( - 'custom' => array( + 'fontSizes' => array( + 'default' => array( array( - 'name' => 'Arial', - 'slug' => 'arial', - 'fontFamily' => 'Arial, sans-serif', + 'name' => 'Small', + 'slug' => 'small', + 'size' => '12px', + ), + array( + 'name' => 'Large', + 'slug' => 'large', + 'size' => '20px', ), ), - 'theme' => array( + 'theme' => array( array( - 'name' => 'Piazzolla', - 'slug' => 'piazzolla', - 'fontFamily' => 'Piazzolla', - 'fontFace' => array( - array( - 'fontFamily' => 'Piazzolla', - 'fontStyle' => 'italic', - 'fontWeight' => '400', - 'src' => 'https://example.com/font.ttf', + 'slug' => 'small', + 'size' => '1.1rem', + 'name' => 'Small', + ), + array( + 'slug' => 'large', + 'size' => '1.75rem', + 'name' => 'Large', + ), + array( + 'name' => 'Huge', + 'slug' => 'huge', + 'size' => '3rem', + ), + ), + ), + ), + ), + ); + $defaults->merge( $theme_json ); + $actual = $defaults->get_raw_data(); + $this->assertSameSetsWithIndex( $expected, $actual ); + } + + public function test_remove_insecure_properties_removes_unsafe_styles() { + $actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'color' => array( + 'gradient' => 'url(\'data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxMCcgaGVpZ2h0PScxMCc+PHNjcmlwdD5hbGVydCgnb2snKTwvc2NyaXB0PjxsaW5lYXJHcmFkaWVudCBpZD0nZ3JhZGllbnQnPjxzdG9wIG9mZnNldD0nMTAlJyBzdG9wLWNvbG9yPScjRjAwJy8+PHN0b3Agb2Zmc2V0PSc5MCUnIHN0b3AtY29sb3I9JyNmY2MnLz4gPC9saW5lYXJHcmFkaWVudD48cmVjdCBmaWxsPSd1cmwoI2dyYWRpZW50KScgeD0nMCcgeT0nMCcgd2lkdGg9JzEwMCUnIGhlaWdodD0nMTAwJScvPjwvc3ZnPg==\')', + 'text' => 'var:preset|color|dark-red', + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'gradient' => 'url(\'data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxMCcgaGVpZ2h0PScxMCc+PHNjcmlwdD5hbGVydCgnb2snKTwvc2NyaXB0PjxsaW5lYXJHcmFkaWVudCBpZD0nZ3JhZGllbnQnPjxzdG9wIG9mZnNldD0nMTAlJyBzdG9wLWNvbG9yPScjRjAwJy8+PHN0b3Agb2Zmc2V0PSc5MCUnIHN0b3AtY29sb3I9JyNmY2MnLz4gPC9saW5lYXJHcmFkaWVudD48cmVjdCBmaWxsPSd1cmwoI2dyYWRpZW50KScgeD0nMCcgeT0nMCcgd2lkdGg9JzEwMCUnIGhlaWdodD0nMTAwJScvPjwvc3ZnPg==\')', + 'text' => 'var:preset|color|dark-pink', + 'background' => 'var:preset|color|dark-red', + ), + ), + ), + 'blocks' => array( + 'core/image' => array( + 'filter' => array( + 'duotone' => 'var:preset|duotone|blue-red', + ), + ), + 'core/cover' => array( + 'filter' => array( + 'duotone' => 'var(--invalid', + ), + ), + 'core/group' => array( + 'color' => array( + 'gradient' => 'url(\'data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxMCcgaGVpZ2h0PScxMCc+PHNjcmlwdD5hbGVydCgnb2snKTwvc2NyaXB0PjxsaW5lYXJHcmFkaWVudCBpZD0nZ3JhZGllbnQnPjxzdG9wIG9mZnNldD0nMTAlJyBzdG9wLWNvbG9yPScjRjAwJy8+PHN0b3Agb2Zmc2V0PSc5MCUnIHN0b3AtY29sb3I9JyNmY2MnLz4gPC9saW5lYXJHcmFkaWVudD48cmVjdCBmaWxsPSd1cmwoI2dyYWRpZW50KScgeD0nMCcgeT0nMCcgd2lkdGg9JzEwMCUnIGhlaWdodD0nMTAwJScvPjwvc3ZnPg==\')', + 'text' => 'var:preset|color|dark-gray', + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'gradient' => 'url(\'data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxMCcgaGVpZ2h0PScxMCc+PHNjcmlwdD5hbGVydCgnb2snKTwvc2NyaXB0PjxsaW5lYXJHcmFkaWVudCBpZD0nZ3JhZGllbnQnPjxzdG9wIG9mZnNldD0nMTAlJyBzdG9wLWNvbG9yPScjRjAwJy8+PHN0b3Agb2Zmc2V0PSc5MCUnIHN0b3AtY29sb3I9JyNmY2MnLz4gPC9saW5lYXJHcmFkaWVudD48cmVjdCBmaWxsPSd1cmwoI2dyYWRpZW50KScgeD0nMCcgeT0nMCcgd2lkdGg9JzEwMCUnIGhlaWdodD0nMTAwJScvPjwvc3ZnPg==\')', + 'text' => 'var:preset|color|dark-pink', ), - array( - 'fontFamily' => 'Piazzolla', - 'fontStyle' => 'italic', - 'fontWeight' => '400', - 'src' => 'https://example.com/font.ttf', + ), + ), + ), + 'invalid/key' => array( + 'background' => 'green', + ), + ), + ), + ) + ); + + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'color' => array( + 'text' => 'var(--wp--preset--color--dark-red)', + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'var(--wp--preset--color--dark-pink)', + 'background' => 'var(--wp--preset--color--dark-red)', + ), + ), + ), + 'blocks' => array( + 'core/image' => array( + 'filter' => array( + 'duotone' => 'var(--wp--preset--duotone--blue-red)', + ), + ), + 'core/group' => array( + 'color' => array( + 'text' => 'var(--wp--preset--color--dark-gray)', + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'var(--wp--preset--color--dark-pink)', + ), + ), + ), + ), + ), + ), + ); + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + + public function test_remove_insecure_properties_removes_unsafe_styles_sub_properties() { + $actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'border' => array( + 'radius' => array( + 'topLeft' => '6px', + 'topRight' => 'var(--invalid', + 'bottomRight' => '6px', + 'bottomLeft' => '6px', + ), + ), + 'spacing' => array( + 'padding' => array( + 'top' => '1px', + 'right' => '1px', + 'bottom' => 'var(--invalid', + 'left' => '1px', + ), + ), + 'elements' => array( + 'link' => array( + 'spacing' => array( + 'padding' => array( + 'top' => '2px', + 'right' => '2px', + 'bottom' => 'var(--invalid', + 'left' => '2px', + ), + ), + ), + ), + 'blocks' => array( + 'core/group' => array( + 'border' => array( + 'radius' => array( + 'topLeft' => '5px', + 'topRight' => 'var(--invalid', + 'bottomRight' => '5px', + 'bottomLeft' => '5px', + ), + ), + 'spacing' => array( + 'padding' => array( + 'top' => '3px', + 'right' => '3px', + 'bottom' => 'var(--invalid', + 'left' => '3px', + ), + ), + 'elements' => array( + 'link' => array( + 'spacing' => array( + 'padding' => array( + 'top' => '4px', + 'right' => '4px', + 'bottom' => 'var(--invalid', + 'left' => '4px', + ), ), ), ), - array( - 'name' => 'Inter', - 'slug' => 'Inter', - 'fontFamily' => 'Inter', - 'fontFace' => array( - array( - 'fontFamily' => 'Inter', - 'fontStyle' => 'italic', - 'fontWeight' => '400', - 'src' => 'https://example.com/font.ttf', + ), + ), + ), + ), + true + ); + + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'border' => array( + 'radius' => array( + 'topLeft' => '6px', + 'bottomRight' => '6px', + 'bottomLeft' => '6px', + ), + ), + 'spacing' => array( + 'padding' => array( + 'top' => '1px', + 'right' => '1px', + 'left' => '1px', + ), + ), + 'elements' => array( + 'link' => array( + 'spacing' => array( + 'padding' => array( + 'top' => '2px', + 'right' => '2px', + 'left' => '2px', + ), + ), + ), + ), + 'blocks' => array( + 'core/group' => array( + 'border' => array( + 'radius' => array( + 'topLeft' => '5px', + 'bottomRight' => '5px', + 'bottomLeft' => '5px', + ), + ), + 'spacing' => array( + 'padding' => array( + 'top' => '3px', + 'right' => '3px', + 'left' => '3px', + ), + ), + 'elements' => array( + 'link' => array( + 'spacing' => array( + 'padding' => array( + 'top' => '4px', + 'right' => '4px', + 'left' => '4px', ), - array( - 'fontFamily' => 'Inter', - 'fontStyle' => 'italic', - 'fontWeight' => '400', - 'src' => 'https://example.com/font.ttf', + ), + ), + ), + ), + ), + ), + ); + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + + public function test_remove_insecure_properties_removes_non_preset_settings() { + $actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'custom' => true, + 'palette' => array( + 'custom' => array( + array( + 'name' => 'Red', + 'slug' => 'red', + 'color' => '#ff0000', + ), + array( + 'name' => 'Green', + 'slug' => 'green', + 'color' => '#00ff00', + ), + array( + 'name' => 'Blue', + 'slug' => 'blue', + 'color' => '#0000ff', + ), + ), + ), + ), + 'spacing' => array( + 'padding' => false, + ), + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'custom' => true, + 'palette' => array( + 'custom' => array( + array( + 'name' => 'Yellow', + 'slug' => 'yellow', + 'color' => '#ff0000', + ), + array( + 'name' => 'Pink', + 'slug' => 'pink', + 'color' => '#00ff00', + ), + array( + 'name' => 'Orange', + 'slug' => 'orange', + 'color' => '#0000ff', + ), ), ), ), + 'spacing' => array( + 'padding' => false, + ), + ), + ), + ), + ) + ); + + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'palette' => array( + 'custom' => array( + array( + 'name' => 'Red', + 'slug' => 'red', + 'color' => '#ff0000', + ), + array( + 'name' => 'Green', + 'slug' => 'green', + 'color' => '#00ff00', + ), + array( + 'name' => 'Blue', + 'slug' => 'blue', + 'color' => '#0000ff', + ), ), ), ), + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'palette' => array( + 'custom' => array( + array( + 'name' => 'Yellow', + 'slug' => 'yellow', + 'color' => '#ff0000', + ), + array( + 'name' => 'Pink', + 'slug' => 'pink', + 'color' => '#00ff00', + ), + array( + 'name' => 'Orange', + 'slug' => 'orange', + 'color' => '#0000ff', + ), + ), + ), + ), + ), + ), + ), + ); + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + + public function test_remove_insecure_properties_removes_unsafe_preset_settings() { + $actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'palette' => array( + 'custom' => array( + array( + 'name' => 'Red/>ok', + 'slug' => 'red', + 'color' => '#ff0000', + ), + array( + 'name' => 'Green', + 'slug' => 'a" attr', + 'color' => '#00ff00', + ), + array( + 'name' => 'Blue', + 'slug' => 'blue', + 'color' => 'var(--invalid', + ), + array( + 'name' => 'Pink', + 'slug' => 'pink', + 'color' => '#FFC0CB', + ), + ), + ), + ), + 'typography' => array( + 'fontFamilies' => array( + 'custom' => array( + array( + 'name' => 'Helvetica Arial/>test', + 'slug' => 'helvetica-arial', + 'fontFamily' => 'Helvetica Neue, Helvetica, Arial, sans-serif', + ), + array( + 'name' => 'Geneva', + 'slug' => 'geneva#asa', + 'fontFamily' => 'Geneva, Tahoma, Verdana, sans-serif', + ), + array( + 'name' => 'Cambria', + 'slug' => 'cambria', + 'fontFamily' => 'Cambria, Georgia, serif', + ), + array( + 'name' => 'Helvetica Arial', + 'slug' => 'helvetica-arial', + 'fontFamily' => 'var(--invalid', + ), + ), + ), + ), + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'palette' => array( + 'custom' => array( + array( + 'name' => 'Red/>ok', + 'slug' => 'red', + 'color' => '#ff0000', + ), + array( + 'name' => 'Green', + 'slug' => 'a" attr', + 'color' => '#00ff00', + ), + array( + 'name' => 'Blue', + 'slug' => 'blue', + 'color' => 'var(--invalid', + ), + array( + 'name' => 'Pink', + 'slug' => 'pink', + 'color' => '#FFC0CB', + ), + ), + ), + ), + ), + ), + ), + ) + ); + + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'palette' => array( + 'custom' => array( + array( + 'name' => 'Pink', + 'slug' => 'pink', + 'color' => '#FFC0CB', + ), + ), + ), + ), + 'typography' => array( + 'fontFamilies' => array( + 'custom' => array( + array( + 'name' => 'Cambria', + 'slug' => 'cambria', + 'fontFamily' => 'Cambria, Georgia, serif', + ), + ), + ), + ), + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'palette' => array( + 'custom' => array( + array( + 'name' => 'Pink', + 'slug' => 'pink', + 'color' => '#FFC0CB', + ), + ), + ), + ), + ), + ), + ), + ); + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + + public function test_remove_insecure_properties_applies_safe_styles() { + $actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'color' => array( + 'text' => '#abcabc ', // Trailing space. + ), + ), + ), + true + ); + + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'color' => array( + 'text' => '#abcabc ', + ), + ), + ); + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + + /** + * @covers WP_Theme_JSON_Gutenberg::remove_insecure_properties + */ + public function test_remove_insecure_properties_should_allow_indirect_properties() { + $actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'spacing' => array( + 'blockGap' => '3em', + ), + 'blocks' => array( + 'core/social-links' => array( + 'spacing' => array( + 'blockGap' => array( + 'left' => '2em', + 'top' => '1em', + ), + ), + ), + ), + ), + 'settings' => array( + 'layout' => array( + 'contentSize' => '800px', + 'wideSize' => '1000px', + ), + ), + ) + ); + + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'spacing' => array( + 'blockGap' => '3em', + ), + 'blocks' => array( + 'core/social-links' => array( + 'spacing' => array( + 'blockGap' => array( + 'left' => '2em', + 'top' => '1em', + ), + ), + ), + ), + ), + 'settings' => array( + 'layout' => array( + 'contentSize' => '800px', + 'wideSize' => '1000px', + ), + ), + ); + + $this->assertSameSetsWithIndex( $expected, $actual ); + } + + + public function test_remove_invalid_element_pseudo_selectors() { + $actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'hotpink', + 'background' => 'yellow', + ), + ':hover' => array( + 'color' => array( + 'text' => 'red', + 'background' => 'blue', + ), + ), + ':seen' => array( + 'color' => array( + 'background' => 'ivory', + ), + ), + ), + ), + ), + ), + true + ); + + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'hotpink', + 'background' => 'yellow', + ), + ':hover' => array( + 'color' => array( + 'text' => 'red', + 'background' => 'blue', + ), + ), + ), + ), + ), + ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + + public function test_get_custom_templates() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 1, + 'customTemplates' => array( + array( + 'name' => 'page-home', + 'title' => 'Homepage template', + ), + ), + ) + ); + + $page_templates = $theme_json->get_custom_templates(); + + $this->assertEqualSetsWithIndex( + $page_templates, + array( + 'page-home' => array( + 'title' => 'Homepage template', + 'postTypes' => array( 'page' ), + ), + ) + ); + } + + public function test_get_template_parts() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 1, + 'templateParts' => array( + array( + 'name' => 'small-header', + 'title' => 'Small Header', + 'area' => 'header', + ), + ), + ) + ); + + $template_parts = $theme_json->get_template_parts(); + + $this->assertEqualSetsWithIndex( + $template_parts, + array( + 'small-header' => array( + 'title' => 'Small Header', + 'area' => 'header', + ), + ) + ); + } + + public function test_get_from_editor_settings() { + $input = array( + 'disableCustomColors' => true, + 'disableCustomGradients' => true, + 'disableCustomFontSizes' => true, + 'enableCustomLineHeight' => true, + 'enableCustomUnits' => true, + 'colors' => array( + array( + 'slug' => 'color-slug', + 'name' => 'Color Name', + 'color' => 'colorvalue', + ), + ), + 'gradients' => array( + array( + 'slug' => 'gradient-slug', + 'name' => 'Gradient Name', + 'gradient' => 'gradientvalue', + ), + ), + 'fontSizes' => array( + array( + 'slug' => 'size-slug', + 'name' => 'Size Name', + 'size' => 'sizevalue', + ), + ), + ); + + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'custom' => false, + 'customGradient' => false, + 'gradients' => array( + array( + 'slug' => 'gradient-slug', + 'name' => 'Gradient Name', + 'gradient' => 'gradientvalue', + ), + ), + 'palette' => array( + array( + 'slug' => 'color-slug', + 'name' => 'Color Name', + 'color' => 'colorvalue', + ), + ), + ), + 'spacing' => array( + 'units' => array( 'px', 'em', 'rem', 'vh', 'vw', '%' ), + ), + 'typography' => array( + 'customFontSize' => false, + 'lineHeight' => true, + 'fontSizes' => array( + array( + 'slug' => 'size-slug', + 'name' => 'Size Name', + 'size' => 'sizevalue', + ), + ), + ), + ), + ); + + $actual = WP_Theme_JSON_Gutenberg::get_from_editor_settings( $input ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + + public function test_get_editor_settings_no_theme_support() { + $input = array( + '__unstableEnableFullSiteEditingBlocks' => false, + 'disableCustomColors' => false, + 'disableCustomFontSizes' => false, + 'disableCustomGradients' => false, + 'enableCustomLineHeight' => false, + 'enableCustomUnits' => false, + 'imageSizes' => array( + array( + 'slug' => 'thumbnail', + 'name' => 'Thumbnail', + ), + array( + 'slug' => 'medium', + 'name' => 'Medium', + ), + array( + 'slug' => 'large', + 'name' => 'Large', + ), + array( + 'slug' => 'full', + 'name' => 'Full Size', + ), + ), + 'isRTL' => false, + 'maxUploadFileSize' => 123, + ); + + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'custom' => true, + 'customGradient' => true, + ), + 'spacing' => array( + 'units' => false, + ), + 'typography' => array( + 'customFontSize' => true, + 'lineHeight' => false, + ), + ), + ); + + $actual = WP_Theme_JSON_Gutenberg::get_from_editor_settings( $input ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + + public function test_get_editor_settings_blank() { + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array(), + ); + $actual = WP_Theme_JSON_Gutenberg::get_from_editor_settings( array() ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + + public function test_get_editor_settings_custom_units_can_be_disabled() { + add_theme_support( 'custom-units', array() ); + $actual = WP_Theme_JSON_Gutenberg::get_from_editor_settings( get_classic_theme_supports_block_editor_settings() ); + remove_theme_support( 'custom-units' ); + + $expected = array( + 'units' => array( array() ), + 'padding' => false, + ); + + $this->assertEqualSetsWithIndex( $expected, $actual['settings']['spacing'] ); + } + + public function test_get_editor_settings_custom_units_can_be_enabled() { + add_theme_support( 'custom-units' ); + $actual = WP_Theme_JSON_Gutenberg::get_from_editor_settings( get_classic_theme_supports_block_editor_settings() ); + remove_theme_support( 'custom-units' ); + + $expected = array( + 'units' => array( 'px', 'em', 'rem', 'vh', 'vw', '%' ), + 'padding' => false, + ); + + $this->assertEqualSetsWithIndex( $expected, $actual['settings']['spacing'] ); + } + + public function test_get_editor_settings_custom_units_can_be_filtered() { + add_theme_support( 'custom-units', 'rem', 'em' ); + $actual = WP_Theme_JSON_Gutenberg::get_from_editor_settings( get_classic_theme_supports_block_editor_settings() ); + remove_theme_support( 'custom-units' ); + + $expected = array( + 'units' => array( 'rem', 'em' ), + 'padding' => false, + ); + $this->assertEqualSetsWithIndex( $expected, $actual['settings']['spacing'] ); + } + + public function test_export_data() { + $theme = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + 'settings' => array( + 'color' => array( + 'palette' => array( + array( + 'slug' => 'white', + 'color' => 'white', + 'label' => 'White', + ), + array( + 'slug' => 'black', + 'color' => 'black', + 'label' => 'Black', + ), + ), + ), + ), + ) + ); + $user = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + 'settings' => array( + 'color' => array( + 'palette' => array( + array( + 'slug' => 'white', + 'color' => '#fff', + 'label' => 'User White', + ), + array( + 'slug' => 'hotpink', + 'color' => 'hotpink', + 'label' => 'hotpink', + ), + ), + ), + ), + ), + 'custom' + ); + + $theme->merge( $user ); + $actual = $theme->get_data(); + $expected = array( + 'version' => 2, + 'settings' => array( + 'color' => array( + 'palette' => array( + array( + 'slug' => 'white', + 'color' => '#fff', + 'label' => 'User White', + ), + array( + 'slug' => 'black', + 'color' => 'black', + 'label' => 'Black', + ), + array( + 'slug' => 'hotpink', + 'color' => 'hotpink', + 'label' => 'hotpink', + ), + ), + ), + ), + ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + + public function test_export_data_deals_with_empty_user_data() { + $theme = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + 'settings' => array( + 'color' => array( + 'palette' => array( + array( + 'slug' => 'white', + 'color' => 'white', + 'label' => 'White', + ), + array( + 'slug' => 'black', + 'color' => 'black', + 'label' => 'Black', + ), + ), + ), + ), + ) + ); + + $actual = $theme->get_data(); + $expected = array( + 'version' => 2, + 'settings' => array( + 'color' => array( + 'palette' => array( + array( + 'slug' => 'white', + 'color' => 'white', + 'label' => 'White', + ), + array( + 'slug' => 'black', + 'color' => 'black', + 'label' => 'Black', + ), + ), + ), + ), + ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + + public function test_export_data_deals_with_empty_theme_data() { + $user = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + 'settings' => array( + 'color' => array( + 'palette' => array( + array( + 'slug' => 'white', + 'color' => '#fff', + 'label' => 'User White', + ), + array( + 'slug' => 'hotpink', + 'color' => 'hotpink', + 'label' => 'hotpink', + ), + ), + ), + ), + ), + 'custom' + ); + + $actual = $user->get_data(); + $expected = array( + 'version' => 2, + 'settings' => array( + 'color' => array( + 'palette' => array( + array( + 'slug' => 'white', + 'color' => '#fff', + 'label' => 'User White', + ), + array( + 'slug' => 'hotpink', + 'color' => 'hotpink', + 'label' => 'hotpink', + ), + ), + ), + ), + ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + + public function test_export_data_deals_with_empty_data() { + $theme_v2 = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + ), + 'theme' + ); + $actual_v2 = $theme_v2->get_data(); + $expected_v2 = array( 'version' => 2 ); + $this->assertEqualSetsWithIndex( $expected_v2, $actual_v2 ); + + $theme_v1 = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 1, + ), + 'theme' + ); + $actual_v1 = $theme_v1->get_data(); + $expected_v1 = array( 'version' => 2 ); + $this->assertEqualSetsWithIndex( $expected_v1, $actual_v1 ); + } + + public function test_export_data_sets_appearance_tools() { + $theme = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + 'settings' => array( + 'appearanceTools' => true, + 'blocks' => array( + 'core/paragraph' => array( + 'appearanceTools' => true, + ), + ), + ), + ) + ); + + $actual = $theme->get_data(); + $expected = array( + 'version' => 2, + 'settings' => array( + 'appearanceTools' => true, + 'blocks' => array( + 'core/paragraph' => array( + 'appearanceTools' => true, + ), + ), + ), + ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + + public function test_export_data_sets_use_root_padding_aware_alignments() { + $theme = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + 'settings' => array( + 'useRootPaddingAwareAlignments' => true, + 'blocks' => array( + 'core/paragraph' => array( + 'useRootPaddingAwareAlignments' => true, + ), + ), + ), + ) + ); + + $actual = $theme->get_data(); + $expected = array( + 'version' => 2, + 'settings' => array( + 'useRootPaddingAwareAlignments' => true, + 'blocks' => array( + 'core/paragraph' => array( + 'useRootPaddingAwareAlignments' => true, + ), + ), + ), + ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + + public function test_remove_invalid_font_family_settings() { + $actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'typography' => array( + 'fontFamilies' => array( + 'custom' => array( + array( + 'name' => 'Open Sans', + 'slug' => 'open-sans', + 'fontFamily' => '"Open Sans", sans-serif', + ), + array( + 'name' => 'Arial', + 'slug' => 'arial', + 'fontFamily' => 'Arial, serif', + ), + ), + ), + ), + ), + ), + true + ); + + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'typography' => array( + 'fontFamilies' => array( + 'custom' => array( + array( + 'name' => 'Arial', + 'slug' => 'arial', + 'fontFamily' => 'Arial, serif', + ), + ), + ), + ), + ), + ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + + public function test_get_element_class_name_button() { + $expected = 'wp-element-button'; + $actual = WP_Theme_JSON_Gutenberg::get_element_class_name( 'button' ); + + $this->assertSame( $expected, $actual ); + } + + public function test_get_element_class_name_invalid() { + $expected = ''; + $actual = WP_Theme_JSON_Gutenberg::get_element_class_name( 'unknown-element' ); + + $this->assertSame( $expected, $actual ); + } + + /** + * Testing that dynamic properties in theme.json return the value they reference, + * e.g. array( 'ref' => 'styles.color.background' ) => "#ffffff". + */ + public function test_get_property_value_valid() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + 'styles' => array( + 'color' => array( + 'background' => '#ffffff', + 'text' => '#000000', + ), + 'elements' => array( + 'button' => array( + 'color' => array( + 'background' => array( 'ref' => 'styles.color.text' ), + 'text' => array( 'ref' => 'styles.color.background' ), + ), + ), + ), + ), + ) + ); + + $base_styles = 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; + $color_styles = 'body{background-color: #ffffff;color: #000000;}.wp-element-button, .wp-block-button__link{background-color: #000000;color: #ffffff;}'; + $expected = $base_styles . $color_styles; + $this->assertSame( $expected, $theme_json->get_stylesheet() ); + } + + /** + * Tests that get_property_value() static method returns an empty string + * if the path is invalid or the value is null. + * + * Also, tests that PHP 8.1 "passing null to non-nullable" deprecation notice + * is not thrown when passing the value to strncmp() in the method. + * + * The notice that we should not see: + * `Deprecated: strncmp(): Passing null to parameter #1 ($string1) of type string is deprecated`. + * + * @dataProvider data_get_property_value_should_return_string_for_invalid_paths_or_null_values + * + * @covers WP_Theme_JSON_Gutenberg::get_property_value + * + * @param array $styles An array with style definitions. + * @param array $path Path to the desired properties. + */ + public function test_get_property_value_should_return_string_for_invalid_paths_or_null_values( $styles, $path ) { + $reflection_class = new ReflectionClass( WP_Theme_JSON_Gutenberg::class ); + + $get_property_value_method = $reflection_class->getMethod( 'get_property_value' ); + $get_property_value_method->setAccessible( true ); + $result = $get_property_value_method->invoke( null, $styles, $path ); + + $this->assertSame( '', $result ); + } + + /** + * Data provider for test_get_property_value_should_return_string_for_invalid_paths_or_null_values(). + * + * @return array + */ + public function data_get_property_value_should_return_string_for_invalid_paths_or_null_values() { + return array( + 'empty string' => array( + 'styles' => array(), + 'path' => array( 'non_existent_path' ), + ), + 'null' => array( + 'styles' => array( 'some_null_value' => null ), + 'path' => array( 'some_null_value' ), + ), + ); + } + + /** + * Testing that dynamic properties in theme.json that + * refer to other dynamic properties in a loop + * should be left untouched. + * + * @expectedIncorrectUsage get_property_value + */ + public function test_get_property_value_loop() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + 'styles' => array( + 'color' => array( + 'background' => '#ffffff', + 'text' => array( 'ref' => 'styles.elements.button.color.background' ), + ), + 'elements' => array( + 'button' => array( + 'color' => array( + 'background' => array( 'ref' => 'styles.color.text' ), + 'text' => array( 'ref' => 'styles.color.background' ), + ), + ), + ), + ), + ) + ); + + $base_styles = 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; + $color_styles = 'body{background-color: #ffffff;}.wp-element-button, .wp-block-button__link{color: #ffffff;}'; + $expected = $base_styles . $color_styles; + $this->assertSame( $expected, $theme_json->get_stylesheet() ); + } + + /** + * Testing that dynamic properties in theme.json that + * refer to other dynamic properties + * should be left unprocessed. + * + * @expectedIncorrectUsage get_property_value + */ + public function test_get_property_value_recursion() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + 'styles' => array( + 'color' => array( + 'background' => '#ffffff', + 'text' => array( 'ref' => 'styles.color.background' ), + ), + 'elements' => array( + 'button' => array( + 'color' => array( + 'background' => array( 'ref' => 'styles.color.text' ), + 'text' => array( 'ref' => 'styles.color.background' ), + ), + ), + ), + ), + ) + ); + + $base_styles = 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; + $color_styles = 'body{background-color: #ffffff;color: #ffffff;}.wp-element-button, .wp-block-button__link{color: #ffffff;}'; + $expected = $base_styles . $color_styles; + $this->assertSame( $expected, $theme_json->get_stylesheet() ); + } + + /** + * Testing that dynamic properties in theme.json that + * refer to themselves should be left unprocessed. + * + * @expectedIncorrectUsage get_property_value + */ + public function test_get_property_value_self() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + 'styles' => array( + 'color' => array( + 'background' => '#ffffff', + 'text' => array( 'ref' => 'styles.color.text' ), + ), + ), + ) + ); + + $base_styles = 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; + $color_styles = 'body{background-color: #ffffff;}'; + $expected = $base_styles . $color_styles; + $this->assertSame( $expected, $theme_json->get_stylesheet() ); + } + + public function test_get_styles_for_block_with_padding_aware_alignments() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + 'styles' => array( + 'spacing' => array( + 'padding' => array( + 'top' => '10px', + 'right' => '12px', + 'bottom' => '10px', + 'left' => '12px', + ), + ), + ), + 'settings' => array( + 'useRootPaddingAwareAlignments' => true, + ), + ) + ); + + $metadata = array( + 'path' => array( 'styles' ), + 'selector' => 'body', + ); + + $expected = 'body { margin: 0; }.wp-site-blocks { padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom); }.has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }.has-global-padding :where(.has-global-padding:not(.wp-block-block)) { padding-right: 0; padding-left: 0; }.has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); }.has-global-padding :where(.has-global-padding:not(.wp-block-block)) > .alignfull { margin-right: 0; margin-left: 0; }.has-global-padding > .alignfull:where(:not(.has-global-padding):not(.is-layout-flex):not(.is-layout-grid)) > :where([class*="wp-block-"]:not(.alignfull):not([class*="__"]),.wp-block:not(.alignfull),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }.has-global-padding :where(.has-global-padding) > .alignfull:where(:not(.has-global-padding)) > :where([class*="wp-block-"]:not(.alignfull):not([class*="__"]),.wp-block:not(.alignfull),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: 0; padding-left: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}body{--wp--style--root--padding-top: 10px;--wp--style--root--padding-right: 12px;--wp--style--root--padding-bottom: 10px;--wp--style--root--padding-left: 12px;}'; + $root_rules = $theme_json->get_root_layout_rules( WP_Theme_JSON_Gutenberg::ROOT_BLOCK_SELECTOR, $metadata ); + $style_rules = $theme_json->get_styles_for_block( $metadata ); + $this->assertSame( $expected, $root_rules . $style_rules ); + } + + public function test_get_styles_for_block_without_padding_aware_alignments() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + 'styles' => array( + 'spacing' => array( + 'padding' => array( + 'top' => '10px', + 'right' => '12px', + 'bottom' => '10px', + 'left' => '12px', + ), + ), + ), + ) + ); + + $metadata = array( + 'path' => array( 'styles' ), + 'selector' => 'body', + ); + + $expected = 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}body{padding-top: 10px;padding-right: 12px;padding-bottom: 10px;padding-left: 12px;}'; + $root_rules = $theme_json->get_root_layout_rules( WP_Theme_JSON_Gutenberg::ROOT_BLOCK_SELECTOR, $metadata ); + $style_rules = $theme_json->get_styles_for_block( $metadata ); + $this->assertSame( $expected, $root_rules . $style_rules ); + } + + public function test_get_styles_for_block_with_content_width() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + 'settings' => array( + 'layout' => array( + 'contentSize' => '800px', + 'wideSize' => '1000px', + ), + ), + ) + ); + + $metadata = array( + 'path' => array( 'settings' ), + 'selector' => 'body', + ); + + $expected = 'body { margin: 0;--wp--style--global--content-size: 800px;--wp--style--global--wide-size: 1000px; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; + $root_rules = $theme_json->get_root_layout_rules( WP_Theme_JSON_Gutenberg::ROOT_BLOCK_SELECTOR, $metadata ); + $style_rules = $theme_json->get_styles_for_block( $metadata ); + $this->assertSame( $expected, $root_rules . $style_rules ); + } + + public function test_get_styles_with_appearance_tools() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + 'settings' => array( + 'appearanceTools' => true, + ), + ) + ); + + $metadata = array( + 'path' => array( 'settings' ), + 'selector' => 'body', + ); + + $expected = 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: ; margin-block-end: 0; }:where(.wp-site-blocks) > :first-child:first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child:last-child { margin-block-end: 0; }body { --wp--style--block-gap: ; }:where(body .is-layout-flow) > :first-child:first-child{margin-block-start: 0;}:where(body .is-layout-flow) > :last-child:last-child{margin-block-end: 0;}:where(body .is-layout-flow) > *{margin-block-start: 1;margin-block-end: 0;}:where(body .is-layout-constrained) > :first-child:first-child{margin-block-start: 0;}:where(body .is-layout-constrained) > :last-child:last-child{margin-block-end: 0;}:where(body .is-layout-constrained) > *{margin-block-start: 1;margin-block-end: 0;}:where(body .is-layout-flex) {gap: 1;}:where(body .is-layout-grid) {gap: 1;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; + $root_rules = $theme_json->get_root_layout_rules( WP_Theme_JSON_Gutenberg::ROOT_BLOCK_SELECTOR, $metadata ); + $this->assertSame( $expected, $root_rules ); + } + + public function test_sanitization() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + 'styles' => array( + 'spacing' => array( + 'blockGap' => 'valid value', + ), + 'blocks' => array( + 'core/group' => array( + 'spacing' => array( + 'margin' => 'valid value', + 'display' => 'none', + ), + ), + ), + ), + ) + ); + + $actual = $theme_json->get_raw_data(); + $expected = array( + 'version' => 2, + 'styles' => array( + 'spacing' => array( + 'blockGap' => 'valid value', + ), + 'blocks' => array( + 'core/group' => array( + 'spacing' => array( + 'margin' => 'valid value', + ), + ), + ), + ), + ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + + public function test_sanitize_for_unregistered_style_variations() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + 'styles' => array( + 'blocks' => array( + 'core/quote' => array( + 'variations' => array( + 'unregisteredVariation' => array( + 'color' => array( + 'background' => 'hotpink', + ), + ), + 'plain' => array( + 'color' => array( + 'background' => 'hotpink', + ), + ), + ), + ), + ), + ), + ) + ); + + $sanitized_theme_json = $theme_json->get_raw_data(); + $expected = array( + 'version' => 2, + 'styles' => array( + 'blocks' => array( + 'core/quote' => array( + 'variations' => array( + 'plain' => array( + 'color' => array( + 'background' => 'hotpink', + ), + ), + ), + ), + ), + ), + ); + $this->assertSameSetsWithIndex( $expected, $sanitized_theme_json, 'Sanitized theme.json styles does not match' ); + } + + /** + * @dataProvider data_sanitize_for_block_with_style_variations + * + * @param array $theme_json_variations Theme.json variations to test. + * @param array $expected_sanitized Expected results after sanitizing. + */ + public function test_sanitize_for_block_with_style_variations( $theme_json_variations, $expected_sanitized ) { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + 'styles' => array( + 'blocks' => array( + 'core/quote' => $theme_json_variations, + ), + ), + ) + ); + + // Validate structure is sanitized. + $sanitized_theme_json = $theme_json->get_raw_data(); + $this->assertIsArray( $sanitized_theme_json, 'Sanitized theme.json is not an array data type' ); + $this->assertArrayHasKey( 'styles', $sanitized_theme_json, 'Sanitized theme.json does not have an "styles" key' ); + $this->assertSameSetsWithIndex( $expected_sanitized, $sanitized_theme_json['styles'], 'Sanitized theme.json styles does not match' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_sanitize_for_block_with_style_variations() { + return array( + '1 variation with 1 valid property' => array( + 'theme_json_variations' => array( + 'variations' => array( + 'plain' => array( + 'color' => array( + 'background' => 'hotpink', + ), + ), + ), + ), + 'expected_sanitized' => array( + 'blocks' => array( + 'core/quote' => array( + 'variations' => array( + 'plain' => array( + 'color' => array( + 'background' => 'hotpink', + ), + ), + ), + ), + ), + ), + ), + '1 variation with 2 invalid properties' => array( + 'theme_json_variations' => array( + 'variations' => array( + 'plain' => array( + 'color' => array( + 'background' => 'hotpink', + ), + 'invalidProperty1' => 'value1', + 'invalidProperty2' => 'value2', + ), + ), + ), + 'expected_sanitized' => array( + 'blocks' => array( + 'core/quote' => array( + 'variations' => array( + 'plain' => array( + 'color' => array( + 'background' => 'hotpink', + ), + ), + ), + ), + ), + ), + ), + ); + } + + /** + * Tests that invalid properties are removed from the theme.json inside indexed arrays as settings.typography.fontFamilies. + */ + public function test_sanitize_indexed_arrays() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => '2', + 'badKey2' => 'I am Evil!', + 'settings' => array( + 'badKey3' => 'I am Evil!', + 'typography' => array( + 'badKey4' => 'I am Evil!', + 'fontFamilies' => array( + 'custom' => array( + array( + 'badKey4' => 'I am Evil!', + 'name' => 'Arial', + 'slug' => 'arial', + 'fontFamily' => 'Arial, sans-serif', + ), + ), + 'theme' => array( + array( + 'badKey5' => 'I am Evil!', + 'name' => 'Piazzolla', + 'slug' => 'piazzolla', + 'fontFamily' => 'Piazzolla', + 'fontFace' => array( + array( + 'badKey6' => 'I am Evil!', + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'src' => 'https://example.com/font.ttf', + ), + array( + 'badKey7' => 'I am Evil!', + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'src' => 'https://example.com/font.ttf', + ), + ), + ), + array( + 'badKey8' => 'I am Evil!', + 'name' => 'Inter', + 'slug' => 'Inter', + 'fontFamily' => 'Inter', + 'fontFace' => array( + array( + 'badKey9' => 'I am Evil!', + 'fontFamily' => 'Inter', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'src' => 'https://example.com/font.ttf', + ), + array( + 'badKey10' => 'I am Evil!', + 'fontFamily' => 'Inter', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'src' => 'https://example.com/font.ttf', + ), + ), + ), + ), + ), + ), + ), + ) + ); + + $expected_sanitized = array( + 'version' => '2', + 'settings' => array( + 'typography' => array( + 'fontFamilies' => array( + 'custom' => array( + array( + 'name' => 'Arial', + 'slug' => 'arial', + 'fontFamily' => 'Arial, sans-serif', + ), + ), + 'theme' => array( + array( + 'name' => 'Piazzolla', + 'slug' => 'piazzolla', + 'fontFamily' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'src' => 'https://example.com/font.ttf', + ), + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'src' => 'https://example.com/font.ttf', + ), + ), + ), + array( + 'name' => 'Inter', + 'slug' => 'Inter', + 'fontFamily' => 'Inter', + 'fontFace' => array( + array( + 'fontFamily' => 'Inter', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'src' => 'https://example.com/font.ttf', + ), + array( + 'fontFamily' => 'Inter', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'src' => 'https://example.com/font.ttf', + ), + ), + ), + ), + ), + ), + ), + ); + $sanitized_theme_json = $theme_json->get_raw_data(); + $this->assertSameSetsWithIndex( $expected_sanitized, $sanitized_theme_json, 'Sanitized theme.json does not match' ); + } + + /** + * @dataProvider data_sanitize_with_invalid_style_variation + * + * @param array $theme_json_variations The theme.json variations to test. + */ + public function test_sanitize_with_invalid_style_variation( $theme_json_variations ) { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + 'styles' => array( + 'blocks' => array( + 'core/quote' => $theme_json_variations, + ), + ), + ) + ); + + // Validate structure is sanitized. + $sanitized_theme_json = $theme_json->get_raw_data(); + $this->assertIsArray( $sanitized_theme_json, 'Sanitized theme.json is not an array data type' ); + $this->assertArrayNotHasKey( 'styles', $sanitized_theme_json, 'Sanitized theme.json should not have a "styles" key' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_sanitize_with_invalid_style_variation() { + return array( + 'empty string variation' => array( + array( + 'variations' => '', + ), + ), + 'boolean variation' => array( + array( + 'variations' => false, + ), + ), + ); + } + + /** + * @dataProvider data_get_styles_for_block_with_style_variations + * + * @param array $theme_json_variations Theme.json variations to test. + * @param string $metadata_variations Style variations to test. + * @param string $expected Expected results for styling. + */ + public function test_get_styles_for_block_with_style_variations( $theme_json_variations, $metadata_variations, $expected ) { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + 'styles' => array( + 'blocks' => array( + 'core/quote' => $theme_json_variations, + ), + ), + ) + ); + + // Validate styles are generated properly. + $metadata = array( + 'path' => array( 'styles', 'blocks', 'core/quote' ), + 'selector' => '.wp-block-quote', + 'variations' => $metadata_variations, + ); + $actual_styles = $theme_json->get_styles_for_block( $metadata ); + $this->assertSame( $expected, $actual_styles ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_get_styles_for_block_with_style_variations() { + $plain = array( + 'metadata' => array( + 'path' => array( 'styles', 'blocks', 'core/quote', 'variations', 'plain' ), + 'selector' => '.is-style-plain.is-style-plain.wp-block-quote', + ), + 'styles' => '.is-style-plain.is-style-plain.wp-block-quote{background-color: hotpink;}', + ); + + return array( + '1 variation with 1 invalid property' => array( + 'theme_json_variations' => array( + 'variations' => array( + 'plain' => array( + 'color' => array( + 'background' => 'hotpink', + ), + ), + ), + ), + 'metadata_variation' => array( $plain['metadata'] ), + 'expected' => $plain['styles'], + ), + '1 variation with 2 invalid properties' => array( + 'theme_json_variations' => array( + 'variations' => array( + 'plain' => array( + 'color' => array( + 'background' => 'hotpink', + ), + 'invalidProperty1' => 'value1', + 'invalidProperty2' => 'value2', + ), + ), + ), + 'metadata_variation' => array( $plain['metadata'] ), + 'expected' => $plain['styles'], + ), + ); + } + + public function test_block_style_variations() { + wp_set_current_user( static::$administrator_id ); + + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/button' => array( + 'color' => array( + 'background' => 'blue', + ), + 'variations' => array( + 'outline' => array( + 'color' => array( + 'background' => 'purple', + ), + ), + ), + ), + ), + ), + ); + + $actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties( $expected ); + + $this->assertSameSetsWithIndex( $expected, $actual ); + } + + public function test_block_style_variations_with_invalid_properties() { + wp_set_current_user( static::$administrator_id ); + + $partially_invalid_variation = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/button' => array( + 'color' => array( + 'background' => 'blue', + ), + 'variations' => array( + 'outline' => array( + 'color' => array( + 'background' => 'purple', + ), + 'invalid' => array( + 'value' => 'should be stripped', + ), + ), + ), + ), + ), + ), + ); + + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/button' => array( + 'color' => array( + 'background' => 'blue', + ), + 'variations' => array( + 'outline' => array( + 'color' => array( + 'background' => 'purple', + ), + ), + ), + ), + ), + ), + ); + + $actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties( $partially_invalid_variation ); + + $this->assertSameSetsWithIndex( $expected, $actual ); + } + + /** + * Tests generating the spacing presets array based on the spacing scale provided. + * + * @dataProvider data_set_spacing_sizes + */ + public function test_set_spacing_sizes( $spacing_scale, $expected_output ) { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + 'settings' => array( + 'spacing' => array( + 'spacingScale' => $spacing_scale, + ), + ), + ) + ); + + $theme_json->set_spacing_sizes(); + $this->assertSame( $expected_output, _wp_array_get( $theme_json->get_raw_data(), array( 'settings', 'spacing', 'spacingSizes', 'default' ) ) ); + } + + /** + * Data provider for spacing scale tests. + * + * @return array + */ + public function data_set_spacing_sizes() { + return array( + 'only one value when single step in spacing scale' => array( + 'spacing_scale' => array( + 'operator' => '+', + 'increment' => 1.5, + 'steps' => 1, + 'mediumStep' => 4, + 'unit' => 'rem', + ), + 'expected_output' => array( + array( + 'name' => '1', + 'slug' => '50', + 'size' => '4rem', + ), + ), + ), + 'one step above medium when two steps in spacing scale' => array( + 'spacing_scale' => array( + 'operator' => '+', + 'increment' => 1.5, + 'steps' => 2, + 'mediumStep' => 4, + 'unit' => 'rem', + ), + 'expected_output' => array( + array( + 'name' => '1', + 'slug' => '50', + 'size' => '4rem', + ), + array( + 'name' => '2', + 'slug' => '60', + 'size' => '5.5rem', + ), + ), + ), + 'one step above medium and one below when three steps in spacing scale' => array( + 'spacing_scale' => array( + 'operator' => '+', + 'increment' => 1.5, + 'steps' => 3, + 'mediumStep' => 4, + 'unit' => 'rem', + ), + 'expected_output' => array( + array( + 'name' => '1', + 'slug' => '40', + 'size' => '2.5rem', + ), + array( + 'name' => '2', + 'slug' => '50', + 'size' => '4rem', + ), + array( + 'name' => '3', + 'slug' => '60', + 'size' => '5.5rem', + ), + ), + ), + 'extra step added above medium when an even number of steps > 2 specified' => array( + 'spacing_scale' => array( + 'operator' => '+', + 'increment' => 1.5, + 'steps' => 4, + 'mediumStep' => 4, + 'unit' => 'rem', + ), + 'expected_output' => array( + array( + 'name' => '1', + 'slug' => '40', + 'size' => '2.5rem', + ), + array( + 'name' => '2', + 'slug' => '50', + 'size' => '4rem', + ), + array( + 'name' => '3', + 'slug' => '60', + 'size' => '5.5rem', + ), + array( + 'name' => '4', + 'slug' => '70', + 'size' => '7rem', + ), + ), + ), + 'extra steps above medium if bottom end will go below zero' => array( + 'spacing_scale' => array( + 'operator' => '+', + 'increment' => 2.5, + 'steps' => 5, + 'mediumStep' => 5, + 'unit' => 'rem', + ), + 'expected_output' => array( + array( + 'name' => '1', + 'slug' => '40', + 'size' => '2.5rem', + ), + array( + 'name' => '2', + 'slug' => '50', + 'size' => '5rem', + ), + array( + 'name' => '3', + 'slug' => '60', + 'size' => '7.5rem', + ), + array( + 'name' => '4', + 'slug' => '70', + 'size' => '10rem', + ), + array( + 'name' => '5', + 'slug' => '80', + 'size' => '12.5rem', + ), + ), + ), + 'multiplier correctly calculated above and below medium' => array( + 'spacing_scale' => array( + 'operator' => '*', + 'increment' => 1.5, + 'steps' => 5, + 'mediumStep' => 1.5, + 'unit' => 'rem', + ), + 'expected_output' => array( + array( + 'name' => '1', + 'slug' => '30', + 'size' => '0.67rem', + ), + array( + 'name' => '2', + 'slug' => '40', + 'size' => '1rem', + ), + array( + 'name' => '3', + 'slug' => '50', + 'size' => '1.5rem', + ), + array( + 'name' => '4', + 'slug' => '60', + 'size' => '2.25rem', + ), + array( + 'name' => '5', + 'slug' => '70', + 'size' => '3.38rem', + ), + ), + ), + 'increment < 1 combined showing * operator acting as divisor above and below medium' => array( + 'spacing_scale' => array( + 'operator' => '*', + 'increment' => 0.25, + 'steps' => 5, + 'mediumStep' => 1.5, + 'unit' => 'rem', + ), + 'expected_output' => array( + array( + 'name' => '1', + 'slug' => '30', + 'size' => '0.09rem', + ), + array( + 'name' => '2', + 'slug' => '40', + 'size' => '0.38rem', + ), + array( + 'name' => '3', + 'slug' => '50', + 'size' => '1.5rem', + ), + array( + 'name' => '4', + 'slug' => '60', + 'size' => '6rem', + ), + array( + 'name' => '5', + 'slug' => '70', + 'size' => '24rem', + ), + ), + ), + 't-shirt sizing used if more than 7 steps in scale' => array( + 'spacing_scale' => array( + 'operator' => '*', + 'increment' => 1.5, + 'steps' => 8, + 'mediumStep' => 1.5, + 'unit' => 'rem', + ), + 'expected_output' => array( + array( + 'name' => '2X-Small', + 'slug' => '20', + 'size' => '0.44rem', + ), + array( + 'name' => 'X-Small', + 'slug' => '30', + 'size' => '0.67rem', + ), + array( + 'name' => 'Small', + 'slug' => '40', + 'size' => '1rem', + ), + array( + 'name' => 'Medium', + 'slug' => '50', + 'size' => '1.5rem', + ), + array( + 'name' => 'Large', + 'slug' => '60', + 'size' => '2.25rem', + ), + array( + 'name' => 'X-Large', + 'slug' => '70', + 'size' => '3.38rem', + ), + array( + 'name' => '2X-Large', + 'slug' => '80', + 'size' => '5.06rem', + ), + array( + 'name' => '3X-Large', + 'slug' => '90', + 'size' => '7.59rem', + ), + ), ), ); - $sanitized_theme_json = $theme_json->get_raw_data(); - $this->assertSameSetsWithIndex( $expected_sanitized, $sanitized_theme_json, 'Sanitized theme.json does not match' ); } /** - * @dataProvider data_sanitize_with_invalid_style_variation + * Tests generating the spacing presets array based on the spacing scale provided. * - * @param array $theme_json_variations The theme.json variations to test. + * @dataProvider data_set_spacing_sizes_when_invalid + * + * @param array $spacing_scale Example spacing scale definitions from the data provider. + * @param array $expected_output Expected output from data provider. */ - public function test_sanitize_with_invalid_style_variation( $theme_json_variations ) { + public function test_set_spacing_sizes_when_invalid( $spacing_scale, $expected_output ) { + $this->expectException( Exception::class ); + $this->expectExceptionMessage( 'Some of the theme.json settings.spacing.spacingScale values are invalid' ); + $theme_json = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, - 'styles' => array( - 'blocks' => array( - 'core/quote' => $theme_json_variations, + 'version' => 2, + 'settings' => array( + 'spacing' => array( + 'spacingScale' => $spacing_scale, ), ), ) ); - // Validate structure is sanitized. - $sanitized_theme_json = $theme_json->get_raw_data(); - $this->assertIsArray( $sanitized_theme_json, 'Sanitized theme.json is not an array data type' ); - $this->assertArrayNotHasKey( 'styles', $sanitized_theme_json, 'Sanitized theme.json should not have a "styles" key' ); + // Ensure PHPUnit 10 compatibility. + set_error_handler( + static function ( $errno, $errstr ) { + restore_error_handler(); + throw new Exception( $errstr, $errno ); + }, + E_ALL + ); + + $theme_json->set_spacing_sizes(); + $this->assertSame( $expected_output, _wp_array_get( $theme_json->get_raw_data(), array( 'settings', 'spacing', 'spacingSizes', 'default' ) ) ); } /** - * Data provider. + * Data provider for spacing scale tests. * * @return array */ - public function data_sanitize_with_invalid_style_variation() { + public function data_set_spacing_sizes_when_invalid() { return array( - 'empty string variation' => array( - array( - 'variations' => '', + 'missing operator value' => array( + 'spacing_scale' => array( + 'operator' => '', + 'increment' => 1.5, + 'steps' => 1, + 'mediumStep' => 4, + 'unit' => 'rem', ), + 'expected_output' => null, ), - 'boolean variation' => array( - array( - 'variations' => false, + 'non numeric increment' => array( + 'spacing_scale' => array( + 'operator' => '+', + 'increment' => 'add two to previous value', + 'steps' => 1, + 'mediumStep' => 4, + 'unit' => 'rem', + ), + 'expected_output' => null, + ), + 'non numeric steps' => array( + 'spacing_scale' => array( + 'operator' => '+', + 'increment' => 1.5, + 'steps' => 'spiral staircase preferred', + 'mediumStep' => 4, + 'unit' => 'rem', + ), + 'expected_output' => null, + ), + 'non numeric medium step' => array( + 'spacing_scale' => array( + 'operator' => '+', + 'increment' => 1.5, + 'steps' => 5, + 'mediumStep' => 'That which is just right', + 'unit' => 'rem', + ), + 'expected_output' => null, + ), + 'missing unit value' => array( + 'spacing_scale' => array( + 'operator' => '+', + 'increment' => 1.5, + 'steps' => 5, + 'mediumStep' => 4, ), + 'expected_output' => null, ), ); } /** - * @dataProvider data_get_styles_for_block_with_style_variations + * Tests the core separator block outbut based on various provided settings. * - * @param array $theme_json_variations Theme.json variations to test. - * @param string $metadata_variations Style variations to test. - * @param string $expected Expected results for styling. + * @dataProvider data_update_separator_declarations + * + * @param array $separator_block_settings Example separator block settings from the data provider. + * @param array $expected_output Expected output from data provider. */ - public function test_get_styles_for_block_with_style_variations( $theme_json_variations, $metadata_variations, $expected ) { + public function test_update_separator_declarations( $separator_block_settings, $expected_output ) { + // If only background is defined, test that includes border-color to the style so it is applied on the front end. $theme_json = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'styles' => array( 'blocks' => array( - 'core/quote' => $theme_json_variations, + 'core/separator' => $separator_block_settings, ), ), - ) + ), + 'default' ); - // Validate styles are generated properly. - $metadata = array( - 'path' => array( 'styles', 'blocks', 'core/quote' ), - 'selector' => '.wp-block-quote', - 'variations' => $metadata_variations, - ); - $actual_styles = $theme_json->get_styles_for_block( $metadata ); - $this->assertSame( $expected, $actual_styles ); + $stylesheet = $theme_json->get_stylesheet( array( 'styles' ) ); + + $this->assertSame( $expected_output, $stylesheet ); } /** - * Data provider. + * Data provider for separator declaration tests. * * @return array */ - public function data_get_styles_for_block_with_style_variations() { - $plain = array( - 'metadata' => array( - 'path' => array( 'styles', 'blocks', 'core/quote', 'variations', 'plain' ), - 'selector' => '.is-style-plain.is-style-plain.wp-block-quote', - ), - 'styles' => '.is-style-plain.is-style-plain.wp-block-quote{background-color: hotpink;}', - ); - + public function data_update_separator_declarations() { return array( - '1 variation with 1 invalid property' => array( - 'theme_json_variations' => array( - 'variations' => array( - 'plain' => array( - 'color' => array( - 'background' => 'hotpink', - ), - ), + // If only background is defined, test that includes border-color to the style so it is applied on the front end. + 'only background' => array( + array( + 'color' => array( + 'background' => 'blue', ), ), - 'metadata_variation' => array( $plain['metadata'] ), - 'expected' => $plain['styles'], + 'expected_output' => 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}.wp-block-separator{background-color: blue;color: blue;}', ), - '1 variation with 2 invalid properties' => array( - 'theme_json_variations' => array( - 'variations' => array( - 'plain' => array( - 'color' => array( - 'background' => 'hotpink', - ), - 'invalidProperty1' => 'value1', - 'invalidProperty2' => 'value2', - ), + // If background and text are defined, do not include border-color, as text color is enough. + 'background and text, no border-color' => array( + array( + 'color' => array( + 'background' => 'blue', + 'text' => 'red', ), ), - 'metadata_variation' => array( $plain['metadata'] ), - 'expected' => $plain['styles'], + 'expected_output' => 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}.wp-block-separator{background-color: blue;color: red;}', ), - ); - } - - public function test_block_style_variations() { - wp_set_current_user( static::$administrator_id ); - - $expected = array( - 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, - 'styles' => array( - 'blocks' => array( - 'core/button' => array( - 'color' => array( - 'background' => 'blue', - ), - 'variations' => array( - 'outline' => array( - 'color' => array( - 'background' => 'purple', - ), - ), - ), + // If only text is defined, do not include border-color, as by itself is enough. + 'only text' => array( + array( + 'color' => array( + 'text' => 'red', ), ), + 'expected_output' => 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}.wp-block-separator{color: red;}', ), - ); - - $actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties( $expected ); - - $this->assertSameSetsWithIndex( $expected, $actual ); - } - - public function test_block_style_variations_with_invalid_properties() { - wp_set_current_user( static::$administrator_id ); - - $partially_invalid_variation = array( - 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, - 'styles' => array( - 'blocks' => array( - 'core/button' => array( - 'color' => array( - 'background' => 'blue', - ), - 'variations' => array( - 'outline' => array( - 'color' => array( - 'background' => 'purple', - ), - 'invalid' => array( - 'value' => 'should be stripped', - ), - ), - ), + // If background, text, and border-color are defined, include everything, CSS specificity will decide which to apply. + 'background, text, and border-color' => array( + array( + 'color' => array( + 'background' => 'blue', + 'text' => 'red', + ), + 'border' => array( + 'color' => 'pink', ), ), + 'expected_output' => 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}.wp-block-separator{background-color: blue;border-color: pink;color: red;}', ), - ); - - $expected = array( - 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, - 'styles' => array( - 'blocks' => array( - 'core/button' => array( - 'color' => array( - 'background' => 'blue', - ), - 'variations' => array( - 'outline' => array( - 'color' => array( - 'background' => 'purple', - ), - ), - ), + // If background and border color are defined, include everything, CSS specificity will decide which to apply. + 'background, and border-color' => array( + array( + 'color' => array( + 'background' => 'blue', + ), + 'border' => array( + 'color' => 'pink', ), ), + 'expected_output' => 'body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}.wp-block-separator{background-color: blue;border-color: pink;}', ), ); - - $actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties( $partially_invalid_variation ); - - $this->assertSameSetsWithIndex( $expected, $actual ); } - public function test_update_separator_declarations() { - // If only background is defined, test that includes border-color to the style so it is applied on the front end. + public function test_shadow_preset_styles() { $theme_json = new WP_Theme_JSON_Gutenberg( array( - 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, - 'styles' => array( - 'blocks' => array( - 'core/separator' => array( - 'color' => array( - 'background' => 'blue', + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'shadow' => array( + 'presets' => array( + array( + 'slug' => 'natural', + 'shadow' => '5px 5px 5px 0 black', ), - ), - ), - ), - ), - 'default' - ); - - $base_styles = 'body { margin: 0;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; - $expected = $base_styles . '.wp-block-separator{background-color: blue;color: blue;}'; - $stylesheet = $theme_json->get_stylesheet( array( 'styles' ) ); - $this->assertEquals( $expected, $stylesheet ); - - // If background and text are defined, do not include border-color, as text color is enough. - $theme_json = new WP_Theme_JSON_Gutenberg( - array( - 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, - 'styles' => array( - 'blocks' => array( - 'core/separator' => array( - 'color' => array( - 'background' => 'blue', - 'text' => 'red', + array( + 'slug' => 'sharp', + 'shadow' => '5px 5px black', ), ), ), ), - ), - 'default' + ) ); - $expected = $base_styles . '.wp-block-separator{background-color: blue;color: red;}'; - $stylesheet = $theme_json->get_stylesheet( array( 'styles' ) ); - $this->assertEquals( $expected, $stylesheet ); + $expected_styles = 'body{--wp--preset--shadow--natural: 5px 5px 5px 0 black;--wp--preset--shadow--sharp: 5px 5px black;}'; + $this->assertSame( $expected_styles, $theme_json->get_stylesheet(), 'Styles returned from "::get_stylesheet()" does not match expectations' ); + $this->assertSame( $expected_styles, $theme_json->get_stylesheet( array( 'variables' ) ), 'Styles returned from "::get_stylesheet()" when requiring "variables" type does not match expectations' ); + } - // If only text is defined, do not include border-color, as by itself is enough. + public function test_get_shadow_styles_for_blocks() { $theme_json = new WP_Theme_JSON_Gutenberg( array( - 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, - 'styles' => array( - 'blocks' => array( - 'core/separator' => array( - 'color' => array( - 'text' => 'red', + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'shadow' => array( + 'presets' => array( + array( + 'slug' => 'natural', + 'shadow' => '5px 5px 0 0 black', ), ), ), ), - ), - 'default' - ); - $expected = $base_styles . '.wp-block-separator{color: red;}'; - $stylesheet = $theme_json->get_stylesheet( array( 'styles' ) ); - $this->assertEquals( $expected, $stylesheet ); - - // If background, text, and border-color are defined, include everything, CSS specificity will decide which to apply. - $theme_json = new WP_Theme_JSON_Gutenberg( - array( - 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, - 'styles' => array( - 'blocks' => array( - 'core/separator' => array( - 'color' => array( - 'background' => 'blue', - 'text' => 'red', - ), - 'border' => array( - 'color' => 'pink', - ), + 'styles' => array( + 'blocks' => array( + 'core/paragraph' => array( + 'shadow' => 'var(--wp--preset--shadow--natural)', ), ), - ), - ), - 'default' - ); - $expected = $base_styles . '.wp-block-separator{background-color: blue;border-color: pink;color: red;}'; - $stylesheet = $theme_json->get_stylesheet( array( 'styles' ) ); - $this->assertEquals( $expected, $stylesheet ); - - // If background and border color are defined, include everything, CSS specificity will decide which to apply. - $theme_json = new WP_Theme_JSON_Gutenberg( - array( - 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, - 'styles' => array( - 'blocks' => array( - 'core/separator' => array( - 'color' => array( - 'background' => 'blue', - ), - 'border' => array( - 'color' => 'pink', - ), + 'elements' => array( + 'button' => array( + 'shadow' => 'var:preset|shadow|natural', + ), + 'link' => array( + 'shadow' => array( 'ref' => 'styles.elements.button.shadow' ), ), ), ), - ), - 'default' + ) ); - $expected = $base_styles . '.wp-block-separator{background-color: blue;border-color: pink;}'; - $stylesheet = $theme_json->get_stylesheet( array( 'styles' ) ); - $this->assertEquals( $expected, $stylesheet ); + + $global_styles = 'body{--wp--preset--shadow--natural: 5px 5px 0 0 black;}body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}'; + $element_styles = 'a:where(:not(.wp-element-button)){box-shadow: var(--wp--preset--shadow--natural);}.wp-element-button, .wp-block-button__link{box-shadow: var(--wp--preset--shadow--natural);}p{box-shadow: var(--wp--preset--shadow--natural);}'; + $expected_styles = $global_styles . $element_styles; + $this->assertSame( $expected_styles, $theme_json->get_stylesheet() ); } public function test_get_custom_css_handles_global_custom_css() { @@ -2102,10 +4755,12 @@ public function test_get_custom_css_handles_global_custom_css() { ); $custom_css = 'body {color:purple;}p{color:red;}'; - $this->assertEquals( $custom_css, $theme_json->get_custom_css() ); + $this->assertSame( $custom_css, $theme_json->get_custom_css() ); } /** + * Tests that custom CSS is kept for users with correct capabilities and removed for others. + * * @dataProvider data_custom_css_for_user_caps * * @param string $user_property The property name for current user. @@ -2190,7 +4845,7 @@ public function test_process_blocks_custom_css( $input, $expected ) { $reflection = new ReflectionMethod( $theme_json, 'process_blocks_custom_css' ); $reflection->setAccessible( true ); - $this->assertEquals( $expected, $reflection->invoke( $theme_json, $input['css'], $input['selector'] ) ); + $this->assertSame( $expected, $reflection->invoke( $theme_json, $input['css'], $input['selector'] ) ); } /** @@ -2452,30 +5107,6 @@ public function test_resolve_variables() { $this->assertEquals( $secondary_color, $styles['blocks']['core/quote']['variations']['plain']['color']['background'], 'Block variations: color' ); } - public function test_get_stylesheet_custom_root_selector() { - $theme_json = new WP_Theme_JSON_Gutenberg( - array( - 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, - 'styles' => array( - 'color' => array( - 'text' => 'teal', - ), - ), - ), - 'default' - ); - - $options = array( 'root_selector' => '.custom' ); - $actual = $theme_json->get_stylesheet( array( 'styles' ), null, $options ); - - // Results also include root site blocks styles which hard code - // `body { margin: 0;}`. - $this->assertEquals( - 'body { margin: 0;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }.custom{color: teal;}', - $actual - ); - } - /** * Tests the correct application of a block style variation's selector to * a block's selector. diff --git a/phpunit/data/languages/themes/block-theme-pl_PL.mo b/phpunit/data/languages/themes/block-theme-pl_PL.mo index ff89f87f127f15..d819be9b57e3cd 100644 Binary files a/phpunit/data/languages/themes/block-theme-pl_PL.mo and b/phpunit/data/languages/themes/block-theme-pl_PL.mo differ diff --git a/phpunit/data/languages/themes/block-theme-pl_PL.po b/phpunit/data/languages/themes/block-theme-pl_PL.po index 0aa4bbd52d34fc..6ac5a6967e73c4 100644 --- a/phpunit/data/languages/themes/block-theme-pl_PL.po +++ b/phpunit/data/languages/themes/block-theme-pl_PL.po @@ -2,22 +2,30 @@ msgid "" msgstr "" "Project-Id-Version: \n" "POT-Creation-Date: 2015-12-31 16:31+0100\n" -"PO-Revision-Date: 2021-03-15 13:10+0100\n" +"PO-Revision-Date: 2022-08-31 11:08+0200\n" +"Last-Translator: \n" +"Language-Team: \n" "Language: pl_PL\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 2.4.2\n" -"X-Poedit-Basepath: .\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.1.1\n" +"X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: __;_e;_x:1,2c;_ex:1,2c;_n:1,2;_nx:1,2,4c;_n_noop:1,2;" "_nx_noop:1,2,3c;esc_attr__;esc_html__;esc_attr_e;esc_html_e;esc_attr_x:1,2c;" "esc_html_x:1,2c\n" "X-Textdomain-Support: yes\n" -"Last-Translator: \n" -"Language-Team: \n" "X-Poedit-SearchPath-0: .\n" +msgctxt "Style variation name" +msgid "Block theme" +msgstr "Motyw blokowy" + +msgctxt "Style variation name" +msgid "Block theme variation" +msgstr "Wariant motywu blokowego" + msgctxt "Custom template name" msgid "Homepage template" msgstr "Szablon strony głównej" diff --git a/phpunit/data/themedir1/block-theme-child/styles/variation-b.json b/phpunit/data/themedir1/block-theme-child/styles/variation-b.json new file mode 100644 index 00000000000000..0a8a4fcab99f61 --- /dev/null +++ b/phpunit/data/themedir1/block-theme-child/styles/variation-b.json @@ -0,0 +1,18 @@ +{ + "version": 2, + "settings": { + "blocks": { + "core/post-title": { + "color": { + "palette": [ + { + "slug": "dark", + "name": "Dark", + "color": "#010101" + } + ] + } + } + } + } +} diff --git a/phpunit/data/themedir1/block-theme/styles/variation.json b/phpunit/data/themedir1/block-theme/styles/variation.json new file mode 100644 index 00000000000000..d0f316cb454dd9 --- /dev/null +++ b/phpunit/data/themedir1/block-theme/styles/variation.json @@ -0,0 +1,24 @@ +{ + "version": 2, + "title": "Block theme variation", + "settings": { + "color": { + "palette": [ + { + "slug": "foreground", + "color": "#3F67C6", + "name": "Foreground" + } + ] + } + }, + "styles": { + "blocks": { + "core/post-title": { + "typography": { + "fontWeight": "700" + } + } + } + } +} diff --git a/phpunit/data/themedir1/block-theme/theme.json b/phpunit/data/themedir1/block-theme/theme.json index 78c7ff4768cf60..212ef5df78f7e6 100644 --- a/phpunit/data/themedir1/block-theme/theme.json +++ b/phpunit/data/themedir1/block-theme/theme.json @@ -1,6 +1,7 @@ { "$schema": "https://schemas.wp.org/trunk/theme.json", "version": 2, + "title": "Block theme", "settings": { "color": { "palette": [ @@ -15,7 +16,38 @@ "color": "#000" } ], - "custom": false + "gradients": [ + { + "name": "Custom gradient", + "gradient": "linear-gradient(135deg,rgba(0,0,0) 0%,rgb(0,0,0) 100%)", + "slug": "custom-gradient" + } + ], + "duotone": [ + { + "colors": [ "#333333", "#aaaaaa" ], + "slug": "custom-duotone", + "name": "Custom Duotone" + } + ], + "custom": false, + "customGradient": false + }, + "typography": { + "fontSizes": [ + { + "name": "Custom", + "slug": "custom", + "size": "100px" + } + ], + "customFontSize": false, + "lineHeight": true + }, + "spacing": { + "units": [ "rem" ], + "padding": true, + "blockGap": true }, "blocks": { "core/paragraph": { @@ -31,10 +63,66 @@ } } }, + "styles": { + "blocks": { + "core/post-featured-image": { + "shadow": "10px 10px 5px 0px rgba(0,0,0,0.66)", + "filter": { + "duotone": "var(--wp--preset--duotone--custom-duotone)" + } + }, + "my/third-party-block": { + "color": { + "background": "hotpink" + }, + "elements": { + "cite": { + "color": { + "text": "white" + } + } + } + } + }, + "elements": { + "button": { + "shadow": "10px 10px 5px 0px rgba(0,0,0,0.66)" + }, + "link": { + "typography": { + "textDecoration": "none" + }, + "border": { + "bottom": { + "width": "2px", + "color": "currentColor", + "style": "solid" + } + }, + ":hover": { + "typography": { + "textDecoration": "none" + }, + "border": { + "bottom": { + "width": "2px", + "color": "#000", + "style": "dotted" + } + } + } + } + } + }, "customTemplates": [ { "name": "page-home", "title": "Homepage template" + }, + { + "name": "custom-single-post-template", + "title": "Custom Single Post template", + "postTypes": [ "post" ] } ], "templateParts": [ diff --git a/phpunit/interactivity-api/class-wp-interactivity-api-wp-router-region-test.php b/phpunit/interactivity-api/class-wp-interactivity-api-wp-router-region-test.php new file mode 100644 index 00000000000000..27fa7c1edd1e72 --- /dev/null +++ b/phpunit/interactivity-api/class-wp-interactivity-api-wp-router-region-test.php @@ -0,0 +1,147 @@ +interactivity = new WP_Interactivity_API(); + + // Remove all hooks set for `wp_footer`. + global $wp_filter; + $this->original_wp_footer = $wp_filter['wp_footer']; + $wp_filter['wp_footer'] = new WP_Hook(); + } + + /** + * Tear down. + */ + public function tear_down() { + // Restore all previous hooks set for `wp_footer`. + global $wp_filter; + $wp_filter['wp_footer'] = $this->original_wp_footer; + + parent::tear_down(); + } + + /** + * Helper to execute the hooks associated to `wp_footer`. + */ + protected function render_wp_footer() { + ob_start(); + do_action( 'wp_footer' ); + return ob_get_clean(); + } + + /** + * Tests that no elements are added if the `data-wp-router-region` is + * missing. + */ + public function test_wp_router_region_missing() { + $html = '