From 2b35b60c36922bbb0e98df35c9723943f88af47f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Zi=C3=B3=C5=82kowski?= Date: Wed, 15 Dec 2021 15:20:02 +0100 Subject: [PATCH 01/34] Build: Fix package dependencies issues discovered (#37396) Props to @youknowriad to fixing all those subtle bug while exploring pnpm in Gutenberg with #37324. --- package-lock.json | 867 ++++++++++++++---- package.json | 2 +- packages/e2e-tests/package.json | 2 +- .../package.json | 4 +- 4 files changed, 680 insertions(+), 195 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9f619f309689d..9d1598ead1c1d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3045,6 +3045,30 @@ "graceful-fs": "^4.2.4" } }, + "jest-snapshot": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz", + "integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0", + "@jest/types": "^26.6.2", + "@types/babel__traverse": "^7.0.4", + "@types/prettier": "^2.0.0", + "chalk": "^4.0.0", + "expect": "^26.6.2", + "graceful-fs": "^4.2.4", + "jest-diff": "^26.6.2", + "jest-get-type": "^26.3.0", + "jest-haste-map": "^26.6.2", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-resolve": "^26.6.2", + "natural-compare": "^1.4.0", + "pretty-format": "^26.6.2", + "semver": "^7.3.2" + } + }, "jest-util": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", @@ -5269,12 +5293,12 @@ }, "dependencies": { "@octokit/request-error": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.4.tgz", - "integrity": "sha512-LjkSiTbsxIErBiRh5wSZvpZqT4t0/c9+4dOe0PII+6jXR+oj/h66s7E4a/MghV7iT8W9ffoQ5Skoxzs96+gBPA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", + "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", "dev": true, "requires": { - "@octokit/types": "^6.0.0", + "@octokit/types": "^6.0.3", "deprecation": "^2.0.0", "once": "^1.4.0" } @@ -5287,6 +5311,28 @@ } } }, + "@octokit/request-error": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-1.2.1.tgz", + "integrity": "sha512-+6yDyk1EES6WK+l3viRDElw96MvwfJxCt45GvmjDUKWjYIb3PJZQkq3i46TwGwoPD4h8NmTrENmtyA1FwbmhRA==", + "dev": true, + "requires": { + "@octokit/types": "^2.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "dependencies": { + "@octokit/types": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.16.2.tgz", + "integrity": "sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==", + "dev": true, + "requires": { + "@types/node": ">= 8" + } + } + } + }, "@octokit/rest": { "version": "16.43.2", "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.43.2.tgz", @@ -6806,15 +6852,15 @@ } }, "@octokit/openapi-types": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-7.3.2.tgz", - "integrity": "sha512-oJhK/yhl9Gt430OrZOzAl2wJqR0No9445vmZ9Ey8GjUZUpwuu/vmEFP0TDhDXdpGDoxD6/EIFHJEcY8nHXpDTA==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz", + "integrity": "sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA==", "dev": true }, "@octokit/request": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.0.tgz", - "integrity": "sha512-4cPp/N+NqmaGQwbh3vUsYqokQIzt7VjsgTYVXiwpUP2pxd5YiZB2XuTedbb0SPtv9XS7nzAKjAuQxmY8/aZkiA==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.2.tgz", + "integrity": "sha512-je66CvSEVf0jCpRISxkUcCa0UkxmFs6eGDRSbfJtAVwbLH5ceqF+YEyC8lj8ystKyZTy8adWr0qmkY52EfOeLA==", "dev": true, "requires": { "@octokit/endpoint": "^6.0.1", @@ -6826,27 +6872,16 @@ }, "dependencies": { "@octokit/types": { - "version": "6.16.4", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.16.4.tgz", - "integrity": "sha512-UxhWCdSzloULfUyamfOg4dJxV9B+XjgrIZscI0VCbp4eNrjmorGEw+4qdwcpTsu6DIrm9tQsFQS2pK5QkqQ04A==", + "version": "6.34.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz", + "integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==", "dev": true, "requires": { - "@octokit/openapi-types": "^7.3.2" + "@octokit/openapi-types": "^11.2.0" } } } }, - "@octokit/request-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", - "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", - "dev": true, - "requires": { - "@octokit/types": "^6.0.3", - "deprecation": "^2.0.0", - "once": "^1.4.0" - } - }, "before-after-hook": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz", @@ -6925,15 +6960,15 @@ } }, "@octokit/openapi-types": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-7.3.2.tgz", - "integrity": "sha512-oJhK/yhl9Gt430OrZOzAl2wJqR0No9445vmZ9Ey8GjUZUpwuu/vmEFP0TDhDXdpGDoxD6/EIFHJEcY8nHXpDTA==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz", + "integrity": "sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA==", "dev": true }, "@octokit/request": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.0.tgz", - "integrity": "sha512-4cPp/N+NqmaGQwbh3vUsYqokQIzt7VjsgTYVXiwpUP2pxd5YiZB2XuTedbb0SPtv9XS7nzAKjAuQxmY8/aZkiA==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.2.tgz", + "integrity": "sha512-je66CvSEVf0jCpRISxkUcCa0UkxmFs6eGDRSbfJtAVwbLH5ceqF+YEyC8lj8ystKyZTy8adWr0qmkY52EfOeLA==", "dev": true, "requires": { "@octokit/endpoint": "^6.0.1", @@ -6945,27 +6980,16 @@ }, "dependencies": { "@octokit/types": { - "version": "6.16.4", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.16.4.tgz", - "integrity": "sha512-UxhWCdSzloULfUyamfOg4dJxV9B+XjgrIZscI0VCbp4eNrjmorGEw+4qdwcpTsu6DIrm9tQsFQS2pK5QkqQ04A==", + "version": "6.34.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz", + "integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==", "dev": true, "requires": { - "@octokit/openapi-types": "^7.3.2" + "@octokit/openapi-types": "^11.2.0" } } } }, - "@octokit/request-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", - "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", - "dev": true, - "requires": { - "@octokit/types": "^6.0.3", - "deprecation": "^2.0.0", - "once": "^1.4.0" - } - }, "is-plain-object": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", @@ -7060,6 +7084,26 @@ "universal-user-agent": "^2.1.0" }, "dependencies": { + "@octokit/request-error": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-1.2.1.tgz", + "integrity": "sha512-+6yDyk1EES6WK+l3viRDElw96MvwfJxCt45GvmjDUKWjYIb3PJZQkq3i46TwGwoPD4h8NmTrENmtyA1FwbmhRA==", + "dev": true, + "requires": { + "@octokit/types": "^2.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "@octokit/types": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.16.2.tgz", + "integrity": "sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==", + "dev": true, + "requires": { + "@types/node": ">= 8" + } + }, "is-plain-object": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz", @@ -7084,11 +7128,12 @@ } }, "@octokit/request-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-1.0.2.tgz", - "integrity": "sha512-T9swMS/Vc4QlfWrvyeSyp/GjhXtYaBzCcibjGywV4k4D2qVrQKfEMPy8OxMDEj7zkIIdpHwqdpVbKCvnUPqkXw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", + "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", "dev": true, "requires": { + "@octokit/types": "^6.0.3", "deprecation": "^2.0.0", "once": "^1.4.0" } @@ -7112,6 +7157,28 @@ "once": "^1.4.0", "universal-user-agent": "^2.0.0", "url-template": "^2.0.8" + }, + "dependencies": { + "@octokit/request-error": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-1.2.1.tgz", + "integrity": "sha512-+6yDyk1EES6WK+l3viRDElw96MvwfJxCt45GvmjDUKWjYIb3PJZQkq3i46TwGwoPD4h8NmTrENmtyA1FwbmhRA==", + "dev": true, + "requires": { + "@octokit/types": "^2.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "@octokit/types": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.16.2.tgz", + "integrity": "sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==", + "dev": true, + "requires": { + "@types/node": ">= 8" + } + } } }, "@octokit/types": { @@ -7134,12 +7201,12 @@ }, "dependencies": { "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { @@ -15772,6 +15839,7 @@ "expect-puppeteer": "^4.4.0", "filenamify": "^4.2.0", "jest-message-util": "^27.0.6", + "jest-snapshot": "^27.4.5", "lodash": "^4.17.21", "puppeteer-testing-library": "^0.5.0", "uuid": "^8.3.0" @@ -16246,7 +16314,9 @@ "requires": { "@actions/core": "^1.4.0", "@actions/github": "^5.0.0", - "@babel/runtime": "^7.16.0" + "@babel/runtime": "^7.16.0", + "@octokit/request-error": "^2.1.0", + "@octokit/webhooks": "^7.1.0" } }, "@wordpress/react-i18n": { @@ -37800,6 +37870,12 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "jest-get-type": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", + "dev": true + }, "jest-message-util": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", @@ -37817,6 +37893,30 @@ "stack-utils": "^2.0.2" } }, + "jest-snapshot": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz", + "integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0", + "@jest/types": "^26.6.2", + "@types/babel__traverse": "^7.0.4", + "@types/prettier": "^2.0.0", + "chalk": "^4.0.0", + "expect": "^26.6.2", + "graceful-fs": "^4.2.4", + "jest-diff": "^26.6.2", + "jest-get-type": "^26.3.0", + "jest-haste-map": "^26.6.2", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-resolve": "^26.6.2", + "natural-compare": "^1.4.0", + "pretty-format": "^26.6.2", + "semver": "^7.3.2" + } + }, "jest-util": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", @@ -39081,6 +39181,12 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "jest-get-type": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", + "dev": true + }, "jest-message-util": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", @@ -39098,6 +39204,30 @@ "stack-utils": "^2.0.2" } }, + "jest-snapshot": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz", + "integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0", + "@jest/types": "^26.6.2", + "@types/babel__traverse": "^7.0.4", + "@types/prettier": "^2.0.0", + "chalk": "^4.0.0", + "expect": "^26.6.2", + "graceful-fs": "^4.2.4", + "jest-diff": "^26.6.2", + "jest-get-type": "^26.3.0", + "jest-haste-map": "^26.6.2", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-resolve": "^26.6.2", + "natural-compare": "^1.4.0", + "pretty-format": "^26.6.2", + "semver": "^7.3.2" + } + }, "jest-util": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", @@ -39833,6 +39963,108 @@ "requires": { "@types/istanbul-lib-report": "*" } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "graceful-fs": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "jest-get-type": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", + "dev": true + }, + "jest-message-util": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", + "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/types": "^26.6.2", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.2", + "pretty-format": "^26.6.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.2" + } + }, + "jest-snapshot": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz", + "integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0", + "@jest/types": "^26.6.2", + "@types/babel__traverse": "^7.0.4", + "@types/prettier": "^2.0.0", + "chalk": "^4.0.0", + "expect": "^26.6.2", + "graceful-fs": "^4.2.4", + "jest-diff": "^26.6.2", + "jest-get-type": "^26.3.0", + "jest-haste-map": "^26.6.2", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-resolve": "^26.6.2", + "natural-compare": "^1.4.0", + "pretty-format": "^26.6.2", + "semver": "^7.3.2" + } + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + }, + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } } } }, @@ -40489,6 +40721,30 @@ "graceful-fs": "^4.2.4" } }, + "jest-snapshot": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz", + "integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0", + "@jest/types": "^26.6.2", + "@types/babel__traverse": "^7.0.4", + "@types/prettier": "^2.0.0", + "chalk": "^4.0.0", + "expect": "^26.6.2", + "graceful-fs": "^4.2.4", + "jest-diff": "^26.6.2", + "jest-get-type": "^26.3.0", + "jest-haste-map": "^26.6.2", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-resolve": "^26.6.2", + "natural-compare": "^1.4.0", + "pretty-format": "^26.6.2", + "semver": "^7.3.2" + } + }, "jest-util": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", @@ -40751,82 +41007,107 @@ } }, "jest-snapshot": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz", - "integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==", + "version": "27.4.5", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.4.5.tgz", + "integrity": "sha512-eCi/iM1YJFrJWiT9de4+RpWWWBqsHiYxFG9V9o/n0WXs6GpW4lUt4FAHAgFPTLPqCUVzrMQmSmTZSgQzwqR7IQ==", "dev": true, "requires": { + "@babel/core": "^7.7.2", + "@babel/generator": "^7.7.2", + "@babel/parser": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", "@babel/types": "^7.0.0", - "@jest/types": "^26.6.2", + "@jest/transform": "^27.4.5", + "@jest/types": "^27.4.2", "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.0.0", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^26.6.2", + "expect": "^27.4.2", "graceful-fs": "^4.2.4", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-haste-map": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-resolve": "^26.6.2", + "jest-diff": "^27.4.2", + "jest-get-type": "^27.4.0", + "jest-haste-map": "^27.4.5", + "jest-matcher-utils": "^27.4.2", + "jest-message-util": "^27.4.2", + "jest-resolve": "^27.4.5", + "jest-util": "^27.4.2", "natural-compare": "^1.4.0", - "pretty-format": "^26.6.2", + "pretty-format": "^27.4.2", "semver": "^7.3.2" }, "dependencies": { + "@jest/transform": { + "version": "27.4.5", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.4.5.tgz", + "integrity": "sha512-PuMet2UlZtlGzwc6L+aZmR3I7CEBpqadO03pU40l2RNY2fFJ191b9/ITB44LNOhVtsyykx0OZvj0PCyuLm7Eew==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/types": "^27.4.2", + "babel-plugin-istanbul": "^6.0.0", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^27.4.5", + "jest-regex-util": "^27.4.0", + "jest-util": "^27.4.2", + "micromatch": "^4.0.4", + "pirates": "^4.0.1", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" + } + }, "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "version": "27.4.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.4.2.tgz", + "integrity": "sha512-j35yw0PMTPpZsUoOBiuHzr1zTYoad1cVIE0ajEjcrJONxxrko/IRGKkXx3os0Nsi4Hu3+5VmDbVfq5WhG/pWAg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", - "@types/yargs": "^15.0.0", + "@types/yargs": "^16.0.0", "chalk": "^4.0.0" } }, "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", "dev": true, "requires": { "@types/istanbul-lib-report": "*" } }, - "@types/prettier": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.1.6.tgz", - "integrity": "sha512-6gOkRe7OIioWAXfnO/2lFiv+SJichKVSys1mSsgyrYHSEjk8Ctv4tSR/Odvnu+HWlH2C8j53dahU03XmQdd5fA==", - "dev": true - }, - "@types/stack-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", - "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", - "dev": true + "@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } }, "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true }, "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "dev": true, "requires": { "normalize-path": "^3.0.0", @@ -40842,27 +41123,38 @@ "fill-range": "^7.0.1" } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } + "camelcase": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.1.tgz", + "integrity": "sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA==", + "dev": true }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "ci-info": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz", + "integrity": "sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==", "dev": true }, - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "diff-sequences": { + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.4.0.tgz", + "integrity": "sha512-YqiQzkrsmHMH5uuh8OdQFU9/ZpADnwzml8z0O5HvRNda+5UZsaX/xN+AAxfR2hWq1Y7HZnAzO9J5lJXOuDz2Ww==", "dev": true }, + "expect": { + "version": "27.4.2", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.4.2.tgz", + "integrity": "sha512-BjAXIDC6ZOW+WBFNg96J22D27Nq5ohn+oGcuP2rtOtcjuxNoV9McpQ60PcQWhdFOSBIQdR72e+4HdnbZTFSTyg==", + "dev": true, + "requires": { + "@jest/types": "^27.4.2", + "ansi-styles": "^5.0.0", + "jest-get-type": "^27.4.0", + "jest-matcher-utils": "^27.4.2", + "jest-message-util": "^27.4.2", + "jest-regex-util": "^27.4.0" + } + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -40872,17 +41164,10 @@ "to-regex-range": "^5.0.1" } }, - "fsevents": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.2.1.tgz", - "integrity": "sha512-bTLYHSeC0UH/EFXS9KqWnXuOl/wHK5Z/d+ghd5AsFMYN7wIGkUCOJyzy88+wJKkZPGON8u4Z9f6U4FdgURE9qA==", - "dev": true, - "optional": true - }, "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", "dev": true }, "has-flag": { @@ -40897,55 +41182,102 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "jest-diff": { + "version": "27.4.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.4.2.tgz", + "integrity": "sha512-ujc9ToyUZDh9KcqvQDkk/gkbf6zSaeEg9AiBxtttXW59H/AcqEYp1ciXAtJp+jXWva5nAf/ePtSsgWwE5mqp4Q==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^27.4.0", + "jest-get-type": "^27.4.0", + "pretty-format": "^27.4.2" + } + }, "jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.4.0.tgz", + "integrity": "sha512-tk9o+ld5TWq41DkK14L4wox4s2D9MtTpKaAVzXfr5CUKm5ZK2ExcaFE0qls2W71zE/6R2TxxrK9w2r6svAFDBQ==", "dev": true }, "jest-haste-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", - "integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==", + "version": "27.4.5", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.4.5.tgz", + "integrity": "sha512-oJm1b5qhhPs78K24EDGifWS0dELYxnoBiDhatT/FThgB9yxqUm5F6li3Pv+Q+apMBmmPNzOBnZ7ZxWMB1Leq1Q==", "dev": true, "requires": { - "@jest/types": "^26.6.2", + "@jest/types": "^27.4.2", "@types/graceful-fs": "^4.1.2", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", - "fsevents": "^2.1.2", + "fsevents": "^2.3.2", "graceful-fs": "^4.2.4", - "jest-regex-util": "^26.0.0", - "jest-serializer": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "micromatch": "^4.0.2", - "sane": "^4.0.3", + "jest-regex-util": "^27.4.0", + "jest-serializer": "^27.4.0", + "jest-util": "^27.4.2", + "jest-worker": "^27.4.5", + "micromatch": "^4.0.4", "walker": "^1.0.7" } }, + "jest-matcher-utils": { + "version": "27.4.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.4.2.tgz", + "integrity": "sha512-jyP28er3RRtMv+fmYC/PKG8wvAmfGcSNproVTW2Y0P/OY7/hWUOmsPfxN1jOhM+0u2xU984u2yEagGivz9OBGQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^27.4.2", + "jest-get-type": "^27.4.0", + "pretty-format": "^27.4.2" + } + }, "jest-message-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", - "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", + "version": "27.4.2", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.4.2.tgz", + "integrity": "sha512-OMRqRNd9E0DkBLZpFtZkAGYOXl6ZpoMtQJWTAREJKDOFa0M6ptB7L67tp+cszMBkvSgKOhNtQp2Vbcz3ZZKo/w==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@jest/types": "^26.6.2", + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.4.2", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.4", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2", + "micromatch": "^4.0.4", + "pretty-format": "^27.4.2", "slash": "^3.0.0", - "stack-utils": "^2.0.2" + "stack-utils": "^2.0.3" + } + }, + "jest-regex-util": { + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.4.0.tgz", + "integrity": "sha512-WeCpMpNnqJYMQoOjm1nTtsgbR4XHAk1u00qDoNBQoykM280+/TmgA5Qh5giC1ecy6a5d4hbSsHzpBtu5yvlbEg==", + "dev": true + }, + "jest-resolve": { + "version": "27.4.5", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.4.5.tgz", + "integrity": "sha512-xU3z1BuOz/hUhVUL+918KqUgK+skqOuUsAi7A+iwoUldK6/+PW+utK8l8cxIWT9AW7IAhGNXjSAh1UYmjULZZw==", + "dev": true, + "requires": { + "@jest/types": "^27.4.2", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^27.4.5", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^27.4.2", + "jest-validate": "^27.4.2", + "resolve": "^1.20.0", + "resolve.exports": "^1.1.0", + "slash": "^3.0.0" } }, "jest-serializer": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz", - "integrity": "sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.4.0.tgz", + "integrity": "sha512-RDhpcn5f1JYTX2pvJAGDcnsNTnsV9bjYPU8xcV+xPwOXnUPOQwf4ZEuiU6G9H1UztH+OapMgu/ckEVwO87PwnQ==", "dev": true, "requires": { "@types/node": "*", @@ -40953,44 +41285,68 @@ } }, "jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", + "version": "27.4.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.4.2.tgz", + "integrity": "sha512-YuxxpXU6nlMan9qyLuxHaMMOzXAl5aGZWCSzben5DhLHemYQxCc4YK+4L3ZrCutT8GPQ+ui9k5D8rUJoDioMnA==", "dev": true, "requires": { - "@jest/types": "^26.6.2", + "@jest/types": "^27.4.2", "@types/node": "*", "chalk": "^4.0.0", + "ci-info": "^3.2.0", "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" + "picomatch": "^2.2.3" + }, + "dependencies": { + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true + } + } + }, + "jest-validate": { + "version": "27.4.2", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.4.2.tgz", + "integrity": "sha512-hWYsSUej+Fs8ZhOm5vhWzwSLmVaPAxRy+Mr+z5MzeaHm9AxUpXdoVMEW4R86y5gOobVfBsMFLk4Rb+QkiEpx1A==", + "dev": true, + "requires": { + "@jest/types": "^27.4.2", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^27.4.0", + "leven": "^3.1.0", + "pretty-format": "^27.4.2" } }, "jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", + "version": "27.4.5", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.4.5.tgz", + "integrity": "sha512-f2s8kEdy15cv9r7q4KkzGXvlY0JTcmCbMHZBfSQDwW77REr45IDWwd0lksDFeVHH2jJ5pqb90T77XscrjeGzzg==", "dev": true, "requires": { "@types/node": "*", "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" + "supports-color": "^8.0.0" } }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", "dev": true, "requires": { "braces": "^3.0.1", - "picomatch": "^2.0.5" + "picomatch": "^2.2.3" + }, + "dependencies": { + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true + } } }, "normalize-path": { @@ -40999,43 +41355,50 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "version": "27.4.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.4.2.tgz", + "integrity": "sha512-p0wNtJ9oLuvgOQDEIZ9zQjZffK7KtyR6Si0jnXULIDwrlNF8Cuir3AZP0hHv0jmKuNN/edOnbMjnzd4uTcmWiw==", "dev": true, "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", + "@jest/types": "^27.4.2", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "react-is": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", - "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, - "stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==", + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", "dev": true, "requires": { - "escape-string-regexp": "^2.0.0" + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" } }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -41049,6 +41412,18 @@ "requires": { "is-number": "^7.0.0" } + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } } } }, @@ -52601,6 +52976,12 @@ "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" }, + "resolve.exports": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", + "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", + "dev": true + }, "responselike": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", @@ -53798,6 +54179,15 @@ "color-convert": "^2.0.1" } }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -53813,6 +54203,90 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "graceful-fs": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "jest-get-type": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", + "dev": true + }, + "jest-message-util": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", + "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/types": "^26.6.2", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.2", + "pretty-format": "^26.6.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.2" + } + }, + "jest-snapshot": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz", + "integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0", + "@jest/types": "^26.6.2", + "@types/babel__traverse": "^7.0.4", + "@types/prettier": "^2.0.0", + "chalk": "^4.0.0", + "expect": "^26.6.2", + "graceful-fs": "^4.2.4", + "jest-diff": "^26.6.2", + "jest-get-type": "^26.3.0", + "jest-haste-map": "^26.6.2", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-resolve": "^26.6.2", + "natural-compare": "^1.4.0", + "pretty-format": "^26.6.2", + "semver": "^7.3.2" + } + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + }, + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true + }, "pretty-format": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", @@ -53830,6 +54304,15 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } } } }, diff --git a/package.json b/package.json index e14e20ef2591c..b7961ae3f6575 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,6 @@ "@emotion/jest": "11.3.0", "@emotion/native": "^11.0.0", "@octokit/rest": "16.26.0", - "@octokit/webhooks": "7.1.0", "@pmmmwh/react-refresh-webpack-plugin": "0.5.2", "@storybook/addon-a11y": "6.4.9", "@storybook/addon-controls": "6.4.9", @@ -199,6 +198,7 @@ "react-dom": "17.0.1", "react-native": "0.64.0", "react-native-url-polyfill": "1.1.2", + "react-refresh": "0.10.0", "react-test-renderer": "17.0.1", "redux": "4.1.2", "rimraf": "3.0.2", diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index 6ea148d6e10b6..7e13b3b061b31 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -32,13 +32,13 @@ "expect-puppeteer": "^4.4.0", "filenamify": "^4.2.0", "jest-message-util": "^27.0.6", + "jest-snapshot": "^27.4.5", "lodash": "^4.17.21", "puppeteer-testing-library": "^0.5.0", "uuid": "^8.3.0" }, "peerDependencies": { "jest": ">=26", - "jest-snapshot": ">=26", "puppeteer-core": ">=11" }, "publishConfig": { diff --git a/packages/project-management-automation/package.json b/packages/project-management-automation/package.json index d3033d24e66a2..f3ddf17c142d3 100644 --- a/packages/project-management-automation/package.json +++ b/packages/project-management-automation/package.json @@ -24,7 +24,9 @@ "dependencies": { "@actions/core": "^1.4.0", "@actions/github": "^5.0.0", - "@babel/runtime": "^7.16.0" + "@babel/runtime": "^7.16.0", + "@octokit/request-error": "^2.1.0", + "@octokit/webhooks": "^7.1.0" }, "publishConfig": { "access": "public" From 92923c6ace1492815ff3eedde9abacf1eaf746aa Mon Sep 17 00:00:00 2001 From: Mustaque Ahmed Date: Wed, 15 Dec 2021 20:14:20 +0530 Subject: [PATCH 02/34] Refactor DatePicker component to react hooks and function component (#36835) * Refactor DatePicker component to use react hooks and change it from class component to function component. It was already done as a part of different PR #22897. After code review it looks like the original author is not able to find time to reply or address review comments. I'm doing takeover from here and will take care of PR until it gets merged. * add changelog entry * add `onMonthPreviewed` in README.md * Remove `getMomentDate` test case Since in real word scenario, in UI we never deal with `getMomentDate` directly so in the test case we cannot write some event or simulate any behaviour that will give access to `getMomentDate` method. If we take help of `currentDate` props then test cases are already in the place. * Fix `onChangeMoment` test cases with help of `onDateChange` props * Move `getMomentDate` to a separate `utils.js` file - moved the `getMomentDate` tests under a new file, eg `test/utils.js` file - changed the `getMomentDate` unit tests to avoid rendering `DatePicker` and using enzyme * Remove extra spaces from the changelog file Co-authored-by: Marco Ciampini * Descriptive documentation on `onMonthPreviewed ` props Co-authored-by: Marco Ciampini Co-authored-by: Marco Ciampini --- packages/components/CHANGELOG.md | 1 + packages/components/src/date-time/README.md | 7 + packages/components/src/date-time/date.js | 151 ++++++++---------- .../components/src/date-time/test/date.js | 32 +--- .../components/src/date-time/test/utils.js | 32 ++++ packages/components/src/date-time/utils.js | 18 +++ 6 files changed, 128 insertions(+), 113 deletions(-) create mode 100644 packages/components/src/date-time/test/utils.js create mode 100644 packages/components/src/date-time/utils.js diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 1651c0e99b4f8..126bfc82e3177 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -27,6 +27,7 @@ ### Enhancements - Wrapped `Modal` in a `forwardRef` call ([#36831](https://github.com/WordPress/gutenberg/pull/36831)). +- Refactor `DateTime` class component to functional component ([#36835](https://github.com/WordPress/gutenberg/pull/36835)) - Unify styles for `ColorIndicator` with how they appear in Global Styles ([#37028](https://github.com/WordPress/gutenberg/pull/37028)) - Add support for rendering the `ColorPalette` in a `Dropdown` when opened in the sidebar ([#37067](https://github.com/WordPress/gutenberg/pull/37067)) - Show an incremental sequence of numbers (1/2/3/4/5) as a label of the font size, when we have at most five font sizes, where at least one the them contains a complex css value(clamp, var, etc..). We do this because complex css values cannot be calculated properly and the incremental sequence of numbers as labels can help the user better mentally map the different available font sizes. ([#37038](https://github.com/WordPress/gutenberg/pull/37038)) diff --git a/packages/components/src/date-time/README.md b/packages/components/src/date-time/README.md index 1ecaa8b68e383..acf927d12c51d 100644 --- a/packages/components/src/date-time/README.md +++ b/packages/components/src/date-time/README.md @@ -64,3 +64,10 @@ A callback function which receives a Date object representing a day as an argume - Type: `Function` - Required: No + +### onMonthPreviewed + +A callback invoked when selecting the previous/next month in the date picker. The callback receives the new month date in the ISO format as an argument. + +- Type: `Function` +- Required: No diff --git a/packages/components/src/date-time/date.js b/packages/components/src/date-time/date.js index d70181d3d655d..fd6869ac84333 100644 --- a/packages/components/src/date-time/date.js +++ b/packages/components/src/date-time/date.js @@ -11,9 +11,14 @@ import DayPickerSingleDateController from 'react-dates/lib/components/DayPickerS /** * WordPress dependencies */ -import { Component, createRef, useEffect, useRef } from '@wordpress/element'; +import { useEffect, useRef } from '@wordpress/element'; import { isRTL, _n, sprintf } from '@wordpress/i18n'; +/** + * Internal dependencies + */ +import { getMomentDate } from './utils'; + /** * Module Constants */ @@ -70,21 +75,18 @@ function DatePickerDay( { day, events = [] } ) { ); } -class DatePicker extends Component { - constructor() { - super( ...arguments ); - - this.onChangeMoment = this.onChangeMoment.bind( this ); - this.nodeRef = createRef(); - this.onMonthPreviewedHandler = this.onMonthPreviewedHandler.bind( - this - ); - } - - onMonthPreviewedHandler( newMonthDate ) { - this.props.onMonthPreviewed?.( newMonthDate.toISOString() ); - this.keepFocusInside(); - } +function DatePicker( { + currentDate, + onChange, + events, + isInvalidDate, + onMonthPreviewed, +} ) { + const nodeRef = useRef(); + const onMonthPreviewedHandler = ( newMonthDate ) => { + onMonthPreviewed?.( newMonthDate.toISOString() ); + keepFocusInside(); + }; /* * Todo: We should remove this function ASAP. @@ -92,21 +94,21 @@ class DatePicker extends Component { * This focus loss closes the date picker popover. * Ideally we should add an upstream commit on react-dates to fix this issue. */ - keepFocusInside() { - if ( ! this.nodeRef.current ) { + const keepFocusInside = () => { + if ( ! nodeRef.current ) { return; } - const { ownerDocument } = this.nodeRef.current; + const { ownerDocument } = nodeRef.current; const { activeElement } = ownerDocument; // If focus was lost. if ( ! activeElement || - ! this.nodeRef.current.contains( ownerDocument.activeElement ) + ! nodeRef.current.contains( ownerDocument.activeElement ) ) { // Retrieve the focus region div. - const focusRegion = this.nodeRef.current.querySelector( + const focusRegion = nodeRef.current.querySelector( '.DayPicker_focusRegion' ); if ( ! focusRegion ) { @@ -115,11 +117,9 @@ class DatePicker extends Component { // Keep the focus on focus region. focusRegion.focus(); } - } - - onChangeMoment( newDate ) { - const { currentDate, onChange } = this.props; + }; + const onChangeMoment = ( newDate ) => { // If currentDate is null, use now as momentTime to designate hours, minutes, seconds. const momentDate = currentDate ? moment( currentDate ) : moment(); const momentTime = { @@ -131,71 +131,54 @@ class DatePicker extends Component { onChange( newDate.set( momentTime ).format( TIMEZONELESS_FORMAT ) ); // Keep focus on the date picker. - this.keepFocusInside(); - } - - /** - * Create a Moment object from a date string. With no currentDate supplied, default to a Moment - * object representing now. If a null value is passed, return a null value. - * - * @param {?string} currentDate Date representing the currently selected date or null to signify no selection. - * @return {?moment.Moment} Moment object for selected date or null. - */ - getMomentDate( currentDate ) { - if ( null === currentDate ) { - return null; - } - return currentDate ? moment( currentDate ) : moment(); - } + keepFocusInside(); + }; - getEventsPerDay( day ) { - if ( ! this.props.events?.length ) { + const getEventsPerDay = ( day ) => { + if ( ! events?.length ) { return []; } - return this.props.events.filter( ( eventDay ) => + return events.filter( ( eventDay ) => day.isSame( eventDay.date, 'day' ) ); - } - - render() { - const { currentDate, isInvalidDate } = this.props; - const momentDate = this.getMomentDate( currentDate ); - - return ( -
- { - return isInvalidDate && isInvalidDate( date.toDate() ); - } } - onPrevMonthClick={ this.onMonthPreviewedHandler } - onNextMonthClick={ this.onMonthPreviewedHandler } - renderDayContents={ ( day ) => ( - - ) } - /> -
- ); - } + }; + + const momentDate = getMomentDate( currentDate ); + + return ( +
+ { + return isInvalidDate && isInvalidDate( date.toDate() ); + } } + onPrevMonthClick={ onMonthPreviewedHandler } + onNextMonthClick={ onMonthPreviewedHandler } + renderDayContents={ ( day ) => ( + + ) } + /> +
+ ); } export default DatePicker; diff --git a/packages/components/src/date-time/test/date.js b/packages/components/src/date-time/test/date.js index 3646249f14b00..4149a76e4eddf 100644 --- a/packages/components/src/date-time/test/date.js +++ b/packages/components/src/date-time/test/date.js @@ -31,32 +31,6 @@ describe( 'DatePicker', () => { expect( moment.isMoment( date ) ).toBe( true ); } ); - describe( 'getMomentDate', () => { - it( 'should return a Moment object representing a given date string', () => { - const currentDate = '1986-10-18T23:00:00'; - const wrapper = shallow( ); - const momentDate = wrapper.instance().getMomentDate( currentDate ); - - expect( moment.isMoment( momentDate ) ).toBe( true ); - expect( momentDate.isSame( moment( currentDate ) ) ).toBe( true ); - } ); - - it( 'should return null when given a null agrument', () => { - const currentDate = null; - const wrapper = shallow( ); - const momentDate = wrapper.instance().getMomentDate( currentDate ); - - expect( momentDate ).toBeNull(); - } ); - - it( 'should return a Moment object representing now when given an undefined argument', () => { - const wrapper = shallow( ); - const momentDate = wrapper.instance().getMomentDate(); - - expect( moment.isMoment( momentDate ) ).toBe( true ); - } ); - } ); - describe( 'onChangeMoment', () => { it( 'should call onChange with a formated date of the input', () => { const onChangeSpy = jest.fn(); @@ -69,7 +43,7 @@ describe( 'DatePicker', () => { ); const newDate = moment(); - wrapper.instance().onChangeMoment( newDate ); + wrapper.childAt( 0 ).props().onDateChange( newDate ); expect( onChangeSpy ).toHaveBeenCalledWith( newDate.format( TIMEZONELESS_FORMAT ) @@ -87,7 +61,7 @@ describe( 'DatePicker', () => { minutes: current.minutes(), seconds: current.seconds(), } ); - wrapper.instance().onChangeMoment( newDate ); + wrapper.childAt( 0 ).props().onDateChange( newDate ); expect( moment( onChangeSpyArgument ).isSame( @@ -110,7 +84,7 @@ describe( 'DatePicker', () => { minutes: current.minutes(), seconds: current.seconds(), } ); - wrapper.instance().onChangeMoment( newDate ); + wrapper.childAt( 0 ).props().onDateChange( newDate ); expect( moment( onChangeSpyArgument ).isSame( diff --git a/packages/components/src/date-time/test/utils.js b/packages/components/src/date-time/test/utils.js new file mode 100644 index 0000000000000..0f7d7a78ae390 --- /dev/null +++ b/packages/components/src/date-time/test/utils.js @@ -0,0 +1,32 @@ +/** + * External dependencies + */ +import moment from 'moment'; + +/** + * Internal dependencies + */ +import { getMomentDate } from '../utils'; + +describe( 'getMomentDate', () => { + it( 'should return a Moment object representing a given date string', () => { + const currentDate = '1986-10-18T23:00:00'; + const momentDate = getMomentDate( currentDate ); + + expect( moment.isMoment( momentDate ) ).toBe( true ); + expect( momentDate.isSame( moment( currentDate ) ) ).toBe( true ); + } ); + + it( 'should return null when given a null argument', () => { + const currentDate = null; + const momentDate = getMomentDate( currentDate ); + + expect( momentDate ).toBeNull(); + } ); + + it( 'should return a Moment object representing now when given an undefined argument', () => { + const momentDate = getMomentDate(); + + expect( moment.isMoment( momentDate ) ).toBe( true ); + } ); +} ); diff --git a/packages/components/src/date-time/utils.js b/packages/components/src/date-time/utils.js new file mode 100644 index 0000000000000..857b831ebb17a --- /dev/null +++ b/packages/components/src/date-time/utils.js @@ -0,0 +1,18 @@ +/** + * External dependencies + */ +import moment from 'moment'; + +/** + * Create a Moment object from a date string. With no date supplied, default to a Moment + * object representing now. If a null value is passed, return a null value. + * + * @param {?string} date Date representing the currently selected date or null to signify no selection. + * @return {?moment.Moment} Moment object for selected date or null. + */ +export const getMomentDate = ( date ) => { + if ( null === date ) { + return null; + } + return date ? moment( date ) : moment(); +}; From d69fcd1b9fcbf5d6c814a6b10683ed25962fbfbf Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski Date: Wed, 15 Dec 2021 16:09:03 +0100 Subject: [PATCH 03/34] ESLint Plugin: Fix the syntax for peer dependencies --- packages/eslint-plugin/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 3ef8f633ccfa6..a5cf6610ddc2f 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -50,8 +50,8 @@ }, "peerDependencies": { "@babel/core": ">=7", - "eslint": "=>8", - "typescript": "=>4" + "eslint": ">=8", + "typescript": ">=4" }, "peerDependenciesMeta": { "typescript": { From b12e82a53d534eac76550e5d03d513f8458b4cf2 Mon Sep 17 00:00:00 2001 From: Joen A <1204802+jasmussen@users.noreply.github.com> Date: Wed, 15 Dec 2021 16:23:39 +0100 Subject: [PATCH 04/34] Group, Columns: Lower specificity of padding rules further. (#37356) --- packages/block-library/src/columns/style.scss | 13 ++++++++----- packages/block-library/src/group/theme.scss | 9 ++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/block-library/src/columns/style.scss b/packages/block-library/src/columns/style.scss index 8bf22d8847e21..8a120282f0da0 100644 --- a/packages/block-library/src/columns/style.scss +++ b/packages/block-library/src/columns/style.scss @@ -10,10 +10,6 @@ flex-wrap: nowrap; } - &:where(.has-background) { - padding: $block-bg-padding--v $block-bg-padding--h; - } - /** * All Columns Alignment */ @@ -85,8 +81,8 @@ > .wp-block-column { // Available space should be divided equally amongst columns. flex-basis: 0; - flex-grow: 1; + flex-grow: 1; // Columns with an explicitly-assigned width should maintain their // `flex-basis` width and not grow. &[style*="flex-basis"] { @@ -101,6 +97,13 @@ } } +// Add low specificity default padding to columns blocks with backgrounds. +:where(.wp-block-columns.has-background) { + // Matches paragraph block padding. + padding: $block-bg-padding--v $block-bg-padding--h; +} + + .wp-block-column { flex-grow: 1; diff --git a/packages/block-library/src/group/theme.scss b/packages/block-library/src/group/theme.scss index 9d2b127311750..5a76fc43bd6d9 100644 --- a/packages/block-library/src/group/theme.scss +++ b/packages/block-library/src/group/theme.scss @@ -1,6 +1,5 @@ -.wp-block-group { - &:where(.has-background) { - // Matches paragraph Block padding - padding: $block-bg-padding--v $block-bg-padding--h; - } +// Add low specificity default padding to groups with backgrounds. +:where(.wp-block-group.has-background) { + // Matches paragraph block padding. + padding: $block-bg-padding--v $block-bg-padding--h; } From 404a0c40b735e5d0252b10871620fa64461abd5b Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 15 Dec 2021 16:56:28 +0000 Subject: [PATCH 05/34] Rename WP_Theme_JSON_Resolver methods (#37393) * Rename WP_Theme_JSON_Resolver methods * Update docblock --- .../class-wp-theme-json-resolver-gutenberg.php | 16 ++++++++-------- .../wordpress-5.9/rest-active-global-styles.php | 4 ++-- lib/full-site-editing/edit-site-page.php | 2 +- phpunit/class-wp-theme-json-resolver-test.php | 12 ++++++------ 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/compat/wordpress-5.9/class-wp-theme-json-resolver-gutenberg.php b/lib/compat/wordpress-5.9/class-wp-theme-json-resolver-gutenberg.php index 4d6acd25122fc..94e01a35f3857 100644 --- a/lib/compat/wordpress-5.9/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/compat/wordpress-5.9/class-wp-theme-json-resolver-gutenberg.php @@ -205,7 +205,7 @@ public static function get_theme_data( $deprecated = array() ) { * * @param WP_Theme $theme The theme object. If empty, it * defaults to the current theme. - * @param bool $should_create_cpt Optional. Whether a new custom post + * @param bool $create_post Optional. Whether a new custom post * type should be created if none are * found. False by default. * @param array $post_status_filter Filter Optional. custom post type by @@ -213,7 +213,7 @@ public static function get_theme_data( $deprecated = array() ) { * so it only fetches published posts. * @return array Custom Post Type for the user's origin config. */ - public static function get_user_data_from_custom_post_type( $theme, $should_create_cpt = false, $post_status_filter = array( 'publish' ) ) { + public static function get_user_data_from_wp_global_styles( $theme, $create_post = false, $post_status_filter = array( 'publish' ) ) { if ( ! $theme instanceof WP_Theme ) { $theme = wp_get_theme(); } @@ -242,14 +242,14 @@ public static function get_user_data_from_custom_post_type( $theme, $should_crea } // Special case: '-1' is a results not found. - if ( -1 === $post_id && ! $should_create_cpt ) { + if ( -1 === $post_id && ! $create_post ) { return $user_cpt; } $recent_posts = wp_get_recent_posts( $args ); if ( is_array( $recent_posts ) && ( count( $recent_posts ) === 1 ) ) { $user_cpt = $recent_posts[0]; - } elseif ( $should_create_cpt ) { + } elseif ( $create_post ) { $cpt_post_id = wp_insert_post( array( 'post_content' => '{"version": ' . WP_Theme_JSON_Gutenberg::LATEST_SCHEMA . ', "isGlobalStylesUserThemeJSON": true }', @@ -274,7 +274,7 @@ public static function get_user_data_from_custom_post_type( $theme, $should_crea /** * Returns the user's origin config. * - * @return WP_Theme_JSON_Gutenberg Entity that holds user data. + * @return WP_Theme_JSON_Gutenberg Entity that holds styles for user data. */ public static function get_user_data() { if ( null !== self::$user ) { @@ -282,7 +282,7 @@ public static function get_user_data() { } $config = array(); - $user_cpt = self::get_user_data_from_custom_post_type( wp_get_theme() ); + $user_cpt = self::get_user_data_from_wp_global_styles( wp_get_theme() ); if ( array_key_exists( 'post_content', $user_cpt ) ) { $decoded_data = json_decode( $user_cpt['post_content'], true ); @@ -352,12 +352,12 @@ public static function get_merged_data( $origin = 'custom' ) { * * @return integer|null */ - public static function get_user_custom_post_type_id() { + public static function get_user_global_styles_post_id() { if ( null !== self::$user_custom_post_type_id ) { return self::$user_custom_post_type_id; } - $user_cpt = self::get_user_data_from_custom_post_type( wp_get_theme(), true ); + $user_cpt = self::get_user_data_from_wp_global_styles( wp_get_theme(), true ); if ( array_key_exists( 'ID', $user_cpt ) ) { self::$user_custom_post_type_id = $user_cpt['ID']; diff --git a/lib/compat/wordpress-5.9/rest-active-global-styles.php b/lib/compat/wordpress-5.9/rest-active-global-styles.php index fef6a5448635e..af7f789110f66 100644 --- a/lib/compat/wordpress-5.9/rest-active-global-styles.php +++ b/lib/compat/wordpress-5.9/rest-active-global-styles.php @@ -14,9 +14,9 @@ function gutenberg_add_active_global_styles_link( $response, $theme ) { if ( $theme->get_stylesheet() === wp_get_theme()->get_stylesheet() ) { // This creates a record for the current theme if not existent. - $id = WP_Theme_JSON_Resolver_Gutenberg::get_user_custom_post_type_id(); + $id = WP_Theme_JSON_Resolver_Gutenberg::get_user_global_styles_post_id(); } else { - $user_cpt = WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_custom_post_type( $theme ); + $user_cpt = WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( $theme ); $id = isset( $user_cpt['ID'] ) ? $user_cpt['ID'] : null; } diff --git a/lib/full-site-editing/edit-site-page.php b/lib/full-site-editing/edit-site-page.php index 9a57b25b3657f..09777a0ab8f72 100644 --- a/lib/full-site-editing/edit-site-page.php +++ b/lib/full-site-editing/edit-site-page.php @@ -133,7 +133,7 @@ static function( $classes ) { $site_editor_context = new WP_Block_Editor_Context(); $settings = gutenberg_get_block_editor_settings( $custom_settings, $site_editor_context ); - $active_global_styles_id = WP_Theme_JSON_Resolver_Gutenberg::get_user_custom_post_type_id(); + $active_global_styles_id = WP_Theme_JSON_Resolver_Gutenberg::get_user_global_styles_post_id(); $active_theme = wp_get_theme()->get_stylesheet(); gutenberg_initialize_editor( 'edit_site_editor', diff --git a/phpunit/class-wp-theme-json-resolver-test.php b/phpunit/class-wp-theme-json-resolver-test.php index f991aea3d4384..489b2b72c91c4 100644 --- a/phpunit/class-wp-theme-json-resolver-test.php +++ b/phpunit/class-wp-theme-json-resolver-test.php @@ -268,25 +268,25 @@ function test_merges_child_theme_json_into_parent_theme_json() { ); } - function test_get_user_data_from_custom_post_type_does_not_use_uncached_queries() { + function test_get_user_data_from_wp_global_styles_does_not_use_uncached_queries() { add_filter( 'query', array( $this, 'filter_db_query' ) ); $query_count = count( $this->queries ); for ( $i = 0; $i < 3; $i++ ) { - WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_custom_post_type( wp_get_theme() ); + WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( wp_get_theme() ); WP_Theme_JSON_Resolver_Gutenberg::clean_cached_data(); } $query_count = count( $this->queries ) - $query_count; - $this->assertEquals( 1, $query_count, 'Only one SQL query should be peformed for multiple invocations of WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_custom_post_type()' ); + $this->assertEquals( 1, $query_count, 'Only one SQL query should be peformed for multiple invocations of WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles()' ); - $user_cpt = WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_custom_post_type( wp_get_theme() ); + $user_cpt = WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( wp_get_theme() ); $this->assertEmpty( $user_cpt ); - $user_cpt = WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_custom_post_type( wp_get_theme(), true ); + $user_cpt = WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( wp_get_theme(), true ); $this->assertNotEmpty( $user_cpt ); $query_count = count( $this->queries ); for ( $i = 0; $i < 3; $i++ ) { - WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_custom_post_type( wp_get_theme() ); + WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( wp_get_theme() ); WP_Theme_JSON_Resolver_Gutenberg::clean_cached_data(); } $query_count = count( $this->queries ) - $query_count; From 408bcd819fb60cfffe7fa10f57c86be0d5693ec6 Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Wed, 15 Dec 2021 18:08:59 +0000 Subject: [PATCH 06/34] Bump plugin version to 12.2.0-rc.1 --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- readme.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index c210f60b018b7..c01347265ca09 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -5,7 +5,7 @@ * Description: Printing since 1440. This is the development plugin for the new block editor in core. * Requires at least: 5.7 * Requires PHP: 5.6 - * Version: 12.1.0 + * Version: 12.2.0-rc.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index 9d1598ead1c1d..b339d41213653 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "12.1.0", + "version": "12.2.0-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index b7961ae3f6575..b93e9230ca28b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "12.1.0", + "version": "12.2.0-rc.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", diff --git a/readme.txt b/readme.txt index 0d5d9e2f2e095..72897ea687e62 100644 --- a/readme.txt +++ b/readme.txt @@ -56,4 +56,4 @@ The four phases of the project are Editing, Customization, Collaboration, and Mu == Changelog == -To read the changelog for Gutenberg 12.1.0, please navigate to the release page. +To read the changelog for Gutenberg 12.2.0-rc.1, please navigate to the release page. From f41c1a21116a7d6c5b26228eb98d83af46d4e3d7 Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Wed, 15 Dec 2021 18:47:49 +0000 Subject: [PATCH 07/34] Update Changelog for 12.2.0-rc.1 --- changelog.txt | 264 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 264 insertions(+) diff --git a/changelog.txt b/changelog.txt index 9ead99bfc85af..e08a8c83ce594 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,269 @@ == Changelog == += 12.2.0-rc.1 = + + + +### Enhancements + +#### Block Library +- Comment Template block: Handle nested comments. ([36065](https://github.com/WordPress/gutenberg/pull/36065)) +- Comments Query Loop: Improve context handling in inner blocks. ([37196](https://github.com/WordPress/gutenberg/pull/37196)) +- Image: Reflect media deletion in the editor. ([35973](https://github.com/WordPress/gutenberg/pull/35973)) +- Navigation: Refactor modal padding to be simpler and more flexible. ([37323](https://github.com/WordPress/gutenberg/pull/37323)) +- Update: PanelColorGradientSettings to use dropdowns. ([37067](https://github.com/WordPress/gutenberg/pull/37067)) +- Refactor ImportForm as a function component and use hooks. ([36938](https://github.com/WordPress/gutenberg/pull/36938)) +- Add a new "aria-pressed" attribute to the Toggle navigation button. ([37351](https://github.com/WordPress/gutenberg/pull/37351)) +- Refactor handling of padding for group and column blocks. ([37356](https://github.com/WordPress/gutenberg/pull/37356)) + +#### Accessibility +- Improve the heading text for screen readers in the Site Editor. ([37271](https://github.com/WordPress/gutenberg/pull/37271)) + +#### Global Styles +- Adds the "Welcome Guide" option to the "More Global Styles Actions" dropdown. ([37180](https://github.com/WordPress/gutenberg/pull/37180)) + +#### Site Editor +- Add the Customizer back to the Appearance menu. ([37175](https://github.com/WordPress/gutenberg/pull/37175)) + +#### Widgets Editor +- Implement preview for blocks. ([37012](https://github.com/WordPress/gutenberg/pull/37012)) + +#### Components +- ToolsPanel: Standardize input control label margin. ([36387](https://github.com/WordPress/gutenberg/pull/36387)) + +### Bug Fixes + +- Fix HTML drop issues with the Windows browsers. ([37151](https://github.com/WordPress/gutenberg/pull/37151)) +- Fix array key warning in block supports layout. ([37101](https://github.com/WordPress/gutenberg/pull/37101)) +- Fix returning empty array in getEntityRecords for unknown entities. ([36984](https://github.com/WordPress/gutenberg/pull/36984)) +- Fix the broken link on the plugin description page. ([37362](https://github.com/WordPress/gutenberg/pull/37362)) +- Format library: Fix unsetting highlight color. ([37062](https://github.com/WordPress/gutenberg/pull/37062)) +- Packages: Include `@babel/core` as a dependency where applicable. ([37328](https://github.com/WordPress/gutenberg/pull/37328)) +- Remove 4 instances of 'gutenberg' text-domain from WordPress core. ([37229](https://github.com/WordPress/gutenberg/pull/37229)) +- Simplify the RESET_BLOCK action to fix the loss of content when using template part focus mode. ([37283](https://github.com/WordPress/gutenberg/pull/37283)) +- Templates: Search for old template names in the parent theme too. ([36910](https://github.com/WordPress/gutenberg/pull/36910)) + +#### Block Library +- Don't request the deprecated navigation areas endpoint outside of the Gutenberg plugin. ([37187](https://github.com/WordPress/gutenberg/pull/37187)) +- Fix DocBlock type. ([37352](https://github.com/WordPress/gutenberg/pull/37352)) +- Fix color issue for captions with hyperlinks in Gallery block. ([37033](https://github.com/WordPress/gutenberg/pull/37033)) +- Fix deprecated usage of passing null to explode(). ([37392](https://github.com/WordPress/gutenberg/pull/37392)) +- Fix documentation and function naming for gallery block registration in PHP. ([37132](https://github.com/WordPress/gutenberg/pull/37132)) +- Fix form-submit styles by adding button classes to the submit button in the post-comments block. ([37245](https://github.com/WordPress/gutenberg/pull/37245)) +- Fix "gutenberg_" prefixed function references in core. ([37021](https://github.com/WordPress/gutenberg/pull/37021)) +- Fix post comment form input width. ([37238](https://github.com/WordPress/gutenberg/pull/37238)) +- Gallery: Remove placeholder border. ([37050](https://github.com/WordPress/gutenberg/pull/37050)) +- Image block: Set image display mode to "grid" when no alignment is set to align caption on resize event properly. ([35787](https://github.com/WordPress/gutenberg/pull/35787)) +- Image: Fix resizer controls being hidden in Safari when switching between alignments. ([37210](https://github.com/WordPress/gutenberg/pull/37210)) +- Link Control: Hide Transforms when no options are available. ([37284](https://github.com/WordPress/gutenberg/pull/37284)) +- Navigation: Fix navigation justifications. ([36991](https://github.com/WordPress/gutenberg/pull/36991)) +- Navigation: Fix vertical alignment of page list in the modal. ([37321](https://github.com/WordPress/gutenberg/pull/37321)) +- Navigation: Remove hardcoded typography units. ([37349](https://github.com/WordPress/gutenberg/pull/37349)) +- Only use block markup for the comment form button when using a block theme. ([37315](https://github.com/WordPress/gutenberg/pull/37315)) +- Query Loop: Add "useBlockPreview" component, fix Query Loop wide alignment in the editor. ([36431](https://github.com/WordPress/gutenberg/pull/36431)) +- Switch to addEventListener event for load event in the navigation block view script. ([37135](https://github.com/WordPress/gutenberg/pull/37135)) +- Template Part: Fix 'isMissing' condition check. ([37370](https://github.com/WordPress/gutenberg/pull/37370)) +- Update the "micromodal" package to include a click-through fix. ([36837](https://github.com/WordPress/gutenberg/pull/36837)) +- Use core version of template and template part post types and REST endpoints for WordPress 5.9, with backward compatibility for WordPress 5.8. ([36898](https://github.com/WordPress/gutenberg/pull/36898)) +- Comments Pagination Block: Add default styling on the editor. ([37057](https://github.com/WordPress/gutenberg/pull/37057)) +- Navigation: Fix vertical layout. ([37009](https://github.com/WordPress/gutenberg/pull/37009)) + +#### Global Styles +- Allow selector ordering to ensure theme.json root selector margin takes precedence. ([36960](https://github.com/WordPress/gutenberg/pull/36960)) +- Custom keys from `theme.json`: Fix kebabCase conversion. ([37380](https://github.com/WordPress/gutenberg/pull/37380)) +- Do not remove theme presets if defaults are hidden. ([37008](https://github.com/WordPress/gutenberg/pull/37008)) +- Fix CSS Custom Properties for presets in the site editor. ([36851](https://github.com/WordPress/gutenberg/pull/36851)) +- Fix how `appearanceTools` works. ([37254](https://github.com/WordPress/gutenberg/pull/37254)) +- Fix styles for previews and patterns. ([37296](https://github.com/WordPress/gutenberg/pull/37296)) +- Remove CSS that causes conflict with theme.json. ([36424](https://github.com/WordPress/gutenberg/pull/36424)) + +#### Components +- Element: Ensure that the package uses the up-to-date React types. ([37365](https://github.com/WordPress/gutenberg/pull/37365)) +- Fix incorrect rendering of ToggleGroupControl's active state. ([37332](https://github.com/WordPress/gutenberg/pull/37332)) +- Hide the "remove control point" button when removing would break gradient control. ([37186](https://github.com/WordPress/gutenberg/pull/37186)) +- Remove unused `reakit-utils` dependency from peer dependencies. ([37369](https://github.com/WordPress/gutenberg/pull/37369)) +- ToggleGroupControl: Fix extra update on incoming change. ([37224](https://github.com/WordPress/gutenberg/pull/37224)) +- Card: Support the `extraSmall` option for the `size` prop. ([37097](https://github.com/WordPress/gutenberg/pull/37097)) +- DuotonePicker typo fix. ([36662](https://github.com/WordPress/gutenberg/pull/36662)) + +#### Site Editor +- Display a notice if export fails. ([37131](https://github.com/WordPress/gutenberg/pull/37131)) +- Fix edit template part link in header dropdown. ([37249](https://github.com/WordPress/gutenberg/pull/37249)) +- Hide the block appender in the Template Part editor. ([37213](https://github.com/WordPress/gutenberg/pull/37213)) +- Improve notification when saving content in the Site Editor. ([36897](https://github.com/WordPress/gutenberg/pull/36897)) +- Sync Export API error codes. ([37347](https://github.com/WordPress/gutenberg/pull/37347)) +- Use server definition for the Template Areas. ([37215](https://github.com/WordPress/gutenberg/pull/37215)) +- Allow global styles sidebar panels to fill vertical space. ([36845](https://github.com/WordPress/gutenberg/pull/36845)) +- Improve handling of parsed requests. ([37209](https://github.com/WordPress/gutenberg/pull/37209)) +- Fix template resolution to give precedence to child theme PHP templates over parent theme block templates with equal specificity. ([37074](https://github.com/WordPress/gutenberg/pull/37074)) + +#### Design Tools +- Border Style Control: Update styling for consistency with border width control. ([37244](https://github.com/WordPress/gutenberg/pull/37244)) +- Border panel: Don't restore deselected border color when width gets changed. ([37049](https://github.com/WordPress/gutenberg/pull/37049)) +- Border panel: Update the value of the ColorPicker when color is cleared. ([37048](https://github.com/WordPress/gutenberg/pull/37048)) +- Gradient: Fix clearing a custom gradient from throwing a React warning. ([37051](https://github.com/WordPress/gutenberg/pull/37051)) +- Gradients: Enable adding custom gradient when gradients are disabled. ([36900](https://github.com/WordPress/gutenberg/pull/36900)) + +#### Templates API +- Fix WordPress 5.9 check for conditionally running code. ([37235](https://github.com/WordPress/gutenberg/pull/37235)) +- Fix mistake in _remove_theme_attribute_in_block_template_content. ([37137](https://github.com/WordPress/gutenberg/pull/37137)) + +#### Testing +- Sanitize flaky test report file names. ([37390](https://github.com/WordPress/gutenberg/pull/37390)) +- E2E: Retry login again after a bad nonce request to prevent intermittent test failures. ([37219](https://github.com/WordPress/gutenberg/pull/37219)) + +#### Template Editor +- Template Editor Mode: Hide editor mode switcher. ([37359](https://github.com/WordPress/gutenberg/pull/37359)) + +#### Post Editor +- Multi-Entity Saving: Decode HTML entities in item titles. ([37353](https://github.com/WordPress/gutenberg/pull/37353)) + +#### Reusable Blocks +- Fix content loss when ungrouping template parts or reusable blocks. ([37280](https://github.com/WordPress/gutenberg/pull/37280)) + +#### Themes +- Fix theme requirement validation with WordPress 5.8. ([37226](https://github.com/WordPress/gutenberg/pull/37226)) + +#### Block settings menu +- Block Editor: Mark last change as persistent on save. ([36887](https://github.com/WordPress/gutenberg/pull/36887)) + + +### Documentation + +- Add a documentation note about inner blocks and excerpts. ([36405](https://github.com/WordPress/gutenberg/pull/36405)) +- Add information about skip links to the block theme overview. ([36555](https://github.com/WordPress/gutenberg/pull/36555)) +- Docs: Add closing ticks for blockGap code example. ([37338](https://github.com/WordPress/gutenberg/pull/37338)) +- Docs: Add how-to guide documentation template. ([36694](https://github.com/WordPress/gutenberg/pull/36694)) +- Docs: Add link and details for npx in create block tutorial. ([37376](https://github.com/WordPress/gutenberg/pull/37376)) +- Docs: Consolidate React Native documentation. ([36685](https://github.com/WordPress/gutenberg/pull/36685)) +- Docs: DimentionControl Importing From Wrong Package. ([37192](https://github.com/WordPress/gutenberg/pull/37192)) +- Docs: Fix code formatting for card components. ([37268](https://github.com/WordPress/gutenberg/pull/37268)) +- Docs: Fix link to how-to guide template. ([37191](https://github.com/WordPress/gutenberg/pull/37191)) +- Docs: Move classes used in nav block from component to new readme. ([37375](https://github.com/WordPress/gutenberg/pull/37375)) +- Docs: Update Format API how-to guide. ([37298](https://github.com/WordPress/gutenberg/pull/37298)) +- Docs: Update broken links. ([37121](https://github.com/WordPress/gutenberg/pull/37121)) +- Docs: Improve formatting of headings in the contributors' guide. ([36689](https://github.com/WordPress/gutenberg/pull/36689)) +- Fix "type" for Prefix transformation. ([36362](https://github.com/WordPress/gutenberg/pull/36362)) +- Fix missing link for shortcut documentation. ([36800](https://github.com/WordPress/gutenberg/pull/36800)) +- Heading and Text Components are experimental. ([37290](https://github.com/WordPress/gutenberg/pull/37290)) +- Radio's still experimental but was loaded directly. ([36934](https://github.com/WordPress/gutenberg/pull/36934)) +- Remove versioning in theme schema descriptions. ([37165](https://github.com/WordPress/gutenberg/pull/37165)) +- Storybook: Fix deprecated `disabled` key. ([37112](https://github.com/WordPress/gutenberg/pull/37112)) +- Update QueryControls README.md. ([37233](https://github.com/WordPress/gutenberg/pull/37233)) +- Update documentation related to schemas. ([37294](https://github.com/WordPress/gutenberg/pull/37294)) +- Update example of usage for suggestionsQuery component. ([37281](https://github.com/WordPress/gutenberg/pull/37281)) +- Fix broken links in the Resources section. ([37307](https://github.com/WordPress/gutenberg/pull/37307)) +- Remove the semicolons from the JSON examples. ([37129](https://github.com/WordPress/gutenberg/pull/37129)) +- Update support documentation URL in the Welcome Guide. ([37176](https://github.com/WordPress/gutenberg/pull/37176)) + +#### Components +- Add missing CHANGELOG entries. ([37384](https://github.com/WordPress/gutenberg/pull/37384)) + +### Code Quality + +- Better synchronization between Gutenberg and core code. ([37141](https://github.com/WordPress/gutenberg/pull/37141)) +- Block editor: iframe: Use block settings to pass assets. ([37193](https://github.com/WordPress/gutenberg/pull/37193)) +- Move WordPress 5.9 wp-admin menus compatibility code to lib/compat folder. ([37257](https://github.com/WordPress/gutenberg/pull/37257)) +- Move the block page templates hook into compat/5.9 folder. ([37142](https://github.com/WordPress/gutenberg/pull/37142)) +- Moves to the template loader hooks and functions into lib/compat folder. ([37149](https://github.com/WordPress/gutenberg/pull/37149)) +- Scripts: Update dependencies shared with other WordPress packages. ([37395](https://github.com/WordPress/gutenberg/pull/37395)) +- Sort externalized dependencies report. ([37377](https://github.com/WordPress/gutenberg/pull/37377)) +- Specify the WordPress versions where API was deprecated. ([37150](https://github.com/WordPress/gutenberg/pull/37150)) +- Synchronize wp_is_block_theme and block-templates block support with core. ([37218](https://github.com/WordPress/gutenberg/pull/37218)) + +#### Global Styles +- Do not register global styles CPT in WordPress 5.9. ([37282](https://github.com/WordPress/gutenberg/pull/37282)) +- Port global styles code to `lib/compat/wordpress-5.9`: + - CSS custom properties. ([37334](https://github.com/WordPress/gutenberg/pull/37334)) + - Front assets. ([37335](https://github.com/WordPress/gutenberg/pull/37335)) +- Rename WP_Theme_JSON_Resolver methods to make them compatible with core. ([37393](https://github.com/WordPress/gutenberg/pull/37393)) + +#### Components +- Refactor DatePicker component to react hooks and function component. ([36835](https://github.com/WordPress/gutenberg/pull/36835)) +- Storybook: Update to the version 6.4. ([37367](https://github.com/WordPress/gutenberg/pull/37367)) + +#### Data Layer +- Data: Ensure that `redux` is listed as a dependency. ([37364](https://github.com/WordPress/gutenberg/pull/37364)) +- @wordpress/data: Refactor use-select in preparation for adding types. ([37017](https://github.com/WordPress/gutenberg/pull/37017)) + +#### Site Editor +- Remove unused PHP code. ([36997](https://github.com/WordPress/gutenberg/pull/36997)) +- Revert "Site Editor: Set the on the list page to be same as the CPT name". ([37270](https://github.com/WordPress/gutenberg/pull/37270)) + +#### Testing +- E2E Tests: Increase delay in image block end-to-end test to fix intermittent failure when clearing URL field. ([37136](https://github.com/WordPress/gutenberg/pull/37136)) +- E2E Tests: Update Multi-Entity Saving test to improve reliability. ([37139](https://github.com/WordPress/gutenberg/pull/37139)) + +#### Block Library +- Remove gutenberg_ prefix from @wordpress/block-library. ([37341](https://github.com/WordPress/gutenberg/pull/37341)) + +#### CSS & Styling +- Block Styles: Remove duplicate styles. ([37133](https://github.com/WordPress/gutenberg/pull/37133)) + + +### Tools + +- ESLint v8 (#35576). ([36283](https://github.com/WordPress/gutenberg/pull/36283)) + +#### Testing +- Add tests for the new gallery hooks. ([36801](https://github.com/WordPress/gutenberg/pull/36801)) +- Build: Remove unused `jest-serializer-enzyme` dependency. ([37373](https://github.com/WordPress/gutenberg/pull/37373)) +- Fix controlled blocks end-to-end tests after template part support from the post editor. ([37333](https://github.com/WordPress/gutenberg/pull/37333)) +- Fix usage of useSetting('color.palette'). ([37108](https://github.com/WordPress/gutenberg/pull/37108)) +- Fix: Heading color end to end test. ([37382](https://github.com/WordPress/gutenberg/pull/37382)) +- PHP Unit Tests: Use global transients. ([37122](https://github.com/WordPress/gutenberg/pull/37122)) +- Re-enable most of the navigation block end-to-end tests. ([37214](https://github.com/WordPress/gutenberg/pull/37214)) +- Specify PHP version in composer. ([37007](https://github.com/WordPress/gutenberg/pull/37007)) + +#### Build Tooling +- Build: Fix package dependencies issues discovered with pnpm. ([37396](https://github.com/WordPress/gutenberg/pull/37396)) +- Build: Use the latest minor version of webpack in Gutenberg. ([37371](https://github.com/WordPress/gutenberg/pull/37371)) +- Fix: Build failure on Windows. ([37189](https://github.com/WordPress/gutenberg/pull/37189)) +- Remove some labels from the "experiments" section of the changelog. ([37098](https://github.com/WordPress/gutenberg/pull/37098)) +- Scripts: Enable React Fast Refresh for block development. ([28273](https://github.com/WordPress/gutenberg/pull/28273)) + +#### Block Library +- Add Comments Pagination Numbers block. ([36890](https://github.com/WordPress/gutenberg/pull/36890)) +- Add actions that fire during the loading process of block template parts. ([36884](https://github.com/WordPress/gutenberg/pull/36884)) +- Add/navigation blocks post-processing after migration from menu items. ([36950](https://github.com/WordPress/gutenberg/pull/36950)) +- Navigation: Add clearance for appender in submenus. ([36720](https://github.com/WordPress/gutenberg/pull/36720)) +- Remove Navigation Menus from the Appearance menu. ([37212](https://github.com/WordPress/gutenberg/pull/37212)) +- Remove the deprecated navigation area block from the inserter. ([37026](https://github.com/WordPress/gutenberg/pull/37026)) +- Block Library: Rename Query Pagination blocks. ([37091](https://github.com/WordPress/gutenberg/pull/37091)) +- query-pagination-next and previous: Changing the markup on the first and last pages of pagination. ([36681](https://github.com/WordPress/gutenberg/pull/36681)) + +#### Global Styles +- Font sizes: Update default values. ([37381](https://github.com/WordPress/gutenberg/pull/37381)) +- Move Duotone palette to the bottom of global styles gradients. ([37253](https://github.com/WordPress/gutenberg/pull/37253)) +- Move the 'Edit colors' button to a standard menu item. ([36842](https://github.com/WordPress/gutenberg/pull/36842)) +- Update: Make the color popover on the gradient picker appear as expected. ([37115](https://github.com/WordPress/gutenberg/pull/37115)) +- Global Styles: Make the "Blocks" section more distinguishable. ([37293](https://github.com/WordPress/gutenberg/pull/37293)) + +#### Components +- ColorPalette: Improve accessibility and visibility. ([36925](https://github.com/WordPress/gutenberg/pull/36925)) +- Unify styles for "ColorIndicator" with how they appear in Global Styles. ([37028](https://github.com/WordPress/gutenberg/pull/37028)) +- FontSizePicker: Use an incremental sequence of numbers as labels for the available font sizes at the segmented control (conditionally). ([37038](https://github.com/WordPress/gutenberg/pull/37038)) + +#### Site Editor +- Add client-side routing for Site Editor. ([36488](https://github.com/WordPress/gutenberg/pull/36488)) +- Template revert flow: Make label description source agnostic. ([37004](https://github.com/WordPress/gutenberg/pull/37004)) + +#### CSS & Styling +- Search Block: Fix application of border color class in the editor. ([37242](https://github.com/WordPress/gutenberg/pull/37242)) + +#### Post Editor +- Remove template parts from post content inserter an __unstable filter. ([37157](https://github.com/WordPress/gutenberg/pull/37157)) + +#### Plugin +- Gallery block: Enable the new gallery block by default if running in core. ([37134](https://github.com/WordPress/gutenberg/pull/37134)) + +#### Design Tools +- Border controls: display border properties in the ToolPanel. ([34061](https://github.com/WordPress/gutenberg/pull/34061)) + + + + + = 12.1.0 = ### Enhancements From d0fa111d992660d05cc7ced418de1a7ca7c693ff Mon Sep 17 00:00:00 2001 From: George Mamadashvili <georgemamadashvili@gmail.com> Date: Thu, 16 Dec 2021 03:10:44 +0000 Subject: [PATCH 08/34] Template Part: Only display a missing notice in debug mode (#37404) --- packages/block-library/src/template-part/index.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/block-library/src/template-part/index.php b/packages/block-library/src/template-part/index.php index 20bc6900f83bb..d1343249ed650 100644 --- a/packages/block-library/src/template-part/index.php +++ b/packages/block-library/src/template-part/index.php @@ -103,7 +103,12 @@ function render_block_core_template_part( $attributes ) { } } - if ( is_null( $content ) && is_user_logged_in() ) { + // WP_DEBUG_DISPLAY must only be honored when WP_DEBUG. This precedent + // is set in `wp_debug_mode()`. + $is_debug = defined( 'WP_DEBUG' ) && WP_DEBUG && + defined( 'WP_DEBUG_DISPLAY' ) && WP_DEBUG_DISPLAY; + + if ( is_null( $content ) && $is_debug ) { if ( ! isset( $attributes['slug'] ) ) { // If there is no slug this is a placeholder and we dont want to return any message. return; @@ -116,11 +121,6 @@ function render_block_core_template_part( $attributes ) { } if ( isset( $seen_ids[ $template_part_id ] ) ) { - // WP_DEBUG_DISPLAY must only be honored when WP_DEBUG. This precedent - // is set in `wp_debug_mode()`. - $is_debug = defined( 'WP_DEBUG' ) && WP_DEBUG && - defined( 'WP_DEBUG_DISPLAY' ) && WP_DEBUG_DISPLAY; - return $is_debug ? // translators: Visible only in the front end, this warning takes the place of a faulty block. __( '[block rendering halted]' ) : From 3044e2f35cab281083b822c51aada208f8a8f63a Mon Sep 17 00:00:00 2001 From: Jonah Tan <47470981+jonahtanjz@users.noreply.github.com> Date: Thu, 16 Dec 2021 15:53:52 +0800 Subject: [PATCH 09/34] Site Editor: Update save panel's cancel button from icon to visible text (#37310) * Update site editor save panel's cancel button to visible text * Update buttons to use Flex and FlexItem * Remove unused class from cancel button --- .../components/entities-saved-states/index.js | 24 +++++++++++-------- .../entities-saved-states/style.scss | 12 ---------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/packages/editor/src/components/entities-saved-states/index.js b/packages/editor/src/components/entities-saved-states/index.js index becb092165dda..867628dc9f41d 100644 --- a/packages/editor/src/components/entities-saved-states/index.js +++ b/packages/editor/src/components/entities-saved-states/index.js @@ -6,14 +6,13 @@ import { some, groupBy } from 'lodash'; /** * WordPress dependencies */ -import { Button } from '@wordpress/components'; +import { Button, Flex, FlexItem } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { useSelect, useDispatch } from '@wordpress/data'; import { useState, useCallback, useRef } from '@wordpress/element'; import { store as coreStore } from '@wordpress/core-data'; import { store as blockEditorStore } from '@wordpress/block-editor'; import { __experimentalUseDialog as useDialog } from '@wordpress/compose'; -import { close as closeIcon } from '@wordpress/icons'; import { store as noticesStore } from '@wordpress/notices'; /** @@ -208,8 +207,10 @@ export default function EntitiesSavedStates( { close } ) { { ...saveDialogProps } className="entities-saved-states__panel" > - <div className="entities-saved-states__panel-header"> - <Button + <Flex className="entities-saved-states__panel-header" gap={ 2 }> + <FlexItem + isBlock + as={ Button } ref={ saveButtonRef } variant="primary" disabled={ @@ -221,13 +222,16 @@ export default function EntitiesSavedStates( { close } ) { className="editor-entities-saved-states__save-button" > { __( 'Save' ) } - </Button> - <Button - icon={ closeIcon } + </FlexItem> + <FlexItem + isBlock + as={ Button } + variant="secondary" onClick={ dismissPanel } - label={ __( 'Close panel' ) } - /> - </div> + > + { __( 'Cancel' ) } + </FlexItem> + </Flex> <div className="entities-saved-states__text-prompt"> <strong>{ __( 'Are you ready to save?' ) }</strong> diff --git a/packages/editor/src/components/entities-saved-states/style.scss b/packages/editor/src/components/entities-saved-states/style.scss index e2e9203dec07b..751deeeb47241 100644 --- a/packages/editor/src/components/entities-saved-states/style.scss +++ b/packages/editor/src/components/entities-saved-states/style.scss @@ -41,18 +41,6 @@ padding-right: $grid-unit-10; height: $header-height + $border-width; border-bottom: $border-width solid $gray-300; - display: flex; - align-items: center; - align-content: space-between; - - .editor-entities-saved-states__save-button { - margin: auto; - } - - .components-button.has-icon { - position: absolute; - right: $grid-unit-10; - } } .entities-saved-states__text-prompt { From 131df0eebb042b9a2081d07c9d1227cfd444244b Mon Sep 17 00:00:00 2001 From: Dave Smith <getdavemail@gmail.com> Date: Thu, 16 Dec 2021 08:45:54 +0000 Subject: [PATCH 10/34] Show a UI warning when user does not have permission to update/edit an existing Navigation block (#37286) * Show warning when user has insufficient permision to edit the given Nav * Move warning underneath block but allow viewing * Sync warning display with entity loading * Include ref as effect dependency * Revert unintentional edit to comment placement * Show permisisons warning using global notices system * Hide delete and rename inspector items based on perms * Switch to snackbar notice * Add e2e test covering update permission notice * Try removing forward slash to see if test passes * Remove usage of uniqueId Resolves https://github.com/WordPress/gutenberg/pull/37286#discussion_r769211397 * Update error message to use clearer terminology Addressses https://github.com/WordPress/gutenberg/pull/37286#discussion_r769214084 * Fix test to match code error message string change * Move permissions selectors to existing hook * Add explaination of requirement for 403 expect for Nav Areas * Attempt to fix flaky test on CI * Remove ref as a dependency to useSelect Co-authored-by: Daniel Richards <daniel.richards@automattic.com> --- .../src/navigation/edit/index.js | 90 ++++++++++++++++--- .../src/navigation/use-navigation-menu.js | 15 ++++ .../specs/editor/blocks/navigation.test.js | 65 ++++++++++++++ 3 files changed, 157 insertions(+), 13 deletions(-) diff --git a/packages/block-library/src/navigation/edit/index.js b/packages/block-library/src/navigation/edit/index.js index 486fc44c82e1d..8fa13a841b98b 100644 --- a/packages/block-library/src/navigation/edit/index.js +++ b/packages/block-library/src/navigation/edit/index.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import noop from 'lodash'; +import { noop } from 'lodash'; /** * WordPress dependencies @@ -27,6 +27,7 @@ import { Warning, } from '@wordpress/block-editor'; import { EntityProvider, useEntityProp } from '@wordpress/core-data'; + import { useDispatch, useSelect } from '@wordpress/data'; import { PanelBody, @@ -38,6 +39,7 @@ import { Button, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import { store as noticeStore } from '@wordpress/notices'; /** * Internal dependencies @@ -103,6 +105,8 @@ function Navigation( { customPlaceholder: CustomPlaceholder = null, customAppender: CustomAppender = null, } ) { + const noticeRef = useRef(); + const { openSubmenusOnClick, overlayMenu, @@ -167,6 +171,8 @@ function Navigation( { __unstableMarkNextChangeAsNotPersistent, } = useDispatch( blockEditorStore ); + const { createWarningNotice, removeNotice } = useDispatch( noticeStore ); + const [ hasSavedUnsavedInnerBlocks, setHasSavedUnsavedInnerBlocks, @@ -189,6 +195,10 @@ function Navigation( { hasResolvedNavigationMenus, navigationMenus, navigationMenu, + canUserUpdateNavigationEntity, + hasResolvedCanUserUpdateNavigationEntity, + canUserDeleteNavigationEntity, + hasResolvedCanUserDeleteNavigationEntity, } = useNavigationMenu( ref ); const navRef = useRef(); @@ -303,6 +313,54 @@ function Navigation( { // with the snapshot from the time when ref became undefined. }, [ clientId, ref, innerBlocks ] ); + useEffect( () => { + const setPermissionsNotice = () => { + if ( noticeRef.current ) { + return; + } + + noticeRef.current = + 'block-library/core/navigation/permissions/update'; + + createWarningNotice( + __( + 'You do not have permission to edit this Menu. Any changes made will not be saved.' + ), + { + id: noticeRef.current, + type: 'snackbar', + } + ); + }; + + const removePermissionsNotice = () => { + if ( ! noticeRef.current ) { + return; + } + removeNotice( noticeRef.current ); + noticeRef.current = null; + }; + + if ( ! isSelected && ! isInnerBlockSelected ) { + removePermissionsNotice(); + } + + if ( + ( isSelected || isInnerBlockSelected ) && + hasResolvedCanUserUpdateNavigationEntity && + ! canUserUpdateNavigationEntity + ) { + setPermissionsNotice(); + } + }, [ + ref, + isEntityAvailable, + hasResolvedCanUserUpdateNavigationEntity, + canUserUpdateNavigationEntity, + isSelected, + isInnerBlockSelected, + ] ); + const startWithEmptyMenu = useCallback( () => { if ( navigationArea ) { setAreaMenu( 0 ); @@ -507,18 +565,24 @@ function Navigation( { </InspectorControls> { isEntityAvailable && ( <InspectorControls __experimentalGroup="advanced"> - <NavigationMenuNameControl /> - <NavigationMenuDeleteControl - onDelete={ () => { - if ( navigationArea ) { - setAreaMenu( 0 ); - } - setAttributes( { - ref: undefined, - } ); - setIsPlaceholderShown( true ); - } } - /> + { hasResolvedCanUserUpdateNavigationEntity && + canUserUpdateNavigationEntity && ( + <NavigationMenuNameControl /> + ) } + { hasResolvedCanUserDeleteNavigationEntity && + canUserDeleteNavigationEntity && ( + <NavigationMenuDeleteControl + onDelete={ () => { + if ( navigationArea ) { + setAreaMenu( 0 ); + } + setAttributes( { + ref: undefined, + } ); + setIsPlaceholderShown( true ); + } } + /> + ) } </InspectorControls> ) } <nav { ...blockProps }> diff --git a/packages/block-library/src/navigation/use-navigation-menu.js b/packages/block-library/src/navigation/use-navigation-menu.js index 92a7477712f35..0ce579c778e0d 100644 --- a/packages/block-library/src/navigation/use-navigation-menu.js +++ b/packages/block-library/src/navigation/use-navigation-menu.js @@ -12,6 +12,7 @@ export default function useNavigationMenu( ref ) { getEditedEntityRecord, getEntityRecords, hasFinishedResolution, + canUser, } = select( coreStore ); const navigationMenuSingleArgs = [ @@ -64,6 +65,20 @@ export default function useNavigationMenu( ref ) { ), navigationMenu, navigationMenus, + canUserUpdateNavigationEntity: ref + ? canUser( 'update', 'navigation', ref ) + : undefined, + hasResolvedCanUserUpdateNavigationEntity: hasFinishedResolution( + 'canUser', + [ 'update', 'navigation', ref ] + ), + canUserDeleteNavigationEntity: ref + ? canUser( 'delete', 'navigation', ref ) + : undefined, + hasResolvedCanUserDeleteNavigationEntity: hasFinishedResolution( + 'canUser', + [ 'delete', 'navigation', ref ] + ), }; }, [ ref ] diff --git a/packages/e2e-tests/specs/editor/blocks/navigation.test.js b/packages/e2e-tests/specs/editor/blocks/navigation.test.js index 68f851d1139fe..86aa34ab9ae33 100644 --- a/packages/e2e-tests/specs/editor/blocks/navigation.test.js +++ b/packages/e2e-tests/specs/editor/blocks/navigation.test.js @@ -17,6 +17,9 @@ import { ensureSidebarOpened, __experimentalRest as rest, publishPost, + createUser, + loginUser, + deleteUser, } from '@wordpress/e2e-test-utils'; /** @@ -111,6 +114,7 @@ const PLACEHOLDER_ACTIONS_CLASS = 'wp-block-navigation-placeholder__actions'; const PLACEHOLDER_ACTIONS_XPATH = `//*[contains(@class, '${ PLACEHOLDER_ACTIONS_CLASS }')]`; const START_EMPTY_XPATH = `${ PLACEHOLDER_ACTIONS_XPATH }//button[text()='Start empty']`; const ADD_ALL_PAGES_XPATH = `${ PLACEHOLDER_ACTIONS_XPATH }//button[text()='Add all pages']`; +const SELECT_MENU_XPATH = `${ PLACEHOLDER_ACTIONS_XPATH }//button[text()='Select menu']`; async function turnResponsivenessOn() { const blocks = await getAllBlocks(); @@ -203,6 +207,17 @@ async function getNavigationMenuRawContent() { // Disable reason - these tests are to be re-written. // eslint-disable-next-line jest/no-disabled-tests describe( 'Navigation', () => { + let username; + let contribUserPassword; + + beforeAll( async () => { + username = 'contributoruser'; + + contribUserPassword = await createUser( username, { + role: 'contributor', + } ); + } ); + beforeEach( async () => { await deleteAll( [ POSTS_ENDPOINT, @@ -223,6 +238,8 @@ describe( 'Navigation', () => { NAVIGATION_MENUS_ENDPOINT, ] ); await deleteAllClassicMenus(); + + await deleteUser( username ); } ); describe( 'placeholder', () => { @@ -766,4 +783,52 @@ describe( 'Navigation', () => { expect( await page.$x( NAV_ENTITY_SELECTOR ) ).toHaveLength( 1 ); } ); } ); + + describe( 'Permission based restrictions', () => { + it( 'shows a warning if user does not have permission to edit or update navigation menus', async () => { + await createNewPost(); + await insertBlock( 'Navigation' ); + + const startEmptyButton = await page.waitForXPath( + START_EMPTY_XPATH + ); + + // This creates an empty Navigation post type entity. + await startEmptyButton.click(); + + // Publishing the Post ensures the Navigation entity is saved. + // The Post itself is irrelevant. + await publishPost(); + + // Switch to a Contributor role user - they should not have + // permission to update Navigations. + await loginUser( username, contribUserPassword ); + + await createNewPost(); + + await insertBlock( 'Navigation' ); + + // Select the Navigation post created by the Admin early + // in the test. + const navigationPostCreatedByAdminName = 'Navigation'; + const dropdown = await page.waitForXPath( SELECT_MENU_XPATH ); + await dropdown.click(); + const theOption = await page.waitForXPath( + `//*[contains(@class, 'components-menu-item__item')][ text()="${ navigationPostCreatedByAdminName }" ]` + ); + await theOption.click(); + + // Make sure the snackbar error shows up + await page.waitForXPath( + `//*[contains(@class, 'components-snackbar__content')][ text()="You do not have permission to edit this Menu. Any changes made will not be saved." ]` + ); + + // Expect a console 403 for request to Navigation Areas for lower permisison users. + // This is because reading requires the `edit_theme_options` capability + // which the Contributor level user does not have. + // See: https://github.com/WordPress/gutenberg/blob/4cedaf0c4abb0aeac4bfd4289d63e9889efe9733/lib/class-wp-rest-block-navigation-areas-controller.php#L81-L91. + // Todo: removed once Nav Areas are removed from the Gutenberg Plugin. + expect( console ).toHaveErrored(); + } ); + } ); } ); From 163ac4cb4af4632495522e5a25709d1972e88be8 Mon Sep 17 00:00:00 2001 From: tellthemachines <tellthemachines@users.noreply.github.com> Date: Thu, 16 Dec 2021 20:23:10 +1100 Subject: [PATCH 11/34] Only add dialog role to navigation when modal is open. (#37434) --- .../src/navigation/edit/responsive-wrapper.js | 17 +++++++++------- .../block-library/src/navigation/index.php | 7 ++++--- packages/block-library/src/navigation/view.js | 20 ++++++++++++------- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/packages/block-library/src/navigation/edit/responsive-wrapper.js b/packages/block-library/src/navigation/edit/responsive-wrapper.js index b27d2b5247cc7..6c3df47b5f231 100644 --- a/packages/block-library/src/navigation/edit/responsive-wrapper.js +++ b/packages/block-library/src/navigation/edit/responsive-wrapper.js @@ -39,12 +39,20 @@ export default function ResponsiveWrapper( { const modalId = `${ id }-modal`; + const dialogProps = { + className: 'wp-block-navigation__responsive-dialog', + ...( isOpen && { + role: 'dialog', + 'aria-modal': true, + 'aria-label': __( 'Menu' ), + } ), + }; + return ( <> { ! isOpen && ( <Button aria-haspopup="true" - aria-expanded={ isOpen } aria-label={ __( 'Open menu' ) } className={ openButtonClasses } onClick={ () => onToggle( true ) } @@ -73,12 +81,7 @@ export default function ResponsiveWrapper( { className="wp-block-navigation__responsive-close" tabIndex="-1" > - <div - className="wp-block-navigation__responsive-dialog" - role="dialog" - aria-modal="true" - aria-labelledby={ `${ modalId }-title` } - > + <div { ...dialogProps }> <Button className="wp-block-navigation__responsive-container-close" aria-label={ __( 'Close menu' ) } diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php index d9eb7fbb889ae..9db34fa82ad30 100644 --- a/packages/block-library/src/navigation/index.php +++ b/packages/block-library/src/navigation/index.php @@ -528,10 +528,10 @@ function render_block_core_navigation( $attributes, $content, $block ) { ); $responsive_container_markup = sprintf( - '<button aria-expanded="false" aria-haspopup="true" aria-label="%3$s" class="%6$s" data-micromodal-trigger="modal-%1$s"><svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" role="img" aria-hidden="true" focusable="false"><rect x="4" y="7.5" width="16" height="1.5" /><rect x="4" y="15" width="16" height="1.5" /></svg></button> + '<button aria-haspopup="true" aria-label="%3$s" class="%6$s" data-micromodal-trigger="modal-%1$s"><svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" role="img" aria-hidden="true" focusable="false"><rect x="4" y="7.5" width="16" height="1.5" /><rect x="4" y="15" width="16" height="1.5" /></svg></button> <div class="%5$s" style="%7$s" id="modal-%1$s"> <div class="wp-block-navigation__responsive-close" tabindex="-1" data-micromodal-close> - <div class="wp-block-navigation__responsive-dialog" role="dialog" aria-modal="true" aria-labelledby="modal-%1$s-title" > + <div class="wp-block-navigation__responsive-dialog" aria-label="%8$s"> <button aria-label="%4$s" data-micromodal-close class="wp-block-navigation__responsive-container-close"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" role="img" aria-hidden="true" focusable="false"><path d="M13 11.8l6.1-6.3-1-1-6.1 6.2-6.1-6.2-1 1 6.1 6.3-6.5 6.7 1 1 6.5-6.6 6.5 6.6 1-1z"></path></svg></button> <div class="wp-block-navigation__responsive-container-content" id="modal-%1$s-content"> %2$s @@ -545,7 +545,8 @@ function render_block_core_navigation( $attributes, $content, $block ) { __( 'Close menu' ), // Close button label. implode( ' ', $responsive_container_classes ), implode( ' ', $open_button_classes ), - $colors['overlay_inline_styles'] + $colors['overlay_inline_styles'], + __( 'Menu' ) ); return sprintf( diff --git a/packages/block-library/src/navigation/view.js b/packages/block-library/src/navigation/view.js index e29dee94724e1..35cc50e947270 100644 --- a/packages/block-library/src/navigation/view.js +++ b/packages/block-library/src/navigation/view.js @@ -5,16 +5,22 @@ import MicroModal from 'micromodal'; // Responsive navigation toggle. function navigationToggleModal( modal ) { - const triggerButton = document.querySelector( - `button[data-micromodal-trigger="${ modal.id }"]` + const dialogContainer = document.querySelector( + `.wp-block-navigation__responsive-dialog` ); - const closeButton = modal.querySelector( 'button[data-micromodal-close]' ); - // Use aria-hidden to determine the status of the modal, as this attribute is - // managed by micromodal. + const isHidden = 'true' === modal.getAttribute( 'aria-hidden' ); - triggerButton.setAttribute( 'aria-expanded', ! isHidden ); - closeButton.setAttribute( 'aria-expanded', ! isHidden ); + modal.classList.toggle( 'has-modal-open', ! isHidden ); + dialogContainer.toggleAttribute( 'aria-modal', ! isHidden ); + + if ( isHidden ) { + dialogContainer.removeAttribute( 'role' ); + dialogContainer.removeAttribute( 'aria-modal' ); + } else { + dialogContainer.setAttribute( 'role', 'dialog' ); + dialogContainer.setAttribute( 'aria-modal', 'true' ); + } // Add a class to indicate the modal is open. const htmlElement = document.documentElement; From b3837134a6ecea321b70362b1ce2ab27ef601e96 Mon Sep 17 00:00:00 2001 From: Joen A <1204802+jasmussen@users.noreply.github.com> Date: Thu, 16 Dec 2021 12:38:11 +0100 Subject: [PATCH 12/34] Fix navigation appender. (#37447) --- packages/block-library/src/navigation/editor.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/block-library/src/navigation/editor.scss b/packages/block-library/src/navigation/editor.scss index 5b3d7f4c8671c..545075e1a7af6 100644 --- a/packages/block-library/src/navigation/editor.scss +++ b/packages/block-library/src/navigation/editor.scss @@ -100,6 +100,7 @@ .is-editing > .wp-block-navigation__submenu-container > .block-list-appender { display: block; position: static; + width: 100%; } // Hide when hovering. From 1dc4af3d55e637244774fb73f047ca853ecbd748 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <4710635+ellatrix@users.noreply.github.com> Date: Thu, 16 Dec 2021 15:30:31 +0200 Subject: [PATCH 13/34] Block editor: Fix Enter handling for nested blocks (#37453) --- docs/reference-guides/data/data-core-block-editor.md | 1 - .../block-editor/src/components/block-list/block.js | 2 +- .../src/components/block-list/block.native.js | 4 ++-- .../components/block-list/use-block-props/index.js | 4 +--- .../components/block-list/use-in-between-inserter.js | 2 +- .../block-actions-menu.native.js | 7 ++----- .../src/components/block-mover/button.js | 8 ++------ .../block-editor/src/components/block-mover/index.js | 7 ++----- .../src/components/block-mover/index.native.js | 7 ++----- .../components/block-tools/block-selection-button.js | 12 +++--------- .../components/inserter/hooks/use-insertion-point.js | 11 ++--------- .../block-editor/src/components/inserter/index.js | 4 ++-- .../src/components/inserter/index.native.js | 7 ++----- .../src/components/inserter/quick-inserter.js | 2 +- .../components/list-view/use-list-view-drop-zone.js | 2 +- .../src/components/use-on-block-drop/index.js | 5 +---- packages/block-editor/src/store/actions.js | 7 +++---- packages/block-editor/src/store/selectors.js | 4 ++-- packages/block-editor/src/store/test/selectors.js | 2 +- packages/block-library/src/group/edit.native.js | 2 +- .../src/hooks/use-widget-library-insertion-point.js | 2 +- 21 files changed, 33 insertions(+), 69 deletions(-) diff --git a/docs/reference-guides/data/data-core-block-editor.md b/docs/reference-guides/data/data-core-block-editor.md index 12361ce112f80..87d88b8396651 100644 --- a/docs/reference-guides/data/data-core-block-editor.md +++ b/docs/reference-guides/data/data-core-block-editor.md @@ -208,7 +208,6 @@ _Parameters_ - _state_ `Object`: Editor state. - _clientId_ `string`: Block client ID. -- _rootClientId_ `?string`: Optional root client ID of block list. _Returns_ diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 70debcd93a450..76dc750ae0d79 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -259,7 +259,7 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { onInsertBlocksAfter( blocks ) { const { clientId, rootClientId } = ownProps; const { getBlockIndex } = select( blockEditorStore ); - const index = getBlockIndex( clientId, rootClientId ); + const index = getBlockIndex( clientId ); insertBlocks( blocks, index + 1, rootClientId ); }, onMerge( forward ) { diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 00d6b97faff30..4095783deaf8f 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -302,7 +302,7 @@ function getWrapperProps( value, getWrapperPropsFunction ) { } export default compose( [ - withSelect( ( select, { clientId, rootClientId } ) => { + withSelect( ( select, { clientId } ) => { const { getBlockIndex, getSettings, @@ -314,7 +314,7 @@ export default compose( [ hasSelectedInnerBlock, } = select( blockEditorStore ); - const order = getBlockIndex( clientId, rootClientId ); + const order = getBlockIndex( clientId ); const isSelected = isBlockSelected( clientId ); const isInnerBlockSelected = hasSelectedInnerBlock( clientId ); const block = getBlock( clientId ); diff --git a/packages/block-editor/src/components/block-list/use-block-props/index.js b/packages/block-editor/src/components/block-list/use-block-props/index.js index 4801e52709c96..d03cb5ecf70a1 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/index.js +++ b/packages/block-editor/src/components/block-list/use-block-props/index.js @@ -75,7 +75,6 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { } = useSelect( ( select ) => { const { - getBlockRootClientId, getBlockIndex, getBlockMode, getBlockName, @@ -91,11 +90,10 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { isBlockMultiSelected( clientId ) || isAncestorMultiSelected( clientId ); const blockName = getBlockName( clientId ); - const rootClientId = getBlockRootClientId( clientId ); const blockType = getBlockType( blockName ); return { - index: getBlockIndex( clientId, rootClientId ), + index: getBlockIndex( clientId ), mode: getBlockMode( clientId ), name: blockName, blockApiVersion: blockType?.apiVersion || 1, diff --git a/packages/block-editor/src/components/block-list/use-in-between-inserter.js b/packages/block-editor/src/components/block-list/use-in-between-inserter.js index 04520bfda1ad0..ad90940a0e657 100644 --- a/packages/block-editor/src/components/block-list/use-in-between-inserter.js +++ b/packages/block-editor/src/components/block-list/use-in-between-inserter.js @@ -144,7 +144,7 @@ export function useInBetweenInserter() { return; } - const index = getBlockIndex( clientId, rootClientId ); + const index = getBlockIndex( clientId ); // Don't show the in-between inserter before the first block in // the list (preserves the original behaviour). diff --git a/packages/block-editor/src/components/block-mobile-toolbar/block-actions-menu.native.js b/packages/block-editor/src/components/block-mobile-toolbar/block-actions-menu.native.js index d67b4beba6629..364e944b76390 100644 --- a/packages/block-editor/src/components/block-mobile-toolbar/block-actions-menu.native.js +++ b/packages/block-editor/src/components/block-mobile-toolbar/block-actions-menu.native.js @@ -316,11 +316,8 @@ export default compose( const rootClientId = getBlockRootClientId( firstClientId ); const blockOrder = getBlockOrder( rootClientId ); - const firstIndex = getBlockIndex( firstClientId, rootClientId ); - const lastIndex = getBlockIndex( - last( normalizedClientIds ), - rootClientId - ); + const firstIndex = getBlockIndex( firstClientId ); + const lastIndex = getBlockIndex( last( normalizedClientIds ) ); const innerBlocks = getBlocksByClientId( clientIds ); diff --git a/packages/block-editor/src/components/block-mover/button.js b/packages/block-editor/src/components/block-mover/button.js index e6ddb05ce913b..3835a84159804 100644 --- a/packages/block-editor/src/components/block-mover/button.js +++ b/packages/block-editor/src/components/block-mover/button.js @@ -84,13 +84,9 @@ const BlockMoverButton = forwardRef( const normalizedClientIds = castArray( clientIds ); const firstClientId = first( normalizedClientIds ); const blockRootClientId = getBlockRootClientId( firstClientId ); - const firstBlockIndex = getBlockIndex( - firstClientId, - blockRootClientId - ); + const firstBlockIndex = getBlockIndex( firstClientId ); const lastBlockIndex = getBlockIndex( - last( normalizedClientIds ), - blockRootClientId + last( normalizedClientIds ) ); const blockOrder = getBlockOrder( blockRootClientId ); const block = getBlock( firstClientId ); diff --git a/packages/block-editor/src/components/block-mover/index.js b/packages/block-editor/src/components/block-mover/index.js index 3b9fd364010e4..0ad15ad185354 100644 --- a/packages/block-editor/src/components/block-mover/index.js +++ b/packages/block-editor/src/components/block-mover/index.js @@ -108,11 +108,8 @@ export default withSelect( ( select, { clientIds } ) => { const firstClientId = first( normalizedClientIds ); const block = getBlock( firstClientId ); const rootClientId = getBlockRootClientId( first( normalizedClientIds ) ); - const firstIndex = getBlockIndex( firstClientId, rootClientId ); - const lastIndex = getBlockIndex( - last( normalizedClientIds ), - rootClientId - ); + const firstIndex = getBlockIndex( firstClientId ); + const lastIndex = getBlockIndex( last( normalizedClientIds ) ); const blockOrder = getBlockOrder( rootClientId ); const isFirst = firstIndex === 0; const isLast = lastIndex === blockOrder.length - 1; diff --git a/packages/block-editor/src/components/block-mover/index.native.js b/packages/block-editor/src/components/block-mover/index.native.js index 20d1b5e4843b5..35a7503cef4a8 100644 --- a/packages/block-editor/src/components/block-mover/index.native.js +++ b/packages/block-editor/src/components/block-mover/index.native.js @@ -138,11 +138,8 @@ export default compose( const firstClientId = first( normalizedClientIds ); const rootClientId = getBlockRootClientId( firstClientId ); const blockOrder = getBlockOrder( rootClientId ); - const firstIndex = getBlockIndex( firstClientId, rootClientId ); - const lastIndex = getBlockIndex( - last( normalizedClientIds ), - rootClientId - ); + const firstIndex = getBlockIndex( firstClientId ); + const lastIndex = getBlockIndex( last( normalizedClientIds ) ); return { firstIndex, diff --git a/packages/block-editor/src/components/block-tools/block-selection-button.js b/packages/block-editor/src/components/block-tools/block-selection-button.js index c5d36b7fc1e6d..8b4b17ee1fa57 100644 --- a/packages/block-editor/src/components/block-tools/block-selection-button.js +++ b/packages/block-editor/src/components/block-tools/block-selection-button.js @@ -59,7 +59,7 @@ function BlockSelectionButton( { clientId, rootClientId, blockElement } ) { hasBlockMovingClientId, getBlockListSettings, } = select( blockEditorStore ); - const index = getBlockIndex( clientId, rootClientId ); + const index = getBlockIndex( clientId ); const { name, attributes } = getBlock( clientId ); const blockMovingMode = hasBlockMovingClientId(); return { @@ -169,14 +169,8 @@ function BlockSelectionButton( { clientId, rootClientId, blockElement } ) { if ( ( isEnter || isSpace ) && startingBlockClientId ) { const sourceRoot = getBlockRootClientId( startingBlockClientId ); const destRoot = getBlockRootClientId( selectedBlockClientId ); - const sourceBlockIndex = getBlockIndex( - startingBlockClientId, - sourceRoot - ); - let destinationBlockIndex = getBlockIndex( - selectedBlockClientId, - destRoot - ); + const sourceBlockIndex = getBlockIndex( startingBlockClientId ); + let destinationBlockIndex = getBlockIndex( selectedBlockClientId ); if ( sourceBlockIndex < destinationBlockIndex && sourceRoot === destRoot diff --git a/packages/block-editor/src/components/inserter/hooks/use-insertion-point.js b/packages/block-editor/src/components/inserter/hooks/use-insertion-point.js index 564227f158661..dd2cc30db9056 100644 --- a/packages/block-editor/src/components/inserter/hooks/use-insertion-point.js +++ b/packages/block-editor/src/components/inserter/hooks/use-insertion-point.js @@ -64,19 +64,12 @@ function useInsertionPoint( { _destinationIndex = insertionIndex; } else if ( clientId ) { // Insert after a specific client ID. - _destinationIndex = getBlockIndex( - clientId, - _destinationRootClientId - ); + _destinationIndex = getBlockIndex( clientId ); } else if ( ! isAppender && selectedBlockClientId ) { _destinationRootClientId = getBlockRootClientId( selectedBlockClientId ); - _destinationIndex = - getBlockIndex( - selectedBlockClientId, - _destinationRootClientId - ) + 1; + _destinationIndex = getBlockIndex( selectedBlockClientId ) + 1; } else { // Insert at the end of the list. _destinationIndex = getBlockOrder( _destinationRootClientId ) diff --git a/packages/block-editor/src/components/inserter/index.js b/packages/block-editor/src/components/inserter/index.js index d08ac5ff272a0..57ebe3b4793f5 100644 --- a/packages/block-editor/src/components/inserter/index.js +++ b/packages/block-editor/src/components/inserter/index.js @@ -265,7 +265,7 @@ export default compose( [ // If the clientId is defined, we insert at the position of the block. if ( clientId ) { - return getBlockIndex( clientId, rootClientId ); + return getBlockIndex( clientId ); } // If there a selected block, we insert after the selected block. @@ -275,7 +275,7 @@ export default compose( [ end && getBlockRootClientId( end ) === rootClientId ) { - return getBlockIndex( end, rootClientId ) + 1; + return getBlockIndex( end ) + 1; } // Otherwise, we insert at the end of the current rootClientId diff --git a/packages/block-editor/src/components/inserter/index.native.js b/packages/block-editor/src/components/inserter/index.native.js index 45ed8c1bfd8f9..1b1fa0f80bab2 100644 --- a/packages/block-editor/src/components/inserter/index.native.js +++ b/packages/block-editor/src/components/inserter/index.native.js @@ -343,10 +343,7 @@ export default compose( [ const destinationRootClientId = isAnyBlockSelected ? getBlockRootClientId( end ) : rootClientId; - const selectedBlockIndex = getBlockIndex( - end, - destinationRootClientId - ); + const selectedBlockIndex = getBlockIndex( end ); const endOfRootIndex = getBlockOrder( rootClientId ).length; const isSelectedUnmodifiedDefaultBlock = isAnyBlockSelected ? isUnmodifiedDefaultBlock( getBlock( end ) ) @@ -364,7 +361,7 @@ export default compose( [ // If the clientId is defined, we insert at the position of the block. if ( clientId ) { - return getBlockIndex( clientId, rootClientId ); + return getBlockIndex( clientId ); } // If there is a selected block, diff --git a/packages/block-editor/src/components/inserter/quick-inserter.js b/packages/block-editor/src/components/inserter/quick-inserter.js index 373a82355e75d..b8ae5e233416e 100644 --- a/packages/block-editor/src/components/inserter/quick-inserter.js +++ b/packages/block-editor/src/components/inserter/quick-inserter.js @@ -56,7 +56,7 @@ export default function QuickInserter( { const { getSettings, getBlockIndex, getBlockCount } = select( blockEditorStore ); - const index = getBlockIndex( clientId, rootClientId ); + const index = getBlockIndex( clientId ); return { setInserterIsOpened: getSettings() .__experimentalSetIsInserterOpened, diff --git a/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js b/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js index f7b1347acd39d..6a0baa21d09b3 100644 --- a/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js +++ b/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js @@ -226,7 +226,7 @@ export default function useListViewDropZone() { return { clientId, rootClientId, - blockIndex: getBlockIndex( clientId, rootClientId ), + blockIndex: getBlockIndex( clientId ), element: blockElement, isDraggedBlock: isBlockDrag ? draggedBlockClientIds.includes( clientId ) diff --git a/packages/block-editor/src/components/use-on-block-drop/index.js b/packages/block-editor/src/components/use-on-block-drop/index.js index 450340b122b4c..c5e1734a9b55f 100644 --- a/packages/block-editor/src/components/use-on-block-drop/index.js +++ b/packages/block-editor/src/components/use-on-block-drop/index.js @@ -95,10 +95,7 @@ export function onBlockDrop( // If the user is moving a block if ( dropType === 'block' ) { - const sourceBlockIndex = getBlockIndex( - sourceClientIds[ 0 ], - sourceRootClientId - ); + const sourceBlockIndex = getBlockIndex( sourceClientIds[ 0 ] ); // If the user is dropping to the same position, return early. if ( diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 2d0d3cc77ecdd..673fa6ae71f74 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -1169,8 +1169,7 @@ export const duplicateBlocks = ( clientIds, updateSelection = true ) => ( { const rootClientId = select.getBlockRootClientId( clientIds[ 0 ] ); const lastSelectedIndex = select.getBlockIndex( - last( castArray( clientIds ) ), - rootClientId + last( castArray( clientIds ) ) ); const clonedBlocks = blocks.map( ( block ) => __experimentalCloneSanitizedBlock( block ) @@ -1205,7 +1204,7 @@ export const insertBeforeBlock = ( clientId ) => ( { select, dispatch } ) => { return; } - const firstSelectedIndex = select.getBlockIndex( clientId, rootClientId ); + const firstSelectedIndex = select.getBlockIndex( clientId ); return dispatch.insertDefaultBlock( {}, rootClientId, firstSelectedIndex ); }; @@ -1224,7 +1223,7 @@ export const insertAfterBlock = ( clientId ) => ( { select, dispatch } ) => { return; } - const firstSelectedIndex = select.getBlockIndex( clientId, rootClientId ); + const firstSelectedIndex = select.getBlockIndex( clientId ); return dispatch.insertDefaultBlock( {}, rootClientId, diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 711a7723cf2d1..d5fcd5cbaedf0 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -892,11 +892,11 @@ export function getBlockOrder( state, rootClientId ) { * * @param {Object} state Editor state. * @param {string} clientId Block client ID. - * @param {?string} rootClientId Optional root client ID of block list. * * @return {number} Index at which block exists in order. */ -export function getBlockIndex( state, clientId, rootClientId ) { +export function getBlockIndex( state, clientId ) { + const rootClientId = getBlockRootClientId( state, clientId ); return getBlockOrder( state, rootClientId ).indexOf( clientId ); } diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js index 3563dd8c92b22..7fce6141dbea4 100644 --- a/packages/block-editor/src/store/test/selectors.js +++ b/packages/block-editor/src/store/test/selectors.js @@ -1358,7 +1358,7 @@ describe( 'selectors', () => { }, }; - expect( getBlockIndex( state, 56, '123' ) ).toBe( 1 ); + expect( getBlockIndex( state, 56 ) ).toBe( 1 ); } ); } ); diff --git a/packages/block-library/src/group/edit.native.js b/packages/block-library/src/group/edit.native.js index 23b5628cb219d..b04211b725a91 100644 --- a/packages/block-library/src/group/edit.native.js +++ b/packages/block-library/src/group/edit.native.js @@ -120,7 +120,7 @@ export default compose( [ const { innerBlocks } = block; const selectedBlockClientId = getSelectedBlockClientId(); const totalInnerBlocks = innerBlocks.length - 1; - const blockIndex = getBlockIndex( selectedBlockClientId, clientId ); + const blockIndex = getBlockIndex( selectedBlockClientId ); isLastInnerBlockSelected = totalInnerBlocks === blockIndex; } diff --git a/packages/edit-widgets/src/hooks/use-widget-library-insertion-point.js b/packages/edit-widgets/src/hooks/use-widget-library-insertion-point.js index a3af4f5d0abab..f275fe1b36886 100644 --- a/packages/edit-widgets/src/hooks/use-widget-library-insertion-point.js +++ b/packages/edit-widgets/src/hooks/use-widget-library-insertion-point.js @@ -57,7 +57,7 @@ const useWidgetLibraryInsertionPoint = () => { return { rootClientId, - insertionIndex: getBlockIndex( clientId, rootClientId ) + 1, + insertionIndex: getBlockIndex( clientId ) + 1, }; }, [ firstRootId ] From f2614c7c590085608c1a8d225b277232060c7c40 Mon Sep 17 00:00:00 2001 From: Nik Tsekouras <ntsekouras@outlook.com> Date: Thu, 16 Dec 2021 16:45:44 +0200 Subject: [PATCH 14/34] Move block patterns compatibility code to lib/compat folder (#37451) --- lib/block-patterns.php | 93 --------------------- lib/compat/wordpress-5.8/block-patterns.php | 54 ++++++++++++ lib/compat/wordpress-5.9/block-patterns.php | 60 +++++++++++++ lib/load.php | 2 + 4 files changed, 116 insertions(+), 93 deletions(-) create mode 100644 lib/compat/wordpress-5.8/block-patterns.php create mode 100644 lib/compat/wordpress-5.9/block-patterns.php diff --git a/lib/block-patterns.php b/lib/block-patterns.php index 744abad233309..8ba3a7efd00d0 100644 --- a/lib/block-patterns.php +++ b/lib/block-patterns.php @@ -199,84 +199,6 @@ function gutenberg_remove_core_patterns() { } } -/** - * Register Core's official patterns from wordpress.org/patterns. - * - * @since 5.8.0 - */ -function gutenberg_load_remote_core_patterns() { - // This is the core function that provides the same feature. - if ( function_exists( '_load_remote_block_patterns' ) ) { - return; - } - - /** - * Filter to disable remote block patterns. - * - * @since 5.8.0 - * - * @param bool $should_load_remote - */ - $should_load_remote = apply_filters( 'should_load_remote_block_patterns', true ); - - if ( $should_load_remote ) { - $request = new WP_REST_Request( 'GET', '/wp/v2/pattern-directory/patterns' ); - $core_keyword_id = 11; // 11 is the ID for "core". - $request->set_param( 'keyword', $core_keyword_id ); - $response = rest_do_request( $request ); - if ( $response->is_error() ) { - return; - } - $patterns = $response->get_data(); - - foreach ( $patterns as $settings ) { - $pattern_name = 'core/' . sanitize_title( $settings['title'] ); - register_block_pattern( $pattern_name, (array) $settings ); - } - } -} - -/** - * Register `Featured` (category) patterns from wordpress.org/patterns. - * - * @since 5.9.0 - */ -function gutenberg_load_remote_featured_patterns() { - /** - * Filter to disable remote block patterns. - * - * @since 5.8.0 - * - * @param bool $should_load_remote - */ - $should_load_remote = apply_filters( 'should_load_remote_block_patterns', true ); - - if ( ! $should_load_remote ) { - return; - } - - if ( ! WP_Block_Pattern_Categories_Registry::get_instance()->is_registered( 'featured' ) ) { - register_block_pattern_category( 'featured', array( 'label' => __( 'Featured', 'gutenberg' ) ) ); - } - $request = new WP_REST_Request( 'GET', '/wp/v2/pattern-directory/patterns' ); - $request['category'] = 26; // This is the `Featured` category id from pattern directory. - $response = rest_do_request( $request ); - if ( $response->is_error() ) { - return; - } - $patterns = $response->get_data(); - foreach ( $patterns as $pattern ) { - $pattern_name = sanitize_title( $pattern['title'] ); - $registry = WP_Block_Patterns_Registry::get_instance(); - // Some patterns might be already registerd as `core patterns with the `core` prefix. - $is_registered = $registry->is_registered( $pattern_name ) || $registry->is_registered( "core/$pattern_name" ); - if ( ! $is_registered ) { - register_block_pattern( $pattern_name, (array) $pattern ); - } - } -} - - add_action( 'init', function() { @@ -287,18 +209,3 @@ function() { gutenberg_register_gutenberg_patterns(); } ); - -add_action( - 'current_screen', - function( $current_screen ) { - if ( ! get_theme_support( 'core-block-patterns' ) ) { - return; - } - - $is_site_editor = ( function_exists( 'gutenberg_is_edit_site_page' ) && gutenberg_is_edit_site_page( $current_screen->id ) ); - if ( $current_screen->is_block_editor || $is_site_editor ) { - gutenberg_load_remote_core_patterns(); - gutenberg_load_remote_featured_patterns(); - } - } -); diff --git a/lib/compat/wordpress-5.8/block-patterns.php b/lib/compat/wordpress-5.8/block-patterns.php new file mode 100644 index 0000000000000..c5063ec1953ba --- /dev/null +++ b/lib/compat/wordpress-5.8/block-patterns.php @@ -0,0 +1,54 @@ +<?php +/** + * Block patterns functions. + * + * @package gutenberg + */ + +if ( ! function_exists( '_load_remote_block_patterns' ) ) { + /** + * Register Core's official patterns from wordpress.org/patterns. + * + * @since 5.8.0 + */ + function _load_remote_block_patterns() { + /** + * Filter to disable remote block patterns. + * + * @since 5.8.0 + * + * @param bool $should_load_remote + */ + $should_load_remote = apply_filters( 'should_load_remote_block_patterns', true ); + + if ( $should_load_remote ) { + $request = new WP_REST_Request( 'GET', '/wp/v2/pattern-directory/patterns' ); + $core_keyword_id = 11; // 11 is the ID for "core". + $request->set_param( 'keyword', $core_keyword_id ); + $response = rest_do_request( $request ); + if ( $response->is_error() ) { + return; + } + $patterns = $response->get_data(); + + foreach ( $patterns as $settings ) { + $pattern_name = 'core/' . sanitize_title( $settings['title'] ); + register_block_pattern( $pattern_name, (array) $settings ); + } + } + } + + add_action( + 'current_screen', + function( $current_screen ) { + if ( ! get_theme_support( 'core-block-patterns' ) ) { + return; + } + + $is_site_editor = ( function_exists( 'gutenberg_is_edit_site_page' ) && gutenberg_is_edit_site_page( $current_screen->id ) ); + if ( $current_screen->is_block_editor || $is_site_editor ) { + _load_remote_block_patterns(); + } + } + ); +} diff --git a/lib/compat/wordpress-5.9/block-patterns.php b/lib/compat/wordpress-5.9/block-patterns.php new file mode 100644 index 0000000000000..8cfc60260993b --- /dev/null +++ b/lib/compat/wordpress-5.9/block-patterns.php @@ -0,0 +1,60 @@ +<?php +/** + * Block patterns functions. + * + * @package gutenberg + */ + +if ( ! function_exists( '_load_remote_featured_patterns' ) ) { + /** + * Loads featured patterns from patterns directory. + */ + function _load_remote_featured_patterns() { + /** + * Filter to disable remote block patterns. + * + * @since 5.8.0 + * + * @param bool $should_load_remote + */ + $should_load_remote = apply_filters( 'should_load_remote_block_patterns', true ); + + if ( ! $should_load_remote ) { + return; + } + + if ( ! WP_Block_Pattern_Categories_Registry::get_instance()->is_registered( 'featured' ) ) { + register_block_pattern_category( 'featured', array( 'label' => __( 'Featured', 'gutenberg' ) ) ); + } + $request = new WP_REST_Request( 'GET', '/wp/v2/pattern-directory/patterns' ); + $request['category'] = 26; // This is the `Featured` category id from pattern directory. + $response = rest_do_request( $request ); + if ( $response->is_error() ) { + return; + } + $patterns = $response->get_data(); + foreach ( $patterns as $pattern ) { + $pattern_name = sanitize_title( $pattern['title'] ); + $registry = WP_Block_Patterns_Registry::get_instance(); + // Some patterns might be already registerd as `core patterns with the `core` prefix. + $is_registered = $registry->is_registered( $pattern_name ) || $registry->is_registered( "core/$pattern_name" ); + if ( ! $is_registered ) { + register_block_pattern( $pattern_name, (array) $pattern ); + } + } + } + + add_action( + 'current_screen', + function( $current_screen ) { + if ( ! get_theme_support( 'core-block-patterns' ) ) { + return; + } + + $is_site_editor = ( function_exists( 'gutenberg_is_edit_site_page' ) && gutenberg_is_edit_site_page( $current_screen->id ) ); + if ( $current_screen->is_block_editor || $is_site_editor ) { + _load_remote_featured_patterns(); + } + } + ); +} diff --git a/lib/load.php b/lib/load.php index a9c3631f029d5..7317f9322ea8d 100644 --- a/lib/load.php +++ b/lib/load.php @@ -92,8 +92,10 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/compat.php'; require __DIR__ . '/compat/wordpress-5.8/index.php'; require __DIR__ . '/compat/wordpress-5.8/utils.php'; +require __DIR__ . '/compat/wordpress-5.8/block-patterns.php'; require __DIR__ . '/compat/wordpress-5.8.1/index.php'; require __DIR__ . '/compat/wordpress-5.9/blocks.php'; +require __DIR__ . '/compat/wordpress-5.9/block-patterns.php'; require __DIR__ . '/compat/wordpress-5.9/block-template-utils.php'; require __DIR__ . '/compat/wordpress-5.9/default-editor-styles.php'; require __DIR__ . '/compat/wordpress-5.9/register-global-styles-cpt.php'; From 04072518e9563923c2e6770dc33fb367ab11c1fd Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Thu, 16 Dec 2021 07:21:29 -0800 Subject: [PATCH 15/34] Fix blank site editor when theme name contains a period (#37167) * Add \. as valid character in rest route regex * Fix regex for stylesheet vs. id * Add slash to regex to support subdir themes * Include subdir in regex * Update regex to match core trac update * Sync route regex for theme directory characters Sync the updates in core from Trac https://core.trac.wordpress.org/changeset/52376 that support theme directories with special characters. Confirm site editor loads properly. * Update global styles phpunit test from trac ticket. The core ticket added additional fixtures and tests that are not in the plugin. So those are not being copied over. Just updating the existing test and adding one for /themes endpoint. --- ...utenberg-rest-global-styles-controller.php | 23 +++++++++++++++---- ...ss-gutenberg-rest-templates-controller.php | 5 +++- ...erg-rest-global-styles-controller-test.php | 11 ++++++++- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/lib/compat/wordpress-5.9/class-gutenberg-rest-global-styles-controller.php b/lib/compat/wordpress-5.9/class-gutenberg-rest-global-styles-controller.php index ed255143cbe61..67b48ed402b69 100644 --- a/lib/compat/wordpress-5.9/class-gutenberg-rest-global-styles-controller.php +++ b/lib/compat/wordpress-5.9/class-gutenberg-rest-global-styles-controller.php @@ -27,7 +27,7 @@ public function register_routes() { // List themes global styles. register_rest_route( $this->namespace, - '/' . $this->rest_base . '/themes/(?P<stylesheet>[^.\/]+(?:\/[^.\/]+)?)', + '/' . $this->rest_base . '/themes/(?P<stylesheet>[\/\s%\w\.\(\)\[\]\@_\-]+)', array( array( 'methods' => WP_REST_Server::READABLE, @@ -46,7 +46,7 @@ public function register_routes() { // Lists/updates a single global style variation based on the given id. register_rest_route( $this->namespace, - '/' . $this->rest_base . '/(?P<id>[\/\w-]+)', + '/' . $this->rest_base . '/(?P<id>[\/\s%\w\.\(\)\[\]\@_\-]+)', array( array( 'methods' => WP_REST_Server::READABLE, @@ -54,8 +54,9 @@ public function register_routes() { 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( 'id' => array( - 'description' => __( 'The id of the global style variation', 'gutenberg' ), - 'type' => 'string', + 'description' => __( 'The id of the global style variation', 'gutenberg' ), + 'type' => 'string', + 'sanitize_callback' => array( $this, '_sanitize_global_styles_callback' ), ), ), ), @@ -70,6 +71,20 @@ public function register_routes() { ); } + /** + * Sanitize the global styles ID or stylesheet to decode endpoint. + * For example, `wp/v2/global-styles/templatetwentytwo%200.4.0` + * would be decoded to `templatetwentytwo 0.4.0`. + * + * @since 5.9.0 + * + * @param string $id_or_stylesheet Global styles ID or stylesheet. + * @return string Sanitized global styles ID or stylesheet. + */ + public function _sanitize_global_styles_callback( $id_or_stylesheet ) { + return urldecode( $id_or_stylesheet ); + } + /** * Checks if the user has permissions to make the request. * diff --git a/lib/compat/wordpress-5.9/class-gutenberg-rest-templates-controller.php b/lib/compat/wordpress-5.9/class-gutenberg-rest-templates-controller.php index d909ffd252625..21b2a4d3a0ded 100644 --- a/lib/compat/wordpress-5.9/class-gutenberg-rest-templates-controller.php +++ b/lib/compat/wordpress-5.9/class-gutenberg-rest-templates-controller.php @@ -59,7 +59,7 @@ public function register_routes() { // Lists/updates a single template based on the given id. register_rest_route( $this->namespace, - '/' . $this->rest_base . '/(?P<id>[\/\w-]+)', + '/' . $this->rest_base . '/(?P<id>[\/\s%\w\.\(\)\[\]\@_\-]+)', array( array( 'methods' => WP_REST_Server::READABLE, @@ -131,7 +131,10 @@ protected function permissions_check() { * @return string Sanitized template ID. */ public function _sanitize_template_id( $id ) { + // Decode empty space. $last_slash_pos = strrpos( $id, '/' ); + $id = urldecode( $id ); + if ( false === $last_slash_pos ) { return $id; } diff --git a/phpunit/class-gutenberg-rest-global-styles-controller-test.php b/phpunit/class-gutenberg-rest-global-styles-controller-test.php index de782a1657f65..fa5687af9ad92 100644 --- a/phpunit/class-gutenberg-rest-global-styles-controller-test.php +++ b/phpunit/class-gutenberg-rest-global-styles-controller-test.php @@ -56,7 +56,16 @@ public static function wpSetupBeforeClass( $factory ) { public function test_register_routes() { $routes = rest_get_server()->get_routes(); - $this->assertArrayHasKey( '/wp/v2/global-styles/(?P<id>[\/\w-]+)', $routes ); + $this->assertArrayHasKey( + '/wp/v2/global-styles/(?P<id>[\/\s%\w\.\(\)\[\]\@_\-]+)', + $routes, + 'Single global style based on the given ID route does not exist' + ); + $this->assertArrayHasKey( + '/wp/v2/global-styles/themes/(?P<stylesheet>[\/\s%\w\.\(\)\[\]\@_\-]+)', + $routes, + 'Theme global styles route does not exist' + ); } public function test_context_param() { From 0133258eea5d4a4ba26ce76392fcb8c841642894 Mon Sep 17 00:00:00 2001 From: Joen A <1204802+jasmussen@users.noreply.github.com> Date: Thu, 16 Dec 2021 17:05:45 +0100 Subject: [PATCH 16/34] Navigation: Fix page list issues in overlay (#37444) * Fix padding for page list in overlay menu. * Fix vertical stacking of menu containers. --- .../block-library/src/navigation/style.scss | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/block-library/src/navigation/style.scss b/packages/block-library/src/navigation/style.scss index 0e800d2a075ba..e55336ec67599 100644 --- a/packages/block-library/src/navigation/style.scss +++ b/packages/block-library/src/navigation/style.scss @@ -432,6 +432,8 @@ button.wp-block-navigation-item__content { // as a column. display: flex; flex-direction: column; + flex-wrap: nowrap; + // Inherit alignment settings from container. align-items: var(--layout-justification-setting, inherit); @@ -460,20 +462,19 @@ button.wp-block-navigation-item__content { // Position and style. position: static; border: none; - padding-left: 32px; - padding-right: 32px; + padding-left: 2em; + padding-right: 2em; } // Space unfolded items using gap and padding for submenus. .wp-block-navigation__submenu-container, .wp-block-navigation__container { gap: var(--wp--style--block-gap, 2em); + } - // Apply top padding to nested submenus. - .wp-block-navigation__submenu-container, - .wp-block-navigation__container { - padding-top: var(--wp--style--block-gap, 2em); - } + // Apply top padding to nested submenus. + .wp-block-navigation__submenu-container { + padding-top: var(--wp--style--block-gap, 2em); } // A default padding is added to submenu items. It's not appropriate inside the modal. @@ -581,14 +582,12 @@ button.wp-block-navigation-item__content { // The menu adds wrapping containers. .wp-block-navigation__responsive-close { width: 100%; - height: 100%; } .is-menu-open .wp-block-navigation__responsive-close, .is-menu-open .wp-block-navigation__responsive-dialog, .is-menu-open .wp-block-navigation__responsive-container-content { box-sizing: border-box; - height: 100%; } .wp-block-navigation__responsive-dialog { From c659f85915c2de2af86fe24740b36377e635c2b4 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@automattic.com> Date: Thu, 16 Dec 2021 16:43:21 +0000 Subject: [PATCH 17/34] Update: Use subtitle styles for the palette names. (#37460) --- packages/components/src/palette-edit/styles.js | 2 -- .../components/global-styles/gradients-palette-panel.js | 8 ++++---- .../edit-site/src/components/global-styles/style.scss | 8 -------- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/packages/components/src/palette-edit/styles.js b/packages/components/src/palette-edit/styles.js index a04991da03a68..1a04a5a38b729 100644 --- a/packages/components/src/palette-edit/styles.js +++ b/packages/components/src/palette-edit/styles.js @@ -57,9 +57,7 @@ export const NameContainer = styled.div` export const PaletteHeading = styled( Heading )` text-transform: uppercase; line-height: ${ space( 6 ) }; - font-weight: 500; &&& { - font-size: 11px; margin-bottom: 0; } `; diff --git a/packages/edit-site/src/components/global-styles/gradients-palette-panel.js b/packages/edit-site/src/components/global-styles/gradients-palette-panel.js index 117f2efce16ff..4aa3e3b37403d 100644 --- a/packages/edit-site/src/components/global-styles/gradients-palette-panel.js +++ b/packages/edit-site/src/components/global-styles/gradients-palette-panel.js @@ -9,8 +9,8 @@ import { noop } from 'lodash'; import { __experimentalVStack as VStack, __experimentalPaletteEdit as PaletteEdit, + __experimentalSpacer as Spacer, DuotonePicker, - __experimentalHeading as Heading, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; @@ -18,6 +18,7 @@ import { __ } from '@wordpress/i18n'; * Internal dependencies */ import { useSetting } from './hooks'; +import Subtitle from './subtitle'; export default function GradientPalettePanel( { name } ) { const [ themeGradients, setThemeGradients ] = useSetting( @@ -83,9 +84,8 @@ export default function GradientPalettePanel( { name } ) { slugPrefix="custom-" /> <div> - <Heading className="edit-site-global-styles-gradient-palette-panel__duotone-heading"> - { __( 'Duotone' ) } - </Heading> + <Subtitle>{ __( 'Duotone' ) }</Subtitle> + <Spacer margin={ 3 } /> <DuotonePicker duotonePalette={ duotonePalette } disableCustomDuotone={ true } diff --git a/packages/edit-site/src/components/global-styles/style.scss b/packages/edit-site/src/components/global-styles/style.scss index a707e2b0a8ccc..11214b3c55891 100644 --- a/packages/edit-site/src/components/global-styles/style.scss +++ b/packages/edit-site/src/components/global-styles/style.scss @@ -59,14 +59,6 @@ } } -h2.edit-site-global-styles-gradient-palette-panel__duotone-heading.components-heading { - text-transform: uppercase; - line-height: $grid-unit-30; - font-weight: 500; - font-size: 11px; - margin-bottom: $grid-unit-10; -} - .edit-site-screen-text-color__control, .edit-site-screen-link-color__control, .edit-site-screen-background-color__control { From 7cbba2646a522738fd95894db8240e4d3d5ac1d3 Mon Sep 17 00:00:00 2001 From: Staci Cooper <63313398+stacimc@users.noreply.github.com> Date: Thu, 16 Dec 2021 10:42:36 -0800 Subject: [PATCH 18/34] Multi-entity saving: Allow publishing a post while not saving changes to non-post entities (#37383) * Check for existence of setEntititesSavedStatesCallback prop * Clean up props destructuring * Update packages/editor/src/components/post-publish-button/index.js Co-authored-by: Ramon <ramonjd@users.noreply.github.com> Co-authored-by: Ramon <ramonjd@users.noreply.github.com> --- .../src/components/post-publish-button/index.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/editor/src/components/post-publish-button/index.js b/packages/editor/src/components/post-publish-button/index.js index 39e2030040350..d17cf77dd95eb 100644 --- a/packages/editor/src/components/post-publish-button/index.js +++ b/packages/editor/src/components/post-publish-button/index.js @@ -41,19 +41,28 @@ export class PostPublishButton extends Component { createOnClick( callback ) { return ( ...args ) => { - const { hasNonPostEntityChanges } = this.props; - if ( hasNonPostEntityChanges ) { + const { + hasNonPostEntityChanges, + setEntitiesSavedStatesCallback, + } = this.props; + // If a post with non-post entities is published, but the user + // elects to not save changes to the non-post entities, those + // entities will still be dirty when the Publish button is clicked. + // We also need to check that the `setEntitiesSavedStatesCallback` + // prop was passed. See https://github.com/WordPress/gutenberg/pull/37383 + if ( hasNonPostEntityChanges && setEntitiesSavedStatesCallback ) { // The modal for multiple entity saving will open, // hold the callback for saving/publishing the post // so that we can call it if the post entity is checked. this.setState( { entitiesSavedStatesCallback: () => callback( ...args ), } ); + // Open the save panel by setting its callback. // To set a function on the useState hook, we must set it // with another function (() => myFunction). Passing the // function on its own will cause an error when called. - this.props.setEntitiesSavedStatesCallback( + setEntitiesSavedStatesCallback( () => this.closeEntitiesSavedStates ); return noop; From 7ed9cbc21de826f13f0e82fecb1ff2a2ffc7eeaf Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@automattic.com> Date: Thu, 16 Dec 2021 23:05:34 +0000 Subject: [PATCH 19/34] Update: Block top level useSetting paths. (#37428) --- .../block-editor/src/components/use-setting/index.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/block-editor/src/components/use-setting/index.js b/packages/block-editor/src/components/use-setting/index.js index 00cda78b2e8b3..d96546dae73f4 100644 --- a/packages/block-editor/src/components/use-setting/index.js +++ b/packages/block-editor/src/components/use-setting/index.js @@ -15,6 +15,8 @@ import { __EXPERIMENTAL_PATHS_WITH_MERGE as PATHS_WITH_MERGE } from '@wordpress/ import { useBlockEditContext } from '../block-edit'; import { store as blockEditorStore } from '../../store'; +const blockedPaths = [ 'color', 'border', 'typography', 'spacing' ]; + const deprecatedFlags = { 'color.palette': ( settings ) => settings.colors === undefined ? undefined : settings.colors, @@ -104,6 +106,13 @@ export default function useSetting( path ) { const setting = useSelect( ( select ) => { + if ( blockedPaths.includes( path ) ) { + // eslint-disable-next-line no-console + console.warn( + 'Top level useSetting paths are disabled. Please use a subpath to query the information needed.' + ); + return undefined; + } const settings = select( blockEditorStore ).getSettings(); // 1 - Use __experimental features, if available. From 3ffbbd0c58bcddec7dc2ae8e9d55c7a01811919a Mon Sep 17 00:00:00 2001 From: Glen Davies <glendaviesnz@users.noreply.github.com> Date: Fri, 17 Dec 2021 12:53:25 +1300 Subject: [PATCH 20/34] Switch to using a $eval to fill user creation fields as page.type sometimes fails to enter full details (#37469) Co-authored-by: Glen Davies <glen.davies@a8c.com> --- packages/e2e-test-utils/src/create-user.js | 24 ++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/e2e-test-utils/src/create-user.js b/packages/e2e-test-utils/src/create-user.js index 5b6b2a52ae7b0..e26ebbc8dc92a 100644 --- a/packages/e2e-test-utils/src/create-user.js +++ b/packages/e2e-test-utils/src/create-user.js @@ -28,13 +28,29 @@ export async function createUser( await switchUserToAdmin(); await visitAdminPage( 'user-new.php' ); await page.waitForSelector( '#user_login', { visible: true } ); - await page.type( '#user_login', username ); - await page.type( '#email', snakeCase( username ) + '@example.com' ); + await page.$eval( + '#user_login', + ( el, value ) => ( el.value = value ), + username + ); + await page.$eval( + '#email', + ( el, value ) => ( el.value = value ), + snakeCase( username ) + '@example.com' + ); if ( firstName ) { - await page.type( '#first_name', firstName ); + await page.$eval( + '#first_name', + ( el, value ) => ( el.value = value ), + firstName + ); } if ( lastName ) { - await page.type( '#last_name', lastName ); + await page.$eval( + '#last_name', + ( el, value ) => ( el.value = value ), + lastName + ); } if ( role ) { await page.select( '#role', role ); From 1534439e8c4e8300d5957dffb287be7af475254b Mon Sep 17 00:00:00 2001 From: Nick Diego <ndiego@outermost.co> Date: Thu, 16 Dec 2021 19:39:59 -0500 Subject: [PATCH 21/34] Fix Site Logo block alignment issues (#36627) * Remove duplicate alignment class. * Copy editor aligncenter styling from editor to frontend. * Ensure default width is set properly and remove conflicting CSS. * Revert some CSS changes after testing. * Remove unnecessary text alignment CSS. * Set the Site Logo width to default when reset. * Remove unnecessary align attribute. --- packages/block-library/src/site-logo/block.json | 3 --- packages/block-library/src/site-logo/edit.js | 11 ++++++----- packages/block-library/src/site-logo/editor.scss | 16 +--------------- packages/block-library/src/site-logo/index.php | 4 ---- packages/block-library/src/site-logo/style.scss | 4 +++- 5 files changed, 10 insertions(+), 28 deletions(-) diff --git a/packages/block-library/src/site-logo/block.json b/packages/block-library/src/site-logo/block.json index 798917ff7cb87..7dc85b04f3ccd 100644 --- a/packages/block-library/src/site-logo/block.json +++ b/packages/block-library/src/site-logo/block.json @@ -7,9 +7,6 @@ "description": "Display a graphic to represent this site. Update the block, and the changes apply everywhere it’s used. This is different than the site icon, which is the smaller image visible in your dashboard, browser tabs, etc used to help others recognize this site.", "textdomain": "default", "attributes": { - "align": { - "type": "string" - }, "width": { "type": "number" }, diff --git a/packages/block-library/src/site-logo/edit.js b/packages/block-library/src/site-logo/edit.js index 6719a4244ab8b..1cc4399a618c7 100644 --- a/packages/block-library/src/site-logo/edit.js +++ b/packages/block-library/src/site-logo/edit.js @@ -142,7 +142,11 @@ const SiteLogo = ( { return <div style={ { width, height } }>{ imgWrapper }</div>; } - const currentWidth = width || imageWidthWithinContainer; + // Set the default width to a responsible size. + // Note that this width is also set in the attached frontend CSS file. + const defaultWidth = 120; + + const currentWidth = width || defaultWidth; const ratio = naturalWidth / naturalHeight; const currentHeight = currentWidth / ratio; const minWidth = naturalWidth < naturalHeight ? MIN_SIZE : MIN_SIZE * ratio; @@ -160,10 +164,6 @@ const SiteLogo = ( { // becomes available. const maxWidthBuffer = maxWidth * 2.5; - // Set the default width to a responsible size. - // Note that this width is also set in the attached CSS file. - const defaultWidth = 120; - let showRightHandle = false; let showLeftHandle = false; @@ -383,6 +383,7 @@ export default function LogoEdit( { const onRemoveLogo = () => { setLogo( null ); setLogoUrl( undefined ); + setAttributes( { width: undefined } ); }; const { createErrorNotice } = useDispatch( noticesStore ); diff --git a/packages/block-library/src/site-logo/editor.scss b/packages/block-library/src/site-logo/editor.scss index af1a9fe59f2bd..ae655c9140156 100644 --- a/packages/block-library/src/site-logo/editor.scss +++ b/packages/block-library/src/site-logo/editor.scss @@ -1,7 +1,7 @@ .wp-block[data-align="center"] > .wp-block-site-logo { + display: table; margin-left: auto; margin-right: auto; - text-align: center; } .wp-block-site-logo { @@ -10,20 +10,6 @@ pointer-events: none; } - &:not(.is-default-size) { - display: table; - } - - // Provide a sane starting point for the size. - &.is-default-size { - width: 120px; - - img { - height: auto; - width: 100%; - } - } - .custom-logo-link { cursor: inherit; diff --git a/packages/block-library/src/site-logo/index.php b/packages/block-library/src/site-logo/index.php index c227781eecf7d..52cfbe161b5bf 100644 --- a/packages/block-library/src/site-logo/index.php +++ b/packages/block-library/src/site-logo/index.php @@ -48,10 +48,6 @@ function render_block_core_site_logo( $attributes ) { $classnames[] = $attributes['className']; } - if ( ! empty( $attributes['align'] ) && in_array( $attributes['align'], array( 'center', 'left', 'right' ), true ) ) { - $classnames[] = "align{$attributes['align']}"; - } - if ( empty( $attributes['width'] ) ) { $classnames[] = 'is-default-size'; } diff --git a/packages/block-library/src/site-logo/style.scss b/packages/block-library/src/site-logo/style.scss index 3ae7d8c895172..07a8f66906a2b 100644 --- a/packages/block-library/src/site-logo/style.scss +++ b/packages/block-library/src/site-logo/style.scss @@ -18,7 +18,9 @@ } &.aligncenter { - display: table; + margin-left: auto; + margin-right: auto; + text-align: center; } // Style variations From dddcb17e110a72d9e4c399f1d02641680ac147c4 Mon Sep 17 00:00:00 2001 From: Glen Davies <glendaviesnz@users.noreply.github.com> Date: Fri, 17 Dec 2021 13:44:25 +1300 Subject: [PATCH 22/34] Template Editor: Change color of welcome dialog close icon so it is visible against black background (#37435) Co-authored-by: Glen Davies <glen.davies@a8c.com> --- .../edit-post/src/components/welcome-guide/style.scss | 9 ++++++++- .../edit-post/src/components/welcome-guide/template.js | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/edit-post/src/components/welcome-guide/style.scss b/packages/edit-post/src/components/welcome-guide/style.scss index 619eddb56c042..1b0ca901fa848 100644 --- a/packages/edit-post/src/components/welcome-guide/style.scss +++ b/packages/edit-post/src/components/welcome-guide/style.scss @@ -1,4 +1,5 @@ -.edit-post-welcome-guide { +.edit-post-welcome-guide, +.edit-template-welcome-guide { width: 312px; &__image { @@ -31,3 +32,9 @@ vertical-align: text-top; } } + +.edit-template-welcome-guide { + .components-button svg { + fill: $white; + } +} diff --git a/packages/edit-post/src/components/welcome-guide/template.js b/packages/edit-post/src/components/welcome-guide/template.js index 460515a9d24e5..bc531f8033e5f 100644 --- a/packages/edit-post/src/components/welcome-guide/template.js +++ b/packages/edit-post/src/components/welcome-guide/template.js @@ -16,7 +16,7 @@ export default function WelcomeGuideTemplate() { return ( <Guide - className="edit-post-welcome-guide" + className="edit-template-welcome-guide" contentLabel={ __( 'Welcome to the template editor' ) } finishButtonText={ __( 'Get started' ) } onFinish={ () => toggleFeature( 'welcomeGuideTemplate' ) } From 00bf8f69e32485ba451898227a452999c3a3a1c9 Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.richards@automattic.com> Date: Fri, 17 Dec 2021 08:45:24 +0800 Subject: [PATCH 23/34] Ensure the overlay menu works when inserting navigation block patterns and that inner blocks are retained (#37358) * Show overlay menu for unsaved inner blocks too * Fix uncontrolled inner blocks being deleted * Add e2e test for converting markup inner blocks to a navigation post * Change useNavigationMenu back to trunk * Re-enable single entity test * Attempt to sync block editor store with controlled state Addresses https://github.com/WordPress/gutenberg/pull/37358#discussion_r769699131 * Add e2e test for lingering uncontrolled blocks issue Co-authored-by: Dave Smith <getdavemail@gmail.com> --- .../src/navigation/edit/index.js | 106 +++++--- .../navigation/edit/unsaved-inner-blocks.js | 30 +-- .../__snapshots__/navigation.test.js.snap | 2 + .../specs/editor/blocks/navigation.test.js | 247 +++++++++++------- 4 files changed, 244 insertions(+), 141 deletions(-) diff --git a/packages/block-library/src/navigation/edit/index.js b/packages/block-library/src/navigation/edit/index.js index 8fa13a841b98b..82355fa98972d 100644 --- a/packages/block-library/src/navigation/edit/index.js +++ b/packages/block-library/src/navigation/edit/index.js @@ -55,6 +55,8 @@ import NavigationMenuNameControl from './navigation-menu-name-control'; import UnsavedInnerBlocks from './unsaved-inner-blocks'; import NavigationMenuDeleteControl from './navigation-menu-delete-control'; +const EMPTY_ARRAY = []; + function getComputedStyle( node ) { return node.ownerDocument.defaultView.getComputedStyle( node ); } @@ -146,25 +148,44 @@ function Navigation( { `navigationMenu/${ ref }` ); - const { innerBlocks, isInnerBlockSelected, hasSubmenus } = useSelect( + const { + hasUncontrolledInnerBlocks, + uncontrolledInnerBlocks, + controlledInnerBlocks, + isInnerBlockSelected, + hasSubmenus, + } = useSelect( ( select ) => { - const { getBlocks, hasSelectedInnerBlock } = select( + const { getBlock, getBlocks, hasSelectedInnerBlock } = select( blockEditorStore ); - const blocks = getBlocks( clientId ); - const firstSubmenu = !! blocks.find( - ( block ) => block.name === 'core/navigation-submenu' - ); + + // This relies on the fact that `getBlock` won't return controlled + // inner blocks, while `getBlocks` does. It might be more stable to + // introduce a selector like `getUncontrolledInnerBlocks`, just in + // case `getBlock` is fixed. + const _uncontrolledInnerBlocks = getBlock( clientId ).innerBlocks; + const _hasUncontrolledInnerBlocks = + _uncontrolledInnerBlocks?.length; + const _controlledInnerBlocks = _hasUncontrolledInnerBlocks + ? EMPTY_ARRAY + : getBlocks( clientId ); + const innerBlocks = _hasUncontrolledInnerBlocks + ? _uncontrolledInnerBlocks + : _controlledInnerBlocks; return { - hasSubmenus: firstSubmenu, - innerBlocks: blocks, + hasSubmenus: !! innerBlocks.find( + ( block ) => block.name === 'core/navigation-submenu' + ), + hasUncontrolledInnerBlocks: _hasUncontrolledInnerBlocks, + uncontrolledInnerBlocks: _uncontrolledInnerBlocks, + controlledInnerBlocks: _controlledInnerBlocks, isInnerBlockSelected: hasSelectedInnerBlock( clientId, true ), }; }, [ clientId ] ); - const hasExistingNavItems = !! innerBlocks.length; const { replaceInnerBlocks, selectBlock, @@ -178,10 +199,10 @@ function Navigation( { setHasSavedUnsavedInnerBlocks, ] = useState( false ); - const isWithinUnassignedArea = navigationArea && ! ref; + const isWithinUnassignedArea = !! navigationArea && ! ref; const [ isPlaceholderShown, setIsPlaceholderShown ] = useState( - ! hasExistingNavItems || isWithinUnassignedArea + ! hasUncontrolledInnerBlocks || isWithinUnassignedArea ); const [ isResponsiveMenuOpen, setResponsiveMenuVisibility ] = useState( @@ -303,15 +324,17 @@ function Navigation( { setIsPlaceholderShown( ! isEntityAvailable ); }, [ isEntityAvailable ] ); - // If the ref no longer exists the reset the inner blocks - // to provide a clean slate. + // If the ref no longer exists the reset the inner blocks to provide a + // clean slate. useEffect( () => { - if ( ref === undefined && innerBlocks.length > 0 ) { + if ( + ! hasUncontrolledInnerBlocks && + controlledInnerBlocks?.length > 0 && + ref === undefined + ) { replaceInnerBlocks( clientId, [] ); } - // innerBlocks are intentionally not listed as deps. This function is only concerned - // with the snapshot from the time when ref became undefined. - }, [ clientId, ref, innerBlocks ] ); + }, [ clientId, ref, hasUncontrolledInnerBlocks, controlledInnerBlocks ] ); useEffect( () => { const setPermissionsNotice = () => { @@ -377,23 +400,42 @@ function Navigation( { // Either this block was saved in the content or inserted by a pattern. // Consider this 'unsaved'. Offer an uncontrolled version of inner blocks, // that automatically saves the menu. - const hasUnsavedBlocks = - hasExistingNavItems && ! isEntityAvailable && ! isWithinUnassignedArea; + const hasUnsavedBlocks = hasUncontrolledInnerBlocks && ! isEntityAvailable; if ( hasUnsavedBlocks ) { return ( - <UnsavedInnerBlocks - blockProps={ blockProps } - blocks={ innerBlocks } - clientId={ clientId } - navigationMenus={ navigationMenus } - hasSelection={ isSelected || isInnerBlockSelected } - hasSavedUnsavedInnerBlocks={ hasSavedUnsavedInnerBlocks } - onSave={ ( post ) => { - setHasSavedUnsavedInnerBlocks( true ); - // Switch to using the wp_navigation entity. - setRef( post.id ); - } } - /> + <nav { ...blockProps }> + <ResponsiveWrapper + id={ clientId } + onToggle={ setResponsiveMenuVisibility } + isOpen={ isResponsiveMenuOpen } + isResponsive={ 'never' !== overlayMenu } + isHiddenByDefault={ 'always' === overlayMenu } + classNames={ overlayClassnames } + styles={ overlayStyles } + > + <UnsavedInnerBlocks + blockProps={ blockProps } + blocks={ uncontrolledInnerBlocks } + clientId={ clientId } + navigationMenus={ navigationMenus } + hasSelection={ isSelected || isInnerBlockSelected } + hasSavedUnsavedInnerBlocks={ + hasSavedUnsavedInnerBlocks + } + onSave={ ( post ) => { + // Set some state used as a guard to prevent the creation of multiple posts. + setHasSavedUnsavedInnerBlocks( true ); + // replaceInnerBlocks is required to ensure the block editor store is sync'd + // to be aware that there are now no inner blocks (as blocks moved to entity). + // This should probably happen automatically with useBlockSync + // but there appears to be a bug. + replaceInnerBlocks( clientId, [] ); + // Switch to using the wp_navigation entity. + setRef( post.id ); + } } + /> + </ResponsiveWrapper> + </nav> ); } diff --git a/packages/block-library/src/navigation/edit/unsaved-inner-blocks.js b/packages/block-library/src/navigation/edit/unsaved-inner-blocks.js index 76b641d7ec072..42a57a0ec81c7 100644 --- a/packages/block-library/src/navigation/edit/unsaved-inner-blocks.js +++ b/packages/block-library/src/navigation/edit/unsaved-inner-blocks.js @@ -125,22 +125,18 @@ export default function UnsavedInnerBlocks( { ] ); return ( - <> - <nav { ...blockProps }> - <div className="wp-block-navigation__unsaved-changes"> - <Disabled - className={ classnames( - 'wp-block-navigation__unsaved-changes-overlay', - { - 'is-saving': hasSelection, - } - ) } - > - <div { ...innerBlocksProps } /> - </Disabled> - { hasSelection && <Spinner /> } - </div> - </nav> - </> + <div className="wp-block-navigation__unsaved-changes"> + <Disabled + className={ classnames( + 'wp-block-navigation__unsaved-changes-overlay', + { + 'is-saving': hasSelection, + } + ) } + > + <div { ...innerBlocksProps } /> + </Disabled> + { hasSelection && <Spinner /> } + </div> ); } diff --git a/packages/e2e-tests/specs/editor/blocks/__snapshots__/navigation.test.js.snap b/packages/e2e-tests/specs/editor/blocks/__snapshots__/navigation.test.js.snap index 85c81cc365ee1..7b910d141d8ed 100644 --- a/packages/e2e-tests/specs/editor/blocks/__snapshots__/navigation.test.js.snap +++ b/packages/e2e-tests/specs/editor/blocks/__snapshots__/navigation.test.js.snap @@ -43,3 +43,5 @@ exports[`Navigation placeholder allows a navigation block to be created from exi exports[`Navigation placeholder allows a navigation block to be created using existing pages 1`] = `"<!-- wp:page-list /-->"`; exports[`Navigation placeholder creates an empty navigation block when the selected existing menu is also empty 1`] = `""`; + +exports[`Navigation supports navigation blocks that have inner blocks within their markup and converts them to wp_navigation posts 1`] = `"<!-- wp:page-list /-->"`; diff --git a/packages/e2e-tests/specs/editor/blocks/navigation.test.js b/packages/e2e-tests/specs/editor/blocks/navigation.test.js index 86aa34ab9ae33..12d8cb841e874 100644 --- a/packages/e2e-tests/specs/editor/blocks/navigation.test.js +++ b/packages/e2e-tests/specs/editor/blocks/navigation.test.js @@ -2,6 +2,8 @@ * WordPress dependencies */ import { + clickButton, + clickOnMoreMenuItem, createJSONResponse, createNewPost, createMenu as createClassicMenu, @@ -66,7 +68,10 @@ async function updateActiveNavigationLink( { url, label, type } ) { }; if ( url ) { - await page.type( 'input[placeholder="Search or type url"]', url ); + const input = await page.waitForSelector( + 'input[placeholder="Search or type url"]' + ); + await input.type( url ); const suggestionPath = `//button[contains(@class, 'block-editor-link-control__search-item') and contains(@class, '${ typeClasses[ type ] }')]/span/span[@class='block-editor-link-control__search-item-title']/mark[text()="${ url }"]`; @@ -196,6 +201,10 @@ async function getNavigationMenuRawContent() { return navigationBlock.attributes.ref; } ); + if ( ! menuRef ) { + throw 'getNavigationMenuRawContent was unable to find a ref attribute on the first navigation block'; + } + const response = await rest( { method: 'GET', path: `/wp/v2/navigation/${ menuRef }?context=edit`, @@ -581,6 +590,151 @@ describe( 'Navigation', () => { expect( quickInserter ).toBeTruthy(); } ); + it( 'supports navigation blocks that have inner blocks within their markup and converts them to wp_navigation posts', async () => { + // Insert 'old-school' inner blocks via the code editor. + await createNewPost(); + await clickOnMoreMenuItem( 'Code editor' ); + const codeEditorInput = await page.waitForSelector( + '.editor-post-text-editor' + ); + await codeEditorInput.click(); + const markup = + '<!-- wp:navigation --><!-- wp:page-list /--><!-- /wp:navigation -->'; + await page.keyboard.type( markup ); + await clickButton( 'Exit code editor' ); + const navBlock = await page.waitForSelector( + 'nav[aria-label="Block: Navigation"]' + ); + // Select the block to convert to a wp_navigation and publish. + // The select menu button shows up when saving is complete. + await navBlock.click(); + await page.waitForSelector( 'button[aria-label="Select Menu"]' ); + await publishPost(); + + // Check that the wp_navigation post has the page list block. + expect( await getNavigationMenuRawContent() ).toMatchSnapshot(); + } ); + + describe( 'Creating and restarting', () => { + const NAV_ENTITY_SELECTOR = + '//div[@class="entities-saved-states__panel"]//label//strong[contains(text(), "Navigation")]'; + + async function populateNavWithOneItem() { + // Add a Link block first. + const appender = await page.waitForSelector( + '.wp-block-navigation .block-list-appender' + ); + await appender.click(); + // Add a link to the Link block. + await updateActiveNavigationLink( { + url: 'https://wordpress.org', + label: 'WP', + type: 'url', + } ); + } + + async function resetNavBlockToInitialState() { + const selectMenuDropdown = await page.waitForSelector( + '[aria-label="Select Menu"]' + ); + await selectMenuDropdown.click(); + const newMenuButton = await page.waitForXPath( + '//span[text()="Create new menu"]' + ); + newMenuButton.click(); + } + + it( 'does not retain uncontrolled inner blocks when creating a new entity', async () => { + await createNewPost(); + await clickOnMoreMenuItem( 'Code editor' ); + const codeEditorInput = await page.waitForSelector( + '.editor-post-text-editor' + ); + await codeEditorInput.click(); + const markup = + '<!-- wp:navigation --><!-- wp:page-list /--><!-- /wp:navigation -->'; + await page.keyboard.type( markup ); + await clickButton( 'Exit code editor' ); + const navBlock = await page.waitForSelector( + 'nav[aria-label="Block: Navigation"]' + ); + + // Select the block to convert to a wp_navigation and publish. + // The select menu button shows up when saving is complete. + await navBlock.click(); + await page.waitForSelector( 'button[aria-label="Select Menu"]' ); + + // Reset the nav block to create a new entity. + await resetNavBlockToInitialState(); + const startEmptyButton = await page.waitForXPath( + START_EMPTY_XPATH + ); + await startEmptyButton.click(); + await populateNavWithOneItem(); + + // Confirm that only the last menu entity was updated. + const publishPanelButton2 = await page.waitForSelector( + '.editor-post-publish-button__button:not([aria-disabled="true"])' + ); + await publishPanelButton2.click(); + + await page.waitForXPath( NAV_ENTITY_SELECTOR ); + expect( await page.$x( NAV_ENTITY_SELECTOR ) ).toHaveLength( 1 ); + } ); + + it( 'only update a single entity currently linked with the block', async () => { + await createNewPost(); + await insertBlock( 'Navigation' ); + + const startEmptyButton = await page.waitForXPath( + START_EMPTY_XPATH + ); + await startEmptyButton.click(); + await populateNavWithOneItem(); + + // Confirm that the menu entity was updated. + const publishPanelButton = await page.waitForSelector( + '.editor-post-publish-panel__toggle:not([aria-disabled="true"])' + ); + await publishPanelButton.click(); + + await page.waitForXPath( NAV_ENTITY_SELECTOR ); + expect( await page.$x( NAV_ENTITY_SELECTOR ) ).toHaveLength( 1 ); + + // Publish the post + const entitySaveButton = await page.waitForSelector( + '.editor-entities-saved-states__save-button' + ); + await entitySaveButton.click(); + const publishButton = await page.waitForSelector( + '.editor-post-publish-button:not([aria-disabled="true"])' + ); + await publishButton.click(); + + // A success notice should show up. + await page.waitForSelector( '.components-snackbar' ); + + // Now try inserting another Link block via the quick inserter. + await page.click( 'nav[aria-label="Block: Navigation"]' ); + + await resetNavBlockToInitialState(); + const startEmptyButton2 = await page.waitForXPath( + START_EMPTY_XPATH + ); + await startEmptyButton2.click(); + await populateNavWithOneItem(); + + // Confirm that only the last menu entity was updated. + const publishPanelButton2 = await page.waitForSelector( + '.editor-post-publish-button__button:not([aria-disabled="true"])' + ); + await publishPanelButton2.click(); + + await page.waitForXPath( NAV_ENTITY_SELECTOR ); + expect( await page.$x( NAV_ENTITY_SELECTOR ) ).toHaveLength( 1 ); + } ); + } ); + // The following tests are unstable, roughly around when https://github.com/WordPress/wordpress-develop/pull/1412 // landed. The block manually tests well, so let's skip to unblock other PRs and immediately follow up. cc @vcanales it.skip( 'loads frontend code only if the block is present', async () => { @@ -693,97 +847,6 @@ describe( 'Navigation', () => { expect( isScriptLoaded ).toBe( true ); } ); - describe.skip( 'Creating and restarting', () => { - async function populateNavWithOneItem() { - // Add a Link block first. - await page.waitForSelector( - '.wp-block-navigation .block-list-appender' - ); - await page.click( '.wp-block-navigation .block-list-appender' ); - // Add a link to the Link block. - await updateActiveNavigationLink( { - url: 'https://wordpress.org', - label: 'WP', - type: 'url', - } ); - } - - async function resetNavBlockToInitialState() { - await page.waitForSelector( '[aria-label="Select Menu"]' ); - await page.click( '[aria-label="Select Menu"]' ); - - await page.waitForXPath( '//span[text()="Create new menu"]' ); - const newMenuButton = await page.$x( - '//span[text()="Create new menu"]' - ); - newMenuButton[ 0 ].click(); - } - - it( 'only update a single entity currently linked with the block', async () => { - // Mock the response from the Pages endpoint. This is done so that the pages returned are always - // consistent and to test the feature more rigorously than the single default sample page. - // await mockPagesResponse( [ - // { - // title: 'Home', - // slug: 'home', - // }, - // { - // title: 'About', - // slug: 'about', - // }, - // { - // title: 'Contact Us', - // slug: 'contact', - // }, - // ] ); - - await insertBlock( 'Navigation' ); - const startEmptyButton = await page.waitForXPath( - START_EMPTY_XPATH - ); - await startEmptyButton.click(); - await populateNavWithOneItem(); - - // Let's confirm that the menu entity was updated. - await page.waitForSelector( - '.editor-post-publish-panel__toggle:not([aria-disabled="true"])' - ); - await page.click( '.editor-post-publish-panel__toggle' ); - - const NAV_ENTITY_SELECTOR = - '//div[@class="entities-saved-states__panel"]//label//strong[contains(text(), "Navigation")]'; - await page.waitForXPath( NAV_ENTITY_SELECTOR ); - expect( await page.$x( NAV_ENTITY_SELECTOR ) ).toHaveLength( 1 ); - - // Publish the post - await page.click( '.editor-entities-saved-states__save-button' ); - await page.waitForSelector( '.editor-post-publish-button' ); - await page.click( '.editor-post-publish-button' ); - - // A success notice should show up - await page.waitForSelector( '.components-snackbar' ); - - // Now try inserting another Link block via the quick inserter. - await page.focus( '.wp-block-navigation' ); - - await resetNavBlockToInitialState(); - const startEmptyButton2 = await page.waitForXPath( - START_EMPTY_XPATH - ); - await startEmptyButton2.click(); - await populateNavWithOneItem(); - - // Let's confirm that only the last menu entity was updated. - await page.waitForSelector( - '.editor-post-publish-button__button:not([aria-disabled="true"])' - ); - await page.click( '.editor-post-publish-button__button' ); - - await page.waitForXPath( NAV_ENTITY_SELECTOR ); - expect( await page.$x( NAV_ENTITY_SELECTOR ) ).toHaveLength( 1 ); - } ); - } ); - describe( 'Permission based restrictions', () => { it( 'shows a warning if user does not have permission to edit or update navigation menus', async () => { await createNewPost(); From 8b2b37e150139143fa43936118121bc676a80021 Mon Sep 17 00:00:00 2001 From: Alex Stine <alex.stine@yourtechadvisors.com> Date: Thu, 16 Dec 2021 20:00:00 -0500 Subject: [PATCH 24/34] Editor: when Toggle navigation receives state false, focus (#37265) * Site Editor: when Toggle navigation receives state false, focus the trigger for screen readers. * Add comment that points to a future PR Co-authored-by: George Mamadashvili <georgemamadashvili@gmail.com> Co-authored-by: George Mamadashvili <georgemamadashvili@gmail.com> --- .../navigation-sidebar/navigation-toggle/index.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/edit-site/src/components/navigation-sidebar/navigation-toggle/index.js b/packages/edit-site/src/components/navigation-sidebar/navigation-toggle/index.js index 9d98fa0c34dfd..63401f3564814 100644 --- a/packages/edit-site/src/components/navigation-sidebar/navigation-toggle/index.js +++ b/packages/edit-site/src/components/navigation-sidebar/navigation-toggle/index.js @@ -2,6 +2,7 @@ * WordPress dependencies */ import { useSelect, useDispatch } from '@wordpress/data'; +import { useEffect, useRef } from '@wordpress/element'; import { Button, Icon, @@ -40,6 +41,16 @@ function NavigationToggle( { icon } ) { const disableMotion = useReducedMotion(); + const navigationToggleRef = useRef(); + + useEffect( () => { + // TODO: Remove this effect when alternative solution is merged. + // See: https://github.com/WordPress/gutenberg/pull/37314 + if ( ! isNavigationOpen ) { + navigationToggleRef.current.focus(); + } + }, [ isNavigationOpen ] ); + const toggleNavigationPanel = () => setIsNavigationPanelOpened( ! isNavigationOpen ); @@ -79,6 +90,7 @@ function NavigationToggle( { icon } ) { <Button className="edit-site-navigation-toggle__button has-icon" label={ __( 'Toggle navigation' ) } + ref={ navigationToggleRef } // isPressed will add unwanted styles. aria-pressed={ isNavigationOpen } onClick={ toggleNavigationPanel } From b34b66171724baf7e6218a07ecb2ef97172a6664 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Fri, 17 Dec 2021 13:32:58 +1000 Subject: [PATCH 25/34] ToolsPanel: Allow items to register when panelId is null (#37273) This helps make the ToolsPanel compatible for use when multiple blocks are selected and the original approach setting the panelId as the block's clientId cannot work. --- packages/components/CHANGELOG.md | 1 + .../components/src/tools-panel/test/index.js | 156 +++++++++++++++--- .../src/tools-panel/tools-panel-item/hook.ts | 20 ++- .../src/tools-panel/tools-panel/README.md | 5 +- 4 files changed, 155 insertions(+), 27 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 126bfc82e3177..8c290cfb737c1 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -23,6 +23,7 @@ - Normalized label line-height and spacing within the `ToolsPanel` ([36387](https://github.com/WordPress/gutenberg/pull/36387)) - Remove unused `reakit-utils` from peer dependencies ([#37369](https://github.com/WordPress/gutenberg/pull/37369)). - Update all Emotion dependencies to the latest version to ensure they work correctly with React types ([#37365](https://github.com/WordPress/gutenberg/pull/37365)). +- Allowed `ToolsPanel` to register items when `panelId` is `null` due to multiple block selection ([37216](https://github.com/WordPress/gutenberg/pull/37216)). ### Enhancements diff --git a/packages/components/src/tools-panel/test/index.js b/packages/components/src/tools-panel/test/index.js index 3d39b54fe2d85..781401d35b76d 100644 --- a/packages/components/src/tools-panel/test/index.js +++ b/packages/components/src/tools-panel/test/index.js @@ -11,6 +11,7 @@ import { createSlotFill, Provider as SlotFillProvider } from '../../slot-fill'; const { Fill: ToolsPanelItems, Slot } = createSlotFill( 'ToolsPanelSlot' ); const resetAll = jest.fn(); +const noop = () => undefined; // Default props for the tools panel. const defaultProps = { @@ -86,6 +87,23 @@ const GroupedItems = ( { ); }; +// This context object is used to help simulate different scenarios in which +// `ToolsPanelItem` registration or deregistration requires testing. +const panelContext = { + panelId: '1234', + menuItems: { + default: {}, + optional: { [ altControlProps.label ]: true }, + }, + hasMenuItems: false, + isResetting: false, + shouldRenderPlaceholderItems: false, + registerPanelItem: jest.fn(), + deregisterPanelItem: jest.fn(), + flagItemCustomization: noop, + areAllOptionalControlsHidden: true, +}; + // Renders a tools panel including panel items that have been grouped within // a custom component. const renderGroupedItemsInPanel = () => { @@ -499,6 +517,10 @@ describe( 'ToolsPanel', () => { } ); describe( 'registration of panel items', () => { + beforeEach( () => { + jest.clearAllMocks(); + } ); + it( 'should register and deregister items when panelId changes', () => { // This test simulates switching block selection, which causes the // `ToolsPanel` to rerender with a new panelId, necessitating the @@ -509,23 +531,7 @@ describe( 'ToolsPanel', () => { // themselves, while those for the old panelId deregister. // // See: https://github.com/WordPress/gutenberg/pull/36588 - - const noop = () => undefined; - const context = { - panelId: '1234', - menuItems: { - default: {}, - optional: { [ altControlProps.label ]: true }, - }, - hasMenuItems: false, - isResetting: false, - shouldRenderPlaceholderItems: false, - registerPanelItem: jest.fn(), - deregisterPanelItem: jest.fn(), - flagItemCustomization: noop, - areAllOptionalControlsHidden: true, - }; - + const context = { ...panelContext }; const TestPanel = () => ( <ToolsPanelContext.Provider value={ context }> <ToolsPanelItem { ...altControlProps } panelId="1234"> @@ -581,6 +587,67 @@ describe( 'ToolsPanel', () => { // deregisterPanelItem has still only been called once. expect( context.deregisterPanelItem ).toHaveBeenCalledTimes( 1 ); } ); + + it( 'should register items when ToolsPanel panelId is null', () => { + // This test simulates when a panel spans multiple block selections. + // Multi-selection means a panel can't have a single id to match + // against the item's. Instead the panel gets an id of `null` and + // individual items should still render themselves in this case. + // + // See: https://github.com/WordPress/gutenberg/pull/37216 + const context = { ...panelContext, panelId: null }; + const TestPanel = () => ( + <ToolsPanelContext.Provider value={ context }> + <ToolsPanelItem { ...altControlProps } panelId="1234"> + <div>Item</div> + </ToolsPanelItem> + </ToolsPanelContext.Provider> + ); + + // On the initial render of the panel, the ToolsPanelItem should + // be registered. + const { rerender, unmount } = render( <TestPanel /> ); + + expect( context.registerPanelItem ).toHaveBeenCalledWith( + expect.objectContaining( { + label: altControlProps.label, + panelId: '1234', + } ) + ); + expect( context.deregisterPanelItem ).not.toHaveBeenCalled(); + + // Simulate a further block selection being added to the + // multi-selection. The panelId will remain `null` in this case. + rerender( <TestPanel /> ); + expect( context.registerPanelItem ).toHaveBeenCalledTimes( 1 ); + expect( context.deregisterPanelItem ).not.toHaveBeenCalled(); + + // Simulate a change in panel back to single block selection for + // which the item matches panelId. + context.panelId = '1234'; + rerender( <TestPanel /> ); + expect( context.registerPanelItem ).toHaveBeenCalledTimes( 1 ); + expect( context.deregisterPanelItem ).not.toHaveBeenCalled(); + + // Simulate another multi-selection where the panelId is `null`. + // Item should re-register itself after it deregistered as the + // multi-selection occurred. + context.panelId = null; + rerender( <TestPanel /> ); + expect( context.registerPanelItem ).toHaveBeenCalledTimes( 2 ); + expect( context.deregisterPanelItem ).toHaveBeenCalledTimes( 1 ); + + // Simulate a change in panel e.g. back to a single block selection + // Where the item's panelId is not a match. + context.panelId = '4321'; + rerender( <TestPanel /> ); + + // As the item no longer matches the panelId it should not have + // registered again but instead deregistered. + unmount(); + expect( context.registerPanelItem ).toHaveBeenCalledTimes( 2 ); + expect( context.deregisterPanelItem ).toHaveBeenCalledTimes( 2 ); + } ); } ); describe( 'callbacks on menu item selection', () => { @@ -775,8 +842,6 @@ describe( 'ToolsPanel', () => { // This test simulates this issue by rendering an item within a // contrived `ToolsPanelContext` to reflect the changes the panel // item needs to protect against. - - const noop = () => undefined; const context = { panelId: '1234', menuItems: { @@ -821,6 +886,59 @@ describe( 'ToolsPanel', () => { expect( altControlProps.onDeselect ).not.toHaveBeenCalled(); } ); + + it( 'should not contain orphaned menu items when panelId changes', async () => { + // As fills and the panel can update independently this aims to + // test that no orphaned items appear registered in the panel menu. + // + // See: https://github.com/WordPress/gutenberg/pull/34085 + const TestSlotFillPanel = ( { panelId } ) => ( + <SlotFillProvider> + <ToolsPanelItems> + <ToolsPanelItem { ...altControlProps } panelId="1234"> + <div>Item 1</div> + </ToolsPanelItem> + </ToolsPanelItems> + <ToolsPanelItems> + <ToolsPanelItem { ...controlProps } panelId="9999"> + <div>Item 2</div> + </ToolsPanelItem> + </ToolsPanelItems> + <ToolsPanel { ...defaultProps } panelId={ panelId }> + <Slot /> + </ToolsPanel> + </SlotFillProvider> + ); + + const { rerender } = render( <TestSlotFillPanel panelId="1234" /> ); + await openDropdownMenu(); + + // Only the item matching the panelId should have been registered + // and appear in the panel menu. + let altMenuItem = screen.getByRole( 'menuitemcheckbox', { + name: 'Show Alt', + } ); + let exampleMenuItem = screen.queryByRole( 'menuitemcheckbox', { + name: 'Hide and reset Example', + } ); + + expect( altMenuItem ).toBeInTheDocument(); + expect( exampleMenuItem ).not.toBeInTheDocument(); + + // Re-render the panel with different panelID simulating a block + // selection change. + rerender( <TestSlotFillPanel panelId="9999" /> ); + + altMenuItem = screen.queryByRole( 'menuitemcheckbox', { + name: 'Show Alt', + } ); + exampleMenuItem = screen.getByRole( 'menuitemcheckbox', { + name: 'Hide and reset Example', + } ); + + expect( altMenuItem ).not.toBeInTheDocument(); + expect( exampleMenuItem ).toBeInTheDocument(); + } ); } ); describe( 'panel header icon toggle', () => { diff --git a/packages/components/src/tools-panel/tools-panel-item/hook.ts b/packages/components/src/tools-panel/tools-panel-item/hook.ts index d519f8a4ef5bf..f7aba1c0bc7ef 100644 --- a/packages/components/src/tools-panel/tools-panel-item/hook.ts +++ b/packages/components/src/tools-panel/tools-panel-item/hook.ts @@ -40,11 +40,15 @@ export function useToolsPanelItem( const hasValueCallback = useCallback( hasValue, [ panelId ] ); const resetAllFilterCallback = useCallback( resetAllFilter, [ panelId ] ); + const previousPanelId = usePrevious( currentPanelId ); + + const hasMatchingPanel = + currentPanelId === panelId || currentPanelId === null; // Registering the panel item allows the panel to include it in its // automatically generated menu and determine its initial checked status. useEffect( () => { - if ( currentPanelId === panelId ) { + if ( hasMatchingPanel && previousPanelId !== null ) { registerPanelItem( { hasValue: hasValueCallback, isShownByDefault, @@ -55,16 +59,21 @@ export function useToolsPanelItem( } return () => { - if ( currentPanelId === panelId ) { + if ( + ( previousPanelId === null && !! currentPanelId ) || + currentPanelId === panelId + ) { deregisterPanelItem( label ); } }; }, [ currentPanelId, - panelId, + hasMatchingPanel, isShownByDefault, label, hasValueCallback, + panelId, + previousPanelId, resetAllFilterCallback, ] ); @@ -88,7 +97,7 @@ export function useToolsPanelItem( // Determine if the panel item's corresponding menu is being toggled and // trigger appropriate callback if it is. useEffect( () => { - if ( isResetting || currentPanelId !== panelId ) { + if ( isResetting || ! hasMatchingPanel ) { return; } @@ -100,11 +109,10 @@ export function useToolsPanelItem( onDeselect?.(); } }, [ - currentPanelId, + hasMatchingPanel, isMenuItemChecked, isResetting, isValueSet, - panelId, wasMenuItemChecked, ] ); diff --git a/packages/components/src/tools-panel/tools-panel/README.md b/packages/components/src/tools-panel/tools-panel/README.md index 6de380b78ffba..23581f90d7546 100644 --- a/packages/components/src/tools-panel/tools-panel/README.md +++ b/packages/components/src/tools-panel/tools-panel/README.md @@ -93,8 +93,9 @@ panel's dropdown menu. ### `panelId`: `string` If a `panelId` is set, it is passed through the `ToolsPanelContext` and used -to restrict panel items. Only items with a matching `panelId` will be able -to register themselves with this panel. +to restrict panel items. When a `panelId` is set, items can only register +themselves if the `panelId` is explicitly `null` or the item's `panelId` matches +exactly. - Required: No From b747b902ac0cdde566c1e8d0f4b02e47363fa8e8 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Fri, 17 Dec 2021 14:25:15 +1000 Subject: [PATCH 26/34] Block Support Panels - Make block support tools panels compatible with multi-selection (#37216) * Update block support panels to display in multi select inspector * Update the order of block support slots --- .../src/components/block-inspector/index.js | 20 ++++- .../block-support-tools-panel.js | 78 ++++++++++++++----- 2 files changed, 73 insertions(+), 25 deletions(-) diff --git a/packages/block-editor/src/components/block-inspector/index.js b/packages/block-editor/src/components/block-inspector/index.js index 4f1061417b211..c2a1b313fcd76 100644 --- a/packages/block-editor/src/components/block-inspector/index.js +++ b/packages/block-editor/src/components/block-inspector/index.js @@ -67,6 +67,18 @@ const BlockInspector = ( { showNoBlockSelectedMessage = true } ) => { <div className="block-editor-block-inspector"> <MultiSelectionInspector /> <InspectorControls.Slot /> + <InspectorControls.Slot + __experimentalGroup="typography" + label={ __( 'Typography' ) } + /> + <InspectorControls.Slot + __experimentalGroup="dimensions" + label={ __( 'Dimensions' ) } + /> + <InspectorControls.Slot + __experimentalGroup="border" + label={ __( 'Border' ) } + /> </div> ); } @@ -131,14 +143,14 @@ const BlockInspectorSingleBlock = ( { __experimentalGroup="typography" label={ __( 'Typography' ) } /> - <InspectorControls.Slot - __experimentalGroup="border" - label={ __( 'Border' ) } - /> <InspectorControls.Slot __experimentalGroup="dimensions" label={ __( 'Dimensions' ) } /> + <InspectorControls.Slot + __experimentalGroup="border" + label={ __( 'Border' ) } + /> <div> <AdvancedControls /> </div> diff --git a/packages/block-editor/src/components/inspector-controls/block-support-tools-panel.js b/packages/block-editor/src/components/inspector-controls/block-support-tools-panel.js index 92479384b5bcb..086817b6713b8 100644 --- a/packages/block-editor/src/components/inspector-controls/block-support-tools-panel.js +++ b/packages/block-editor/src/components/inspector-controls/block-support-tools-panel.js @@ -11,37 +11,73 @@ import { store as blockEditorStore } from '../../store'; import { cleanEmptyObject } from '../../hooks/utils'; export default function BlockSupportToolsPanel( { children, group, label } ) { - const { clientId, attributes } = useSelect( ( select ) => { - const { getBlockAttributes, getSelectedBlockClientId } = select( - blockEditorStore - ); + const { attributes, clientIds, panelId } = useSelect( ( select ) => { + const { + getBlockAttributes, + getMultiSelectedBlockClientIds, + getSelectedBlockClientId, + hasMultiSelection, + } = select( blockEditorStore ); + + // When we currently have a multi-selection, the value returned from + // `getSelectedBlockClientId()` is `null`. When a `null` value is used + // for the `panelId`, a `ToolsPanel` will still allow panel items to + // register themselves despite their panelIds not matching. const selectedBlockClientId = getSelectedBlockClientId(); + if ( hasMultiSelection() ) { + const selectedBlockClientIds = getMultiSelectedBlockClientIds(); + const selectedBlockAttributes = selectedBlockClientIds.reduce( + ( blockAttributes, blockId ) => { + blockAttributes[ blockId ] = getBlockAttributes( blockId ); + return blockAttributes; + }, + {} + ); + + return { + panelId: selectedBlockClientId, + clientIds: selectedBlockClientIds, + attributes: selectedBlockAttributes, + }; + } + return { - clientId: selectedBlockClientId, - attributes: getBlockAttributes( selectedBlockClientId ), + panelId: selectedBlockClientId, + clientIds: [ selectedBlockClientId ], + attributes: { + [ selectedBlockClientId ]: getBlockAttributes( + selectedBlockClientId + ), + }, }; }, [] ); const { updateBlockAttributes } = useDispatch( blockEditorStore ); const resetAll = ( resetFilters = [] ) => { - const { style } = attributes; - let newAttributes = { style }; + const newAttributes = {}; + + clientIds.forEach( ( clientId ) => { + const { style } = attributes[ clientId ]; + let newBlockAttributes = { style }; - resetFilters.forEach( ( resetFilter ) => { - newAttributes = { - ...newAttributes, - ...resetFilter( newAttributes ), + resetFilters.forEach( ( resetFilter ) => { + newBlockAttributes = { + ...newBlockAttributes, + ...resetFilter( newBlockAttributes ), + }; + } ); + + // Enforce a cleaned style object. + newBlockAttributes = { + ...newBlockAttributes, + style: cleanEmptyObject( newBlockAttributes.style ), }; - } ); - // Enforce a cleaned style object. - newAttributes = { - ...newAttributes, - style: cleanEmptyObject( newAttributes.style ), - }; + newAttributes[ clientId ] = newBlockAttributes; + } ); - updateBlockAttributes( clientId, newAttributes ); + updateBlockAttributes( clientIds, newAttributes, true ); }; return ( @@ -49,8 +85,8 @@ export default function BlockSupportToolsPanel( { children, group, label } ) { className={ `${ group }-block-support-panel` } label={ label } resetAll={ resetAll } - key={ clientId } - panelId={ clientId } + key={ panelId } + panelId={ panelId } hasInnerWrapper={ true } shouldRenderPlaceholderItems={ true } // Required to maintain fills ordering. > From 8b8313d15f0a67ba0da0286ea4cfe87f0f07f14d Mon Sep 17 00:00:00 2001 From: George Mamadashvili <georgemamadashvili@gmail.com> Date: Fri, 17 Dec 2021 06:16:09 +0000 Subject: [PATCH 27/34] Gallery: Fix block registration hook priority (#37409) --- packages/block-library/src/gallery/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/gallery/index.php b/packages/block-library/src/gallery/index.php index a159de4df173e..ac30d58e225ed 100644 --- a/packages/block-library/src/gallery/index.php +++ b/packages/block-library/src/gallery/index.php @@ -48,4 +48,4 @@ function register_block_core_gallery() { ); } -add_action( 'init', 'register_block_core_gallery', 20 ); +add_action( 'init', 'register_block_core_gallery' ); From 5604b2323e57f0125ad8fb8dc5423c067978b128 Mon Sep 17 00:00:00 2001 From: Nik Tsekouras <ntsekouras@outlook.com> Date: Fri, 17 Dec 2021 08:37:14 +0200 Subject: [PATCH 28/34] [Global Styles]: Add support for nameless font sizes (#37410) * add support for nameless font sizes * add test --- .../class-wp-theme-json-gutenberg.php | 103 ++++++++++++------ phpunit/class-wp-theme-json-test.php | 91 ++++++++++++++++ 2 files changed, 162 insertions(+), 32 deletions(-) diff --git a/lib/compat/wordpress-5.9/class-wp-theme-json-gutenberg.php b/lib/compat/wordpress-5.9/class-wp-theme-json-gutenberg.php index f98b5b96e51d8..280a30fafeb51 100644 --- a/lib/compat/wordpress-5.9/class-wp-theme-json-gutenberg.php +++ b/lib/compat/wordpress-5.9/class-wp-theme-json-gutenberg.php @@ -95,48 +95,53 @@ class WP_Theme_JSON_Gutenberg { */ const PRESETS_METADATA = array( array( - 'path' => array( 'color', 'palette' ), - 'override' => array( 'color', 'defaultPalette' ), - 'value_key' => 'color', - 'css_vars' => '--wp--preset--color--$slug', - 'classes' => array( + 'path' => array( 'color', 'palette' ), + 'override' => array( 'color', 'defaultPalette' ), + 'use_default_names' => false, + 'value_key' => 'color', + 'css_vars' => '--wp--preset--color--$slug', + 'classes' => array( '.has-$slug-color' => 'color', '.has-$slug-background-color' => 'background-color', '.has-$slug-border-color' => 'border-color', ), - 'properties' => array( 'color', 'background-color', 'border-color' ), + 'properties' => array( 'color', 'background-color', 'border-color' ), ), array( - 'path' => array( 'color', 'gradients' ), - 'override' => array( 'color', 'defaultGradients' ), - 'value_key' => 'gradient', - 'css_vars' => '--wp--preset--gradient--$slug', - 'classes' => array( '.has-$slug-gradient-background' => 'background' ), - 'properties' => array( 'background' ), + 'path' => array( 'color', 'gradients' ), + 'override' => array( 'color', 'defaultGradients' ), + 'use_default_names' => false, + 'value_key' => 'gradient', + 'css_vars' => '--wp--preset--gradient--$slug', + 'classes' => array( '.has-$slug-gradient-background' => 'background' ), + 'properties' => array( 'background' ), ), array( - 'path' => array( 'color', 'duotone' ), - 'override' => true, - 'value_func' => 'gutenberg_render_duotone_filter_preset', - 'css_vars' => '--wp--preset--duotone--$slug', - 'classes' => array(), - 'properties' => array( 'filter' ), + 'path' => array( 'color', 'duotone' ), + 'override' => true, + 'use_default_names' => false, + 'value_func' => 'gutenberg_render_duotone_filter_preset', + 'css_vars' => '--wp--preset--duotone--$slug', + 'classes' => array(), + 'properties' => array( 'filter' ), ), array( - 'path' => array( 'typography', 'fontSizes' ), - 'override' => true, - 'value_key' => 'size', - 'css_vars' => '--wp--preset--font-size--$slug', - 'classes' => array( '.has-$slug-font-size' => 'font-size' ), - 'properties' => array( 'font-size' ), + 'path' => array( 'typography', 'fontSizes' ), + 'override' => true, + 'use_default_names' => true, + 'value_key' => 'size', + 'css_vars' => '--wp--preset--font-size--$slug', + 'classes' => array( '.has-$slug-font-size' => 'font-size' ), + 'properties' => array( 'font-size' ), ), array( - 'path' => array( 'typography', 'fontFamilies' ), - 'override' => true, - 'value_key' => 'fontFamily', - 'css_vars' => '--wp--preset--font-family--$slug', - 'classes' => array( '.has-$slug-font-family' => 'font-family' ), - 'properties' => array( 'font-family' ), + 'path' => array( 'typography', 'fontFamilies' ), + 'override' => true, + 'use_default_names' => false, + 'value_key' => 'fontFamily', + 'css_vars' => '--wp--preset--font-family--$slug', + 'classes' => array( '.has-$slug-font-family' => 'font-family' ), + 'properties' => array( 'font-family' ), ), ); @@ -1445,12 +1450,24 @@ public function merge( $incoming ) { $override_preset = self::should_override_preset( $this->theme_json, $node['path'], $preset['override'] ); foreach ( self::VALID_ORIGINS as $origin ) { - $path = array_merge( $node['path'], $preset['path'], array( $origin ) ); - $content = _wp_array_get( $incoming_data, $path, null ); + $base_path = array_merge( $node['path'], $preset['path'] ); + $path = array_merge( $base_path, array( $origin ) ); + $content = _wp_array_get( $incoming_data, $path, null ); if ( ! isset( $content ) ) { continue; } + if ( 'theme' === $origin && $preset['use_default_names'] ) { + foreach ( $content as &$item ) { + if ( ! array_key_exists( 'name', $item ) ) { + $name = self::get_name_from_defaults( $item['slug'], $base_path ); + if ( null !== $name ) { + $item['name'] = $name; + } + } + } + } + if ( ( 'theme' !== $origin ) || ( 'theme' === $origin && $override_preset ) @@ -1546,6 +1563,28 @@ function( $value ) { return $slugs; } + /** + * Get a `default`'s preset name by a provided slug. + * + * @param string $slug The slug we want to find a match from default presets. + * @param array $base_path The path to inspect. It's 'settings' by default. + * + * @return string|null + */ + private function get_name_from_defaults( $slug, $base_path ) { + $path = array_merge( $base_path, array( 'default' ) ); + $default_content = _wp_array_get( $this->theme_json, $path, null ); + if ( ! $default_content ) { + return null; + } + foreach ( $default_content as $item ) { + if ( $slug === $item['slug'] ) { + return $item['name']; + } + } + return null; + } + /** * Removes the preset values whose slug is equal to any of given slugs. * diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 8afe7dd6c14c7..2fe7077420208 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -1434,6 +1434,97 @@ public function test_merge_incoming_data_color_presets_with_same_slugs_as_defaul $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( + 'fontSizes' => array( + 'theme' => array( + array( + 'name' => 'Small', + 'slug' => 'small', + 'size' => '1.1rem', + ), + array( + 'name' => 'Large', + 'slug' => 'large', + 'size' => '1.75rem', + ), + array( + 'name' => 'Huge', + 'slug' => 'huge', + 'size' => '3rem', + ), + ), + 'default' => array( + array( + 'name' => 'Small', + 'slug' => 'small', + 'size' => '12px', + ), + array( + 'name' => 'Large', + 'slug' => 'large', + 'size' => '20px', + ), + ), + ), + ), + ), + ); + $defaults->merge( $theme_json ); + $actual = $defaults->get_raw_data(); + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + function test_remove_insecure_properties_removes_unsafe_styles() { $actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties( array( From 54ca4a4b58d8c291a691c0d52439255169b84204 Mon Sep 17 00:00:00 2001 From: Nik Tsekouras <ntsekouras@outlook.com> Date: Fri, 17 Dec 2021 10:27:06 +0200 Subject: [PATCH 29/34] Update Query Loop patterns with less posts per page (#37475) --- lib/block-patterns.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/block-patterns.php b/lib/block-patterns.php index 8ba3a7efd00d0..8203def3c98dd 100644 --- a/lib/block-patterns.php +++ b/lib/block-patterns.php @@ -19,7 +19,7 @@ function gutenberg_register_gutenberg_patterns() { 'title' => _x( 'Standard', 'Block pattern title', 'gutenberg' ), 'blockTypes' => array( 'core/query' ), 'categories' => array( 'query' ), - 'content' => '<!-- wp:query {"query":{"perPage":3,"pages":0,"offset":0,"postType":"post","categoryIds":[],"tagIds":[],"order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"","inherit":false}} --> + 'content' => '<!-- wp:query {"query":{"perPage":1,"pages":0,"offset":0,"postType":"post","categoryIds":[],"tagIds":[],"order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"","inherit":false}} --> <div class="wp-block-query"> <!-- wp:post-template --> <!-- wp:post-title {"isLink":true} /--> @@ -37,7 +37,7 @@ function gutenberg_register_gutenberg_patterns() { 'title' => _x( 'Image at left', 'Block pattern title', 'gutenberg' ), 'blockTypes' => array( 'core/query' ), 'categories' => array( 'query' ), - 'content' => '<!-- wp:query {"query":{"perPage":3,"pages":0,"offset":0,"postType":"post","categoryIds":[],"tagIds":[],"order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"","inherit":false}} --> + 'content' => '<!-- wp:query {"query":{"perPage":1,"pages":0,"offset":0,"postType":"post","categoryIds":[],"tagIds":[],"order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"","inherit":false}} --> <div class="wp-block-query"> <!-- wp:post-template --> <!-- wp:columns {"align":"wide"} --> @@ -57,7 +57,7 @@ function gutenberg_register_gutenberg_patterns() { 'title' => _x( 'Small image and title', 'Block pattern title', 'gutenberg' ), 'blockTypes' => array( 'core/query' ), 'categories' => array( 'query' ), - 'content' => '<!-- wp:query {"query":{"perPage":3,"pages":0,"offset":0,"postType":"post","categoryIds":[],"tagIds":[],"order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"","inherit":false}} --> + 'content' => '<!-- wp:query {"query":{"perPage":1,"pages":0,"offset":0,"postType":"post","categoryIds":[],"tagIds":[],"order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"","inherit":false}} --> <div class="wp-block-query"> <!-- wp:post-template --> <!-- wp:columns {"verticalAlignment":"center"} --> @@ -76,7 +76,7 @@ function gutenberg_register_gutenberg_patterns() { 'title' => _x( 'Grid', 'Block pattern title', 'gutenberg' ), 'blockTypes' => array( 'core/query' ), 'categories' => array( 'query' ), - 'content' => '<!-- wp:query {"query":{"perPage":6,"pages":0,"offset":0,"postType":"post","categoryIds":[],"tagIds":[],"order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"exclude","inherit":false},"displayLayout":{"type":"flex","columns":3}} --> + 'content' => '<!-- wp:query {"query":{"perPage":3,"pages":0,"offset":0,"postType":"post","categoryIds":[],"tagIds":[],"order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"exclude","inherit":false},"displayLayout":{"type":"flex","columns":3}} --> <div class="wp-block-query"> <!-- wp:post-template --> <!-- wp:group {"style":{"spacing":{"padding":{"top":"30px","right":"30px","bottom":"30px","left":"30px"}}},"layout":{"inherit":false}} --> From 73035140fcd650c5e64d891d1eb9fd5a51665670 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Fri, 17 Dec 2021 11:01:31 +0100 Subject: [PATCH 30/34] Add the typography support to group/row blocks (#37456) --- packages/block-library/src/group/block.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/block-library/src/group/block.json b/packages/block-library/src/group/block.json index d0288b127907a..b9a7b9ea8acf5 100644 --- a/packages/block-library/src/group/block.json +++ b/packages/block-library/src/group/block.json @@ -43,6 +43,17 @@ "width": true } }, + "typography": { + "fontSize": true, + "lineHeight": true, + "__experimentalFontStyle": true, + "__experimentalFontWeight": true, + "__experimentalLetterSpacing": true, + "__experimentalTextTransform": true, + "__experimentalDefaultControls": { + "fontSize": true + } + }, "__experimentalLayout": true }, "editorStyle": "wp-block-group-editor", From 585692bcc2db26cf417b08eda13fcc3ace4f2e68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Fri, 17 Dec 2021 11:23:41 +0100 Subject: [PATCH 31/34] ESLint Plugin: Fix Babel config resolution when a custom ESLint config present (#37406) * ESLint Plugin: Fix Babel config resolution when a custom ESLint config present * Include a note about `babel.config.json` --- package-lock.json | 1 + packages/eslint-plugin/CHANGELOG.md | 4 +++ packages/eslint-plugin/configs/esnext.js | 29 ++++++++++++++++--- .../configs/recommended-with-formatting.js | 1 - packages/eslint-plugin/package.json | 1 + 5 files changed, 31 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index b339d41213653..e46ae5a55ee29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16067,6 +16067,7 @@ "@babel/eslint-parser": "^7.16.0", "@typescript-eslint/eslint-plugin": "^5.3.0", "@typescript-eslint/parser": "^5.3.0", + "@wordpress/babel-preset-default": "file:packages/babel-preset-default", "@wordpress/prettier-config": "file:packages/prettier-config", "cosmiconfig": "^7.0.0", "eslint-config-prettier": "^8.3.0", diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 0c7752d3e1dd9..424eae1518ebc 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -12,6 +12,10 @@ - The bundled `eslint-plugin-jsdoc` dependency has been updated from requiring `^36.0.8` to requiring `^37.0.3` ([#36283](https://github.com/WordPress/gutenberg/pull/36283)). - The bundled `globals` dependency has been updated from requiring `^12.0.0` to requiring `^13.12.0` ([#36283](https://github.com/WordPress/gutenberg/pull/36283)). +### Bug Fix + +- Fix Babel config resolution when a custom ESLint config present ([#37406](https://github.com/WordPress/gutenberg/pull/37406)). Warning: it won't recognize the `babel.config.json` file present in the project until the upstream bug in `cosmiconfig` is fixed. + ## 9.3.0 (2021-11-15) ### Enhancements diff --git a/packages/eslint-plugin/configs/esnext.js b/packages/eslint-plugin/configs/esnext.js index 4cdd60b0fa05b..7c1f2e8140d30 100644 --- a/packages/eslint-plugin/configs/esnext.js +++ b/packages/eslint-plugin/configs/esnext.js @@ -1,11 +1,17 @@ -module.exports = { +/** + * External dependencies + */ +const { cosmiconfigSync } = require( 'cosmiconfig' ); + +const config = { + parser: '@babel/eslint-parser', + parserOptions: { + sourceType: 'module', + }, env: { es6: true, }, extends: [ require.resolve( './es5.js' ) ], - parserOptions: { - sourceType: 'module', - }, rules: { // Disable ES5-specific (extended from ES5) 'vars-on-top': 'off', @@ -45,3 +51,18 @@ module.exports = { 'template-curly-spacing': [ 'error', 'always' ], }, }; + +// It won't recognize the `babel.config.json` file used in the project until the upstream bug in `cosmiconfig` is fixed: +// https://github.com/davidtheclark/cosmiconfig/issues/246. +const result = cosmiconfigSync( 'babel' ).search(); +if ( ! result || ! result.filepath ) { + config.parserOptions = { + ...config.parserOptions, + requireConfigFile: false, + babelOptions: { + presets: [ require.resolve( '@wordpress/babel-preset-default' ) ], + }, + }; +} + +module.exports = config; diff --git a/packages/eslint-plugin/configs/recommended-with-formatting.js b/packages/eslint-plugin/configs/recommended-with-formatting.js index affafe7ba8e4e..b904a15d246c9 100644 --- a/packages/eslint-plugin/configs/recommended-with-formatting.js +++ b/packages/eslint-plugin/configs/recommended-with-formatting.js @@ -4,7 +4,6 @@ const { isPackageInstalled } = require( '../utils' ); const config = { - parser: '@babel/eslint-parser', extends: [ require.resolve( './jsx-a11y.js' ), require.resolve( './custom.js' ), diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index a5cf6610ddc2f..31199ff296b02 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -34,6 +34,7 @@ "@babel/eslint-parser": "^7.16.0", "@typescript-eslint/eslint-plugin": "^5.3.0", "@typescript-eslint/parser": "^5.3.0", + "@wordpress/babel-preset-default": "file:../babel-preset-default", "@wordpress/prettier-config": "file:../prettier-config", "cosmiconfig": "^7.0.0", "eslint-config-prettier": "^8.3.0", From 78c7f0c7a9c3d69979d0633375325104fb03b86d Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@automattic.com> Date: Fri, 17 Dec 2021 10:41:01 +0000 Subject: [PATCH 32/34] Chora: Add Changelog entry for __experimentalIsRenderedInSidebar gradient components flag (#37457) --- packages/components/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 8c290cfb737c1..0e8026b8115aa 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -14,6 +14,7 @@ - Divider: improve support for vertical orientation and RTL styles, use start/end logical props instead of top/bottom, change border-color to `currentColor` ([#36579](https://github.com/WordPress/gutenberg/pull/36579)). - `ToggleGroupControl`: Avoid calling `onChange` if radio state changed from an incoming value ([#37224](https://github.com/WordPress/gutenberg/pull/37224/)). - `ToggleGroupControl`: fix the computation of the backdrop dimensions when rendered in a Popover ([#37067](https://github.com/WordPress/gutenberg/pull/37067)). +- Add `__experimentalIsRenderedInSidebar` property to the `GradientPicker`and `CustomGradientPicker`. The property changes the color popover behavior to have a special placement behavior appropriate for sidebar UI's. ### Bug Fix From a92586c6ba83e735e102867bc69584b6af856643 Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.richards@automattic.com> Date: Fri, 17 Dec 2021 19:24:50 +0800 Subject: [PATCH 33/34] Fix and enable remaining navigation block e2e tests (#37437) * Re-enable single entity test * Fix and enable page creation test * Split up and enable script loading tests * Fix page adding test * Add permissions test back again * Fix script tests --- .../__snapshots__/navigation.test.js.snap | 2 + .../specs/editor/blocks/navigation.test.js | 192 +++++------------- 2 files changed, 53 insertions(+), 141 deletions(-) diff --git a/packages/e2e-tests/specs/editor/blocks/__snapshots__/navigation.test.js.snap b/packages/e2e-tests/specs/editor/blocks/__snapshots__/navigation.test.js.snap index 7b910d141d8ed..310dc8a161121 100644 --- a/packages/e2e-tests/specs/editor/blocks/__snapshots__/navigation.test.js.snap +++ b/packages/e2e-tests/specs/editor/blocks/__snapshots__/navigation.test.js.snap @@ -6,6 +6,8 @@ exports[`Navigation allows an empty navigation block to be created and manually <!-- wp:navigation-link {\\"label\\":\\"Contact\\",\\"type\\":\\"page\\",\\"id\\":[number],\\"url\\":\\"https://this/is/a/test/search/get-in-touch\\",\\"kind\\":\\"post-type\\",\\"isTopLevelLink\\":true} /-->" `; +exports[`Navigation allows pages to be created from the navigation block and their links added to menu 1`] = `"<!-- wp:navigation-link {\\"label\\":\\"A really long page name that will not exist\\",\\"type\\":\\"page\\",\\"id\\":[number],\\"url\\":\\"http://localhost:8889/?page_id=[number]\\",\\"kind\\":\\"post-type\\",\\"isTopLevelLink\\":true} /-->"`; + exports[`Navigation encodes URL when create block if needed 1`] = ` "<!-- wp:navigation-link {\\"label\\":\\"wordpress.org/шеллы\\",\\"url\\":\\"https://wordpress.org/%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\\",\\"kind\\":\\"custom\\",\\"isTopLevelLink\\":true} /--> diff --git a/packages/e2e-tests/specs/editor/blocks/navigation.test.js b/packages/e2e-tests/specs/editor/blocks/navigation.test.js index 12d8cb841e874..6f99aca050999 100644 --- a/packages/e2e-tests/specs/editor/blocks/navigation.test.js +++ b/packages/e2e-tests/specs/editor/blocks/navigation.test.js @@ -14,8 +14,6 @@ import { saveDraft, showBlockToolbar, openPreviewPage, - selectBlockByClientId, - getAllBlocks, ensureSidebarOpened, __experimentalRest as rest, publishPost, @@ -121,21 +119,6 @@ const START_EMPTY_XPATH = `${ PLACEHOLDER_ACTIONS_XPATH }//button[text()='Start const ADD_ALL_PAGES_XPATH = `${ PLACEHOLDER_ACTIONS_XPATH }//button[text()='Add all pages']`; const SELECT_MENU_XPATH = `${ PLACEHOLDER_ACTIONS_XPATH }//button[text()='Select menu']`; -async function turnResponsivenessOn() { - const blocks = await getAllBlocks(); - - await selectBlockByClientId( blocks[ 0 ].clientId ); - await ensureSidebarOpened(); - - const [ responsivenessToggleButton ] = await page.$x( - '//label[text()[contains(.,"Enable responsive menu")]]' - ); - - await responsivenessToggleButton.click(); - - await saveDraft(); -} - /** * Delete all items for the given REST resources using the REST API. * @@ -216,17 +199,6 @@ async function getNavigationMenuRawContent() { // Disable reason - these tests are to be re-written. // eslint-disable-next-line jest/no-disabled-tests describe( 'Navigation', () => { - let username; - let contribUserPassword; - - beforeAll( async () => { - username = 'contributoruser'; - - contribUserPassword = await createUser( username, { - role: 'contributor', - } ); - } ); - beforeEach( async () => { await deleteAll( [ POSTS_ENDPOINT, @@ -247,8 +219,6 @@ describe( 'Navigation', () => { NAVIGATION_MENUS_ENDPOINT, ] ); await deleteAllClassicMenus(); - - await deleteUser( username ); } ); describe( 'placeholder', () => { @@ -278,7 +248,7 @@ describe( 'Navigation', () => { await allPagesButton.click(); // Wait for the page list block to be present - await page.waitForSelector( 'div[aria-label="Block: Page List"]' ); + await page.waitForSelector( 'ul[aria-label="Block: Page List"]' ); expect( await getNavigationMenuRawContent() ).toMatchSnapshot(); } ); @@ -464,43 +434,28 @@ describe( 'Navigation', () => { expect( await getNavigationMenuRawContent() ).toMatchSnapshot(); } ); - // URL details endpoint is throwing a 404, which causes this test to fail. - it.skip( 'allows pages to be created from the navigation block and their links added to menu', async () => { + it( 'allows pages to be created from the navigation block and their links added to menu', async () => { await createNewPost(); await insertBlock( 'Navigation' ); const startEmptyButton = await page.waitForXPath( START_EMPTY_XPATH ); await startEmptyButton.click(); - const appender = await page.waitForSelector( '.wp-block-navigation .block-list-appender' ); await appender.click(); // Wait for URL input to be focused - await page.waitForSelector( - 'input.block-editor-url-input__input:focus' - ); - // Insert name for the new page. - await page.type( - 'input[placeholder="Search or type url"]', - 'A really long page name that will not exist' - ); - - // Wait for URL input to be focused - await page.waitForSelector( + const pageTitle = 'A really long page name that will not exist'; + const input = await page.waitForSelector( 'input.block-editor-url-input__input:focus' ); + await input.type( pageTitle ); // Wait for the create button to appear and click it. - await page.waitForSelector( - '.block-editor-link-control__search-create' - ); - - const createPageButton = await page.$( + const createPageButton = await page.waitForSelector( '.block-editor-link-control__search-create' ); - await createPageButton.click(); const draftLink = await page.waitForSelector( @@ -508,6 +463,20 @@ describe( 'Navigation', () => { ); await draftLink.click(); + // Creating a draft is async, so wait for a sign of completion. In this + // case the link that shows in the URL popover once a link is added. + await page.waitForXPath( + `//a[contains(@class, "block-editor-link-control__search-item-title") and contains(., "${ pageTitle }")]` + ); + + // The URL Details endpoint 404s for the created page, since it will + // be a draft that is inaccessible publicly. Wait for the HTTP request + // to finish, since this seems to make the test more stable. + await page.waitForNetworkIdle(); + expect( console ).toHaveErrored(); + + await publishPost(); + // Expect a Navigation Block with a link for "A really long page name that will not exist". expect( await getNavigationMenuRawContent() ).toMatchSnapshot(); } ); @@ -682,7 +651,7 @@ describe( 'Navigation', () => { expect( await page.$x( NAV_ENTITY_SELECTOR ) ).toHaveLength( 1 ); } ); - it( 'only update a single entity currently linked with the block', async () => { + it( 'only updates a single entity currently linked with the block', async () => { await createNewPost(); await insertBlock( 'Navigation' ); @@ -735,31 +704,16 @@ describe( 'Navigation', () => { } ); } ); - // The following tests are unstable, roughly around when https://github.com/WordPress/wordpress-develop/pull/1412 - // landed. The block manually tests well, so let's skip to unblock other PRs and immediately follow up. cc @vcanales - it.skip( 'loads frontend code only if the block is present', async () => { - // Mock the response from the Pages endpoint. This is done so that the pages returned are always - // consistent and to test the feature more rigorously than the single default sample page. - // await mockPagesResponse( [ - // { - // title: 'Home', - // slug: 'home', - // }, - // { - // title: 'About', - // slug: 'about', - // }, - // { - // title: 'Contact Us', - // slug: 'contact', - // }, - // ] ); - - // Create first block at the start in order to enable preview. - await insertBlock( 'Navigation' ); - await saveDraft(); + it( 'does not load the frontend script if no navigation blocks are present', async () => { + await createNewPost(); + await insertBlock( 'Paragraph' ); + await page.waitForSelector( 'p[data-title="Paragraph"]:focus' ); + await page.keyboard.type( 'Hello' ); const previewPage = await openPreviewPage(); + await previewPage.bringToFront(); + await previewPage.waitForNetworkIdle(); + const isScriptLoaded = await previewPage.evaluate( () => null !== @@ -769,85 +723,41 @@ describe( 'Navigation', () => { ); expect( isScriptLoaded ).toBe( false ); + } ); - const allPagesButton = await page.waitForXPath( ADD_ALL_PAGES_XPATH ); - await allPagesButton.click(); + it( 'loads the frontend script only once even when multiple navigation blocks are present', async () => { + await createNewPost(); + await insertBlock( 'Navigation' ); await insertBlock( 'Navigation' ); - const allPagesButton2 = await page.waitForXPath( ADD_ALL_PAGES_XPATH ); - await allPagesButton2.click(); - await turnResponsivenessOn(); - await previewPage.reload( { - waitFor: [ 'networkidle0', 'domcontentloaded' ], - } ); + const previewPage = await openPreviewPage(); + await previewPage.bringToFront(); + await previewPage.waitForNetworkIdle(); - /* - Count instances of the tag to make sure that it's been loaded only once, - regardless of the number of navigation blocks present. - */ const tagCount = await previewPage.evaluate( () => - Array.from( - document.querySelectorAll( - 'script[src*="navigation/view.min.js"]' - ) + document.querySelectorAll( + 'script[src*="navigation/view.min.js"]' ).length ); expect( tagCount ).toBe( 1 ); } ); - it.skip( 'loads frontend code only if responsiveness is turned on', async () => { - // await mockPagesResponse( [ - // { - // title: 'Home', - // slug: 'home', - // }, - // { - // title: 'About', - // slug: 'about', - // }, - // { - // title: 'Contact Us', - // slug: 'contact', - // }, - // ] ); - - await insertBlock( 'Navigation' ); - await saveDraft(); - - const previewPage = await openPreviewPage(); - let isScriptLoaded = await previewPage.evaluate( - () => - null !== - document.querySelector( - 'script[src*="navigation/view.min.js"]' - ) - ); - - expect( isScriptLoaded ).toBe( false ); - - const allPagesButton = await page.waitForXPath( ADD_ALL_PAGES_XPATH ); - await allPagesButton.click(); - - await turnResponsivenessOn(); + describe( 'Permission based restrictions', () => { + const contributorUsername = 'contributoruser'; + let contributorPassword; - await previewPage.reload( { - waitFor: [ 'networkidle0', 'domcontentloaded' ], + beforeAll( async () => { + contributorPassword = await createUser( contributorUsername, { + role: 'contributor', + } ); } ); - isScriptLoaded = await previewPage.evaluate( - () => - null !== - document.querySelector( - 'script[src*="navigation/view.min.js"]' - ) - ); - - expect( isScriptLoaded ).toBe( true ); - } ); + afterAll( async () => { + await deleteUser( contributorUsername ); + } ); - describe( 'Permission based restrictions', () => { it( 'shows a warning if user does not have permission to edit or update navigation menus', async () => { await createNewPost(); await insertBlock( 'Navigation' ); @@ -864,8 +774,8 @@ describe( 'Navigation', () => { await publishPost(); // Switch to a Contributor role user - they should not have - // permission to update Navigations. - await loginUser( username, contribUserPassword ); + // permission to update Navigation menus. + await loginUser( contributorUsername, contributorPassword ); await createNewPost(); @@ -886,7 +796,7 @@ describe( 'Navigation', () => { `//*[contains(@class, 'components-snackbar__content')][ text()="You do not have permission to edit this Menu. Any changes made will not be saved." ]` ); - // Expect a console 403 for request to Navigation Areas for lower permisison users. + // Expect a console 403 for request to Navigation Areas for lower permission users. // This is because reading requires the `edit_theme_options` capability // which the Contributor level user does not have. // See: https://github.com/WordPress/gutenberg/blob/4cedaf0c4abb0aeac4bfd4289d63e9889efe9733/lib/class-wp-rest-block-navigation-areas-controller.php#L81-L91. From 77a862243ed850b8788d9cd20499ea85b3468e62 Mon Sep 17 00:00:00 2001 From: Gerardo Pacheco <gerardo.pacheco@automattic.com> Date: Fri, 17 Dec 2021 14:42:39 +0100 Subject: [PATCH 34/34] Mobile - Cover block - Fixes color settings and placeholder visibility (#37372) * Mobile - Cover block: Fixes setting a color, gradient. It also adds compatibility for resetting the overlay color to the default one and fixes a case where the placeholder would be visible after creating the block and clearing its overlay color/media. * Mobile - Cover block: Show add media button if there's a background or if there's one inner block present * Mobile - Cover block - Update tests: Adds test cases for setting the overlay solid and gradient color, as well as resetting the overlay color. * Mobile - Cover - Add tests to check changing between solid and gradient colors --- .../panel-color-gradient-settings.native.js | 2 + .../block-library/src/cover/edit.native.js | 47 ++- .../cover/overlay-color-settings.native.js | 10 + .../test/__snapshots__/edit.native.js.snap | 33 ++ .../src/cover/test/edit.native.js | 301 +++++++++++++++++- .../src/color-palette/index.native.js | 2 + .../src/mobile/color-settings/index.native.js | 2 + .../color-settings/palette.screen.native.js | 5 + 8 files changed, 371 insertions(+), 31 deletions(-) create mode 100644 packages/block-library/src/cover/test/__snapshots__/edit.native.js.snap diff --git a/packages/block-editor/src/components/colors-gradients/panel-color-gradient-settings.native.js b/packages/block-editor/src/components/colors-gradients/panel-color-gradient-settings.native.js index 548137215fafc..b00b643dfb99a 100644 --- a/packages/block-editor/src/components/colors-gradients/panel-color-gradient-settings.native.js +++ b/packages/block-editor/src/components/colors-gradients/panel-color-gradient-settings.native.js @@ -21,6 +21,7 @@ export default function PanelColorGradientSettings( { settings, title } ) { return settings.map( ( { onColorChange, + onColorCleared, colorValue, onGradientChange, gradientValue, @@ -33,6 +34,7 @@ export default function PanelColorGradientSettings( { settings, title } ) { colorValue: gradientValue || colorValue, gradientValue, onGradientChange, + onColorCleared, label, } ); } } diff --git a/packages/block-library/src/cover/edit.native.js b/packages/block-library/src/cover/edit.native.js index 56437afde080d..bffb94c7655e3 100644 --- a/packages/block-library/src/cover/edit.native.js +++ b/packages/block-library/src/cover/edit.native.js @@ -40,8 +40,9 @@ import { MediaPlaceholder, MediaUpload, MediaUploadProgress, - withColors, - __experimentalUseGradient, + getColorObjectByColorValue, + getColorObjectByAttributeValues, + getGradientValueBySlug, useSetting, store as blockEditorStore, } from '@wordpress/block-editor'; @@ -83,13 +84,13 @@ const Cover = ( { getStylesFromColorScheme, isParentSelected, onFocus, - overlayColor, setAttributes, openGeneralSidebar, closeSettingsBottomSheet, isSelected, selectBlock, blockWidth, + hasInnerBlocks, } ) => { const { backgroundType, @@ -103,6 +104,9 @@ const Cover = ( { minHeightUnit = 'px', allowedBlocks, templateLock, + customGradient, + gradient, + overlayColor, } = attributes; const [ isScreenReaderEnabled, setIsScreenReaderEnabled ] = useState( false @@ -145,18 +149,24 @@ const Cover = ( { const coverDefaultPalette = { colors: colorsDefault.slice( 0, THEME_COLORS_COUNT ), }; - - const { gradientValue } = __experimentalUseGradient(); + const gradients = useSetting( 'color.gradients' ) || []; + const gradientValue = + customGradient || getGradientValueBySlug( gradients, gradient ); + const overlayColorValue = getColorObjectByAttributeValues( + colorsDefault, + overlayColor + ); const hasBackground = !! ( url || ( style && style.color && style.color.background ) || attributes.overlayColor || - overlayColor.color || + overlayColorValue.color || + customOverlayColor || gradientValue ); - const hasOnlyColorBackground = ! url && hasBackground; + const hasOnlyColorBackground = ! url && ( hasBackground || hasInnerBlocks ); const [ isCustomColorPickerShowing, @@ -225,10 +235,12 @@ const Cover = ( { }, [ closeSettingsBottomSheet ] ); function setColor( color ) { + const colorValue = getColorObjectByColorValue( colorsDefault, color ); + setAttributes( { // clear all related attributes (only one should be set) - overlayColor: undefined, - customOverlayColor: color, + overlayColor: colorValue?.slug ?? undefined, + customOverlayColor: ( ! colorValue?.slug && color ) ?? undefined, gradient: undefined, customGradient: undefined, } ); @@ -251,12 +263,12 @@ const Cover = ( { ! gradientValue && { backgroundColor: customOverlayColor || - overlayColor?.color || + overlayColorValue?.color || style?.color?.background || styles.overlay?.color, }, // While we don't support theme colors we add a default bg color - ! overlayColor.color && ! url ? backgroundColor : {}, + ! overlayColorValue.color && ! url ? backgroundColor : {}, isImage && isParentSelected && ! isUploadInProgress && @@ -432,7 +444,10 @@ const Cover = ( { </TouchableWithoutFeedback> ); - if ( ! hasBackground || isCustomColorPickerShowing ) { + if ( + ( ! hasBackground && ! hasInnerBlocks ) || + isCustomColorPickerShowing + ) { return ( <View> { isCustomColorPickerShowing && colorPickerControls } @@ -575,17 +590,21 @@ const Cover = ( { }; export default compose( [ - withColors( { overlayColor: 'background-color' } ), withSelect( ( select, { clientId } ) => { - const { getSelectedBlockClientId } = select( blockEditorStore ); + const { getSelectedBlockClientId, getBlock } = select( + blockEditorStore + ); const selectedBlockClientId = getSelectedBlockClientId(); const { getSettings } = select( blockEditorStore ); + const hasInnerBlocks = getBlock( clientId )?.innerBlocks.length > 0; + return { settings: getSettings(), isParentSelected: selectedBlockClientId === clientId, + hasInnerBlocks, }; } ), withDispatch( ( dispatch, { clientId } ) => { diff --git a/packages/block-library/src/cover/overlay-color-settings.native.js b/packages/block-library/src/cover/overlay-color-settings.native.js index 08fa32878f4c6..92e0739f119a4 100644 --- a/packages/block-library/src/cover/overlay-color-settings.native.js +++ b/packages/block-library/src/cover/overlay-color-settings.native.js @@ -74,6 +74,15 @@ function OverlayColorSettings( { } }; + const onColorCleared = () => { + setAttributes( { + overlayColor: undefined, + customOverlayColor: undefined, + gradient: undefined, + customGradient: undefined, + } ); + }; + return [ { label: __( 'Color' ), @@ -81,6 +90,7 @@ function OverlayColorSettings( { colorValue, gradientValue, onGradientChange, + onColorCleared, }, ]; }, [ colorValue, gradientValue, colors, gradients ] ); diff --git a/packages/block-library/src/cover/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/cover/test/__snapshots__/edit.native.js.snap new file mode 100644 index 0000000000000..0e9e5ad155faf --- /dev/null +++ b/packages/block-library/src/cover/test/__snapshots__/edit.native.js.snap @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`color settings clears the selected overlay color and mantains the inner blocks 1`] = ` +"<!-- wp:cover --> +<div class=\\"wp-block-cover\\"><span aria-hidden=\\"true\\" class=\\"has-background-dim-100 wp-block-cover__gradient-background has-background-dim\\"></span><div class=\\"wp-block-cover__inner-container\\"><!-- wp:paragraph {\\"align\\":\\"center\\",\\"placeholder\\":\\"Write title…\\"} --> +<p class=\\"has-text-align-center\\"></p> +<!-- /wp:paragraph --></div></div> +<!-- /wp:cover -->" +`; + +exports[`color settings sets a color for the overlay background when the placeholder is visible 1`] = ` +"<!-- wp:cover {\\"overlayColor\\":\\"vivid-red\\"} --> +<div class=\\"wp-block-cover\\"><span aria-hidden=\\"true\\" class=\\"has-vivid-red-background-color has-background-dim-100 wp-block-cover__gradient-background has-background-dim\\"></span><div class=\\"wp-block-cover__inner-container\\"><!-- wp:paragraph {\\"align\\":\\"center\\",\\"placeholder\\":\\"Write title…\\"} --> +<p class=\\"has-text-align-center\\"></p> +<!-- /wp:paragraph --></div></div> +<!-- /wp:cover -->" +`; + +exports[`color settings sets a gradient overlay background when a solid background was already selected 1`] = ` +"<!-- wp:cover {\\"gradient\\":\\"light-green-cyan-to-vivid-green-cyan\\"} --> +<div class=\\"wp-block-cover\\"><span aria-hidden=\\"true\\" class=\\"has-background-dim-100 wp-block-cover__gradient-background has-light-green-cyan-to-vivid-green-cyan-gradient-background has-background-dim has-background-gradient has-light-green-cyan-to-vivid-green-cyan-gradient-background\\"></span><div class=\\"wp-block-cover__inner-container\\"><!-- wp:paragraph {\\"align\\":\\"center\\",\\"placeholder\\":\\"Write title…\\"} --> +<p class=\\"has-text-align-center\\"></p> +<!-- /wp:paragraph --></div></div> +<!-- /wp:cover -->" +`; + +exports[`color settings toggles between solid colors and gradients 1`] = ` +"<!-- wp:cover {\\"gradient\\":\\"light-green-cyan-to-vivid-green-cyan\\"} --> +<div class=\\"wp-block-cover\\"><span aria-hidden=\\"true\\" class=\\"has-background-dim-100 wp-block-cover__gradient-background has-light-green-cyan-to-vivid-green-cyan-gradient-background has-background-dim has-background-gradient has-light-green-cyan-to-vivid-green-cyan-gradient-background\\"></span><div class=\\"wp-block-cover__inner-container\\"><!-- wp:paragraph {\\"align\\":\\"center\\",\\"placeholder\\":\\"Write title…\\"} --> +<p class=\\"has-text-align-center\\"></p> +<!-- /wp:paragraph --></div></div> +<!-- /wp:cover -->" +`; diff --git a/packages/block-library/src/cover/test/edit.native.js b/packages/block-library/src/cover/test/edit.native.js index eef5346026a0e..c09b21f3eb8ff 100644 --- a/packages/block-library/src/cover/test/edit.native.js +++ b/packages/block-library/src/cover/test/edit.native.js @@ -1,15 +1,22 @@ /** * External dependencies */ -import { Image } from 'react-native'; -import { render, fireEvent, waitFor } from 'test/helpers'; +import { AccessibilityInfo, Image } from 'react-native'; +import { + getEditorHtml, + initializeEditor, + render, + fireEvent, + waitFor, + within, +} from 'test/helpers'; /** * WordPress dependencies */ import { BottomSheetSettings, BlockEdit } from '@wordpress/block-editor'; import { SlotFillProvider } from '@wordpress/components'; -import { registerBlockType, unregisterBlockType } from '@wordpress/blocks'; +import { setDefaultBlockName, unregisterBlockType } from '@wordpress/blocks'; import { requestMediaPicker, requestMediaEditor, @@ -19,7 +26,9 @@ import { * Internal dependencies */ import { IMAGE_BACKGROUND_TYPE } from '../shared'; -import { metadata, settings, name } from '../index'; +import * as paragraph from '../../paragraph'; +import * as cover from '..'; +import { registerBlock } from '../..'; // Avoid errors due to mocked stylesheet files missing required selectors jest.mock( '@wordpress/compose', () => ( { @@ -33,10 +42,25 @@ jest.mock( '@wordpress/compose', () => ( { ) ), } ) ); +const COVER_BLOCK_PLACEHOLDER_HTML = `<!-- wp:cover --> +<div class="wp-block-cover"><span aria-hidden="true" class="has-background-dim-100 wp-block-cover__gradient-background has-background-dim"></span><div class="wp-block-cover__inner-container"></div></div> +<!-- /wp:cover -->`; +const COVER_BLOCK_SOLID_COLOR_HTML = `<!-- wp:cover {"overlayColor":"cyan-bluish-gray"} --> +<div class="wp-block-cover"><span aria-hidden="true" class="has-cyan-bluish-gray-background-color has-background-dim-100 wp-block-cover__gradient-background has-background-dim"></span><div class="wp-block-cover__inner-container"><!-- wp:paragraph {"align":"center","placeholder":"Write title…"} --> +<p class="has-text-align-center"></p> +<!-- /wp:paragraph --></div></div> +<!-- /wp:cover -->`; + +const COLOR_PINK = '#f78da7'; +const COLOR_RED = '#cf2e2e'; +const COLOR_GRAY = '#abb8c3'; +const GRADIENT_GREEN = + 'linear-gradient(135deg,rgb(122,220,180) 0%,rgb(0,208,130) 100%)'; + // Simplified tree to render Cover edit within slot const CoverEdit = ( props ) => ( <SlotFillProvider> - <BlockEdit isSelected name={ name } clientId={ 0 } { ...props } /> + <BlockEdit isSelected name={ cover.name } clientId={ 0 } { ...props } /> <BottomSheetSettings isVisible /> </SlotFillProvider> ); @@ -55,26 +79,24 @@ beforeAll( () => { const getSizeSpy = jest.spyOn( Image, 'getSize' ); getSizeSpy.mockImplementation( ( _url, callback ) => callback( 300, 200 ) ); + AccessibilityInfo.isScreenReaderEnabled.mockResolvedValue( + Promise.resolve( true ) + ); + // Register required blocks - registerBlockType( name, { - ...metadata, - ...settings, - } ); - registerBlockType( 'core/paragraph', { - category: 'text', - title: 'Paragraph', - edit: () => {}, - save: () => {}, - } ); + registerBlock( paragraph ); + registerBlock( cover ); + setDefaultBlockName( paragraph.name ); } ); afterAll( () => { // Restore mocks Image.getSize.mockRestore(); + AccessibilityInfo.isScreenReaderEnabled.mockReset(); // Clean up registered blocks - unregisterBlockType( name ); - unregisterBlockType( 'core/paragraph' ); + unregisterBlockType( paragraph.name ); + unregisterBlockType( cover.name ); } ); describe( 'when no media is attached', () => { @@ -270,3 +292,248 @@ describe( 'when an image is attached', () => { ); } ); } ); + +describe( 'color settings', () => { + it( 'sets a color for the overlay background when the placeholder is visible', async () => { + const { getByTestId, getByA11yLabel } = await initializeEditor( { + initialHtml: COVER_BLOCK_PLACEHOLDER_HTML, + } ); + + const block = await waitFor( () => + getByA11yLabel( 'Cover block. Empty' ) + ); + expect( block ).toBeDefined(); + + // Select a color from the placeholder palette + const colorPalette = await waitFor( () => + getByTestId( 'color-palette' ) + ); + const colorButton = within( colorPalette ).getByTestId( COLOR_PINK ); + + expect( colorButton ).toBeDefined(); + fireEvent.press( colorButton ); + + // Wait for the block to be created + const coverBlockWithOverlay = await waitFor( () => + getByA11yLabel( /Cover Block\. Row 1/ ) + ); + fireEvent.press( coverBlockWithOverlay ); + + // Open Block Settings + const settingsButton = await waitFor( () => + getByA11yLabel( 'Open Settings' ) + ); + fireEvent.press( settingsButton ); + + // Wait for Block Settings to be visible + const blockSettingsModal = getByTestId( 'block-settings-modal' ); + await waitFor( () => blockSettingsModal.props.isVisible ); + + // Open the overlay color settings + const colorOverlay = await waitFor( () => + getByA11yLabel( 'Color. Empty' ) + ); + expect( colorOverlay ).toBeDefined(); + fireEvent.press( colorOverlay ); + + // Find the selected color + const colorPaletteButton = await waitFor( () => + getByTestId( COLOR_PINK ) + ); + expect( colorPaletteButton ).toBeDefined(); + + // Select another color + const newColorButton = await waitFor( () => getByTestId( COLOR_RED ) ); + fireEvent.press( newColorButton ); + + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + + it( 'sets a gradient overlay background when a solid background was already selected', async () => { + const { getByTestId, getByA11yLabel } = await initializeEditor( { + initialHtml: COVER_BLOCK_SOLID_COLOR_HTML, + } ); + + // Wait for the block to be created + const coverBlock = await waitFor( () => + getByA11yLabel( /Cover Block\. Row 1/ ) + ); + expect( coverBlock ).toBeDefined(); + fireEvent.press( coverBlock ); + + // Open Block Settings + const settingsButton = await waitFor( () => + getByA11yLabel( 'Open Settings' ) + ); + fireEvent.press( settingsButton ); + + // Wait for Block Settings to be visible + const blockSettingsModal = getByTestId( 'block-settings-modal' ); + await waitFor( () => blockSettingsModal.props.isVisible ); + + // Open the overlay color settings + const colorOverlay = await waitFor( () => + getByA11yLabel( 'Color. Empty' ) + ); + expect( colorOverlay ).toBeDefined(); + fireEvent.press( colorOverlay ); + + // Find the selected color + const colorButton = await waitFor( () => getByTestId( COLOR_GRAY ) ); + expect( colorButton ).toBeDefined(); + + // Open the gradients + const gradientsButton = await waitFor( () => + getByA11yLabel( 'Gradient' ) + ); + expect( gradientsButton ).toBeDefined(); + + fireEvent( gradientsButton, 'layout', { + nativeEvent: { layout: { width: 80, height: 26 } }, + } ); + fireEvent.press( gradientsButton ); + + // Find the gradient color + const newGradientButton = await waitFor( () => + getByTestId( GRADIENT_GREEN ) + ); + expect( newGradientButton ).toBeDefined(); + fireEvent.press( newGradientButton ); + + // Dismiss the Block Settings modal + fireEvent( blockSettingsModal, 'backdropPress' ); + + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + + it( 'toggles between solid colors and gradients', async () => { + const { getByTestId, getByA11yLabel } = await initializeEditor( { + initialHtml: COVER_BLOCK_PLACEHOLDER_HTML, + } ); + + const block = await waitFor( () => + getByA11yLabel( 'Cover block. Empty' ) + ); + expect( block ).toBeDefined(); + + // Select a color from the placeholder palette + const colorPalette = await waitFor( () => + getByTestId( 'color-palette' ) + ); + const colorButton = within( colorPalette ).getByTestId( COLOR_PINK ); + + expect( colorButton ).toBeDefined(); + fireEvent.press( colorButton ); + + // Wait for the block to be created + const coverBlockWithOverlay = await waitFor( () => + getByA11yLabel( /Cover Block\. Row 1/ ) + ); + fireEvent.press( coverBlockWithOverlay ); + + // Open Block Settings + const settingsButton = await waitFor( () => + getByA11yLabel( 'Open Settings' ) + ); + fireEvent.press( settingsButton ); + + // Wait for Block Settings to be visible + const blockSettingsModal = getByTestId( 'block-settings-modal' ); + await waitFor( () => blockSettingsModal.props.isVisible ); + + // Open the overlay color settings + const colorOverlay = await waitFor( () => + getByA11yLabel( 'Color. Empty' ) + ); + expect( colorOverlay ).toBeDefined(); + fireEvent.press( colorOverlay ); + + // Find the selected color + const colorPaletteButton = await waitFor( () => + getByTestId( COLOR_PINK ) + ); + expect( colorPaletteButton ).toBeDefined(); + + // Select another color + const newColorButton = await waitFor( () => getByTestId( COLOR_RED ) ); + fireEvent.press( newColorButton ); + + // Open the gradients + const gradientsButton = await waitFor( () => + getByA11yLabel( 'Gradient' ) + ); + expect( gradientsButton ).toBeDefined(); + + fireEvent( gradientsButton, 'layout', { + nativeEvent: { layout: { width: 80, height: 26 } }, + } ); + fireEvent.press( gradientsButton ); + + // Find the gradient color + const newGradientButton = await waitFor( () => + getByTestId( GRADIENT_GREEN ) + ); + expect( newGradientButton ).toBeDefined(); + fireEvent.press( newGradientButton ); + + // Go back to the settings list + fireEvent.press( await waitFor( () => getByA11yLabel( 'Go back' ) ) ); + + // Find the color setting + const colorSetting = await waitFor( () => + getByA11yLabel( 'Color. Empty' ) + ); + expect( colorSetting ).toBeDefined(); + fireEvent.press( colorSetting ); + + // Dismiss the Block Settings modal + fireEvent( blockSettingsModal, 'backdropPress' ); + + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + + it( 'clears the selected overlay color and mantains the inner blocks', async () => { + const { + getByTestId, + getByA11yLabel, + getByText, + } = await initializeEditor( { + initialHtml: COVER_BLOCK_SOLID_COLOR_HTML, + } ); + + // Wait for the block to be created + const coverBlock = await waitFor( () => + getByA11yLabel( /Cover Block\. Row 1/ ) + ); + expect( coverBlock ).toBeDefined(); + fireEvent.press( coverBlock ); + + // Open Block Settings + const settingsButton = await waitFor( () => + getByA11yLabel( 'Open Settings' ) + ); + fireEvent.press( settingsButton ); + + // Wait for Block Settings to be visible + const blockSettingsModal = getByTestId( 'block-settings-modal' ); + await waitFor( () => blockSettingsModal.props.isVisible ); + + // Open the overlay color settings + const colorOverlay = await waitFor( () => + getByA11yLabel( 'Color. Empty' ) + ); + expect( colorOverlay ).toBeDefined(); + fireEvent.press( colorOverlay ); + + // Find the selected color + const colorButton = await waitFor( () => getByTestId( COLOR_GRAY ) ); + expect( colorButton ).toBeDefined(); + + // Reset the selected color + const resetButton = await waitFor( () => getByText( 'Reset' ) ); + expect( resetButton ).toBeDefined(); + fireEvent.press( resetButton ); + + expect( getEditorHtml() ).toMatchSnapshot(); + } ); +} ); diff --git a/packages/components/src/color-palette/index.native.js b/packages/components/src/color-palette/index.native.js index decfddf44b0ad..940e0cbbd1d03 100644 --- a/packages/components/src/color-palette/index.native.js +++ b/packages/components/src/color-palette/index.native.js @@ -219,6 +219,7 @@ function ColorPalette( { onScrollBeginDrag={ () => shouldEnableBottomSheetScroll( false ) } onScrollEndDrag={ () => shouldEnableBottomSheetScroll( true ) } ref={ scrollViewRef } + testID="color-palette" > { shouldShowCustomIndicator && ( <View @@ -266,6 +267,7 @@ function ColorPalette( { selected: isSelected( color ), } } accessibilityHint={ color } + testID={ color } > <Animated.View style={ { diff --git a/packages/components/src/mobile/color-settings/index.native.js b/packages/components/src/mobile/color-settings/index.native.js index 9c877782b9282..4997125b27a45 100644 --- a/packages/components/src/mobile/color-settings/index.native.js +++ b/packages/components/src/mobile/color-settings/index.native.js @@ -27,6 +27,7 @@ const ColorSettingsMemo = memo( colorValue, gradientValue, onGradientChange, + onColorCleared, label, hideNavigation, } ) => { @@ -44,6 +45,7 @@ const ColorSettingsMemo = memo( colorValue, gradientValue, onGradientChange, + onColorCleared, label, hideNavigation, } } diff --git a/packages/components/src/mobile/color-settings/palette.screen.native.js b/packages/components/src/mobile/color-settings/palette.screen.native.js index 9c6ae92486538..011eddf8a6041 100644 --- a/packages/components/src/mobile/color-settings/palette.screen.native.js +++ b/packages/components/src/mobile/color-settings/palette.screen.native.js @@ -36,6 +36,7 @@ const PaletteScreen = () => { label, onColorChange, onGradientChange, + onColorCleared, colorValue, defaultSettings, hideNavigation = false, @@ -85,6 +86,10 @@ const PaletteScreen = () => { } else { onGradientChange( '' ); } + + if ( onColorCleared ) { + onColorCleared(); + } } function onCustomPress() {