-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Add block nesting governance schema updates and docs #43801
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add block nesting governance schema updates and docs #43801
Conversation
|
👋 Thanks for your first Pull Request and for helping build the future of Gutenberg and WordPress, @alecgeatches! In case you missed it, we'd love to have you join us in our Slack community, where we hold regularly weekly meetings open to anyone to coordinate with each other. If you want to learn more about WordPress development in general, check out the Core Handbook full of helpful information. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you so much for fixing all of those issues that the schema has! And adding the schema validation test is another huge win!
We couldn't find a
draft-04-compatible setup that would allow for this configuration. Instead, this usesdraft-07'spropertyNamesfeature workaround mentioned in the link above.
The example in that link can be achieved in draft-04, albeit not as cleanly as with draft-07's propertyNames.
Expand to reference the example from the link.
This fails because an object with both foo and bar doesn't pass both tests individually.
{
"type": "object",
"allOf": [
{ "$ref": "#/definitions/foo" },
{ "$ref": "#/definitions/bar" }
],
"definitions": {
"foo": {
"properties": {
"foo": { "type": "string" }
},
"additionalProperties": false
},
"bar": {
"properties": {
"bar": { "type": "number" }
},
"additionalProperties": false
}
}
}In draft-07 the merging is done like this.
{
"type": "object",
"allOf": [
{ "$ref": "#/definitions/foo" },
{ "$ref": "#/definitions/bar" }
],
"propertyNames": {
"anyOf": [
{ "$ref": "#/definitions/fooNames" },
{ "$ref": "#/definitions/barNames" }
]
},
"definitions": {
"foo": {
"properties": {
"foo": { "type": "string" }
}
},
"fooNames": { "enum": ["foo"] },
"bar": {
"properties": {
"bar": { "type": "number" }
}
},
"barNames": { "enum": ["bar"] }
}
}In draft-04, you have to manually merge the property names like this.
{
"type": "object",
"allOf": [
{ "$ref": "#/definitions/foo" },
{ "$ref": "#/definitions/bar" },
{
"properties": {
"foo": {},
"bar": {}
},
"additionalProperties": false
}
],
"definitions": {
"foo": {
"properties": {
"foo": { "type": "string" }
}
},
"bar": {
"properties": {
"bar": { "type": "number" }
}
}
}
}In the theme.json schema, we use the terminology *Complete when a definition includes the empty properties and "additionalProperties": false. It makes for a long list of definitions when you need to add every combination manually, but it is possible with to add all of them.
{
"type": "object",
"properties": {
"foo": { "$ref": "#/definitions/fooComplete" },
"bar": { "$ref": "#/definitions/barComplete" },
"foobar": { "$ref": "#/definitions/foobarComplete" }
},
"additionalProperties": false,
"definitions": {
"foo": {
"properties": {
"foo": { "type": "string" }
}
},
"fooComplete": {
"allOf": [
{ "$ref": "#/definitions/foo" },
{
"properties": { "foo": {} },
"additionalProperties": false
}
]
},
"bar": {
"properties": {
"bar": { "type": "number" }
}
},
"barComplete": {
"allOf": [
{ "$ref": "#/definitions/bar" },
{
"properties": { "bar": {} },
"additionalProperties": false
}
]
},
"foobar": {
"allOf": [
{ "$ref": "#/definitions/foo" },
{ "$ref": "#/definitions/bar" }
]
},
"foobarComplete": {
"allOf": [
{ "$ref": "#/definitions/foobar" },
{
"properties": {
"foo": {},
"bar": {}
},
"additionalProperties": false
}
]
}
}
}Unfortunately, with this method, all places that merge properties need to update their own list of properties when a new property is added to any of the children—draft-07's propertyNames would avoid the problem.
One more thing to consider is that the WordPress core JSON Schema validator used by the REST API only supports draft-04. If we ever wanted to utilize that for theme.json (we're currently doing custom validation), we'd also want to stick with draft-04.
See if you can refactor it to draft-04 using the method shown above. I personally think that the benefits of sticking to draft-04 are worth the maintenance burden that propertyNames helps with.
| } ); | ||
|
|
||
| test( 'schema valid', () => { | ||
| const result = ajv.validate( themeSchema, baseThemeJson ) || ajv.errors; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First off, thank you for adding tests here! Based on the number of things you've had to fix, this is sorely needed.
I think we could validate the schema directly rather than indirectly by validating data. I think you should be able to do something like this with AJV.
| const result = ajv.validate( themeSchema, baseThemeJson ) || ajv.errors; | |
| const result = ajv.validate( | |
| 'http://json-schema.org/draft-07/schema#', | |
| themeSchema | |
| ) || ajv.errors; |
| "properties": { | ||
| "core/archives": { | ||
| "description": "Archive block. Display a monthly archive of your posts. This block has no block-level settings", | ||
| "additionalProperties": false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The archives block does not have any block-level settings, so we should probably update this to add "type": "object" rather than removing "additionalProperties": false
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, that makes sense to me!
schemas/json/theme.json
Outdated
| }, | ||
| "core/audio": { | ||
| "$ref": "#/definitions/settingsPropertiesComplete" | ||
| "$ref": "#/definitions/settingsPropertiesNested" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If a block doesn't support inner blocks, should we allow nested properties?
We're avoiding the specific settings that are available to each block because blocks are getting new supports pretty often and there's a high maintenance overhead for keeping the specifics up to date, but blocks updating to support nesting happens pretty rarely, so I don't think it would be too big of a problem to use settingsPropertiesComplete for blocks that don't have inner blocks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a great idea - limiting which blocks allow nesting via the schema will also make the intellisense a lot more helpful.
|
@ajlende Thank you for all of the great feedback! I've applied your guidance using
|
|
Wouldn't it make sense to put the definition of blocks inside {
"color": {
"text": true
},
"blocks": {
"core/child-block": {
"color": {
"text": false
}
},
"core/parent-block": {
"color": {
"text": false
},
"blocks": {
"core/child-block": {
"color": {
"text": true
}
}
}
}
}
}If we skip And also, wouldn't that make the |
|
@luisherranz Thank you for the feedback! @ingeniumed and I have been discussing this. We agree it would definitely make the schema changes easier, but we’re not sure if nesting blocks is a cause for confusion:
From the perspective of the designer/developer in Your suggested approach would be good for simplifying the schema changes by namespacing the new changes to a @luisherranz We’d love to hear any follow-up thoughts on this or if anyone else can add in as well. Thank you! We’d also be interested in @dabowman’s opinion here on @luisherranz schema changes above from the designer perspective. |
|
Sure. Let's bring more opinions: cc @mtias, @mcsf, @youknowriad, @oandregal, @jorgefilipecosta. |
|
Given our chat with the Gutenberg IO team and @mcsf's comment here with a different approach, we're likely not going to need the schema changes for nesting. However, since the original test changes and schema fixes may provide some value, we're going to split that off into a different PR. We'll plan to keep this PR around for a bit until we're sure #43796 isn't going to be involved. Thank you! |
|
Please see #44252 for just the test changes to the existing schema. Thank you! |
|
Closing this issue as the related nesting governance in #43796 has been closed, and the unit test changes from this PR were merged separately in #44252. @ingeniumed has created another (smaller) PR in #45089 to address governance within a plugin. Thank you! |
This PR contains the schema updates to theme.json used in the block nesting implementation in #43796. That PR allows for nesting blocks in theme.json like this:
With the setup above, a
core/child-blockwill havecolor.text === truewhen nested in acore/parent-block. We've added this as a separate PR, since these schema changes are a bit more controversial and might be a different discussion.What?
There are a handful of related changes bundled here:
theme-schema.test.jsbased on existing block schema tests. The existing schema failed validation with Ajv's strict mode againstdraft-04, mostly due to some missingtypeidentifiers and a handful of other small changes.Why?
Block nesting support has been added to support the
theme.jsonparsing and block editor changes in #43796.While we were updating the schema, it seemed like a good idea to put the existing
schemas/json/theme.jsonfile under test to ensure nothing was getting broken. Since the existing schema did not pass Ajv'sdraft-04validation, some minor changes were made to fix the errors. These are covered in the How section.How
Validation changes
As discussed above, some minor changes were made to the original schema to make it validate correctly against
draft-04. To view the original errors, check out theadd/theme-schema-updates-and-docsbranch of the related fork and use the7bc6ee9commit to view the original errors. Running the test gives this validation result:Click to expand
$ npx wp-scripts test-unit-js --config test/unit/jest.config.js --no-cache theme-schema.test.js FAIL test/integration/theme-schema.test.js ● theme.json schema › schema valid strict mode: property core/archives matches pattern ^[a-z][a-z0-9-]*/[a-z][a-z0-9-]*$ (use allowMatchingProperties) 13 | 14 | test( 'schema valid', () => { > 15 | const result = ajv.validate( themeSchema, {} ) || ajv.errors; | ^ 16 | 17 | expect( result ).toBe( true ); 18 | } ); at checkStrictMode (node_modules/ajv/lib/compile/util.ts:211:28) at checkMatchingProperties (node_modules/ajv/lib/vocabularies/applicator/patternProperties.ts:54:26) at validatePatternProperties (node_modules/ajv/lib/vocabularies/applicator/patternProperties.ts:40:30) ... at Ajv.compile (node_modules/ajv/lib/core.ts:370:34) at Ajv.validate (node_modules/ajv/lib/core.ts:346:16) at Object.<anonymous> (test/integration/theme-schema.test.js:15:22) at runMicrotasks (<anonymous>) ● theme.json schema › schema valid expect(jest.fn()).not.toHaveWarned(expected) Expected mock function not to be called but it was called with: ["strict mode: missing type \"object\" for keyword \"properties\" at \"#/definitions/settingsPropertiesAppearanceTools\" (strictTypes)"] ["strict mode: missing type \"object\" for keyword \"properties\" at \"#/definitions/settingsPropertiesBorder\" (strictTypes)"] ["strict mode: missing type \"object\" for keyword \"properties\" at \"#/definitions/settingsPropertiesColor\" (strictTypes)"] ["strict mode: missing type \"object\" for keyword \"properties\" at \"#/definitions/settingsPropertiesLayout\" (strictTypes)"] ["strict mode: missing type \"object\" for keyword \"properties\" at \"#/definitions/settingsPropertiesSpacing\" (strictTypes)"] ["strict mode: missing type \"object\" for keyword \"properties\" at \"#/definitions/settingsPropertiesTypography\" (strictTypes)"] ["strict mode: use allowUnionTypes to allow union type keyword at \"#/definitions/settingsPropertiesTypography/properties/typography/properties/fontSizes/items/properties/fluid\" (strictTypes)"] ["strict mode: type \"integer\" not allowed by context \"string\" at \"#/definitions/settingsPropertiesTypography/properties/typography/properties/fontFamilies/items/properties/fontFace/items/properties/fontWeight/oneOf/1\" (strictTypes)"] ["strict mode: missing type \"object\" for keyword \"properties\" at \"#\" (strictTypes)"] ["strict mode: missing type \"object\" for keyword \"additionalProperties\" at \"#/properties/core~1archives\" (strictTypes)"] ["strict mode: missing type \"object\" for keyword \"properties\" at \"#/definitions/settingsPropertiesAppearanceTools\" (strictTypes)"] ["strict mode: missing type \"object\" for keyword \"properties\" at \"#/properties/core~1button/allOf/1\" (strictTypes)"] ["strict mode: missing type \"object\" for keyword \"properties\" at \"#/definitions/settingsPropertiesColor\" (strictTypes)"] ["strict mode: missing type \"object\" for keyword \"properties\" at \"#/definitions/settingsPropertiesLayout\" (strictTypes)"] ["strict mode: missing type \"object\" for keyword \"properties\" at \"#/definitions/settingsPropertiesSpacing\" (strictTypes)"] ["strict mode: missing type \"object\" for keyword \"properties\" at \"#/definitions/settingsPropertiesTypography\" (strictTypes)"] ["strict mode: use allowUnionTypes to allow union type keyword at \"#/definitions/settingsPropertiesTypography/properties/typography/properties/fontSizes/items/properties/fluid\" (strictTypes)"] ["strict mode: type \"integer\" not allowed by context \"string\" at \"#/definitions/settingsPropertiesTypography/properties/typography/properties/fontFamilies/items/properties/fontFace/items/properties/fontWeight/oneOf/1\" (strictTypes)"] 30 | function assertExpectedCalls() { 31 | if ( spy.assertionsNumber === 0 && spy.mock.calls.length > 0 ) { > 32 | expect( console ).not[ matcherName ](); | ^ 33 | } 34 | } 35 | at Object.assertExpectedCalls (packages/jest-console/src/index.js:32:4) at runMicrotasks (<anonymous>) at TestScheduler.scheduleTests (node_modules/@jest/core/build/TestScheduler.js:333:13) at runJest (node_modules/@jest/core/build/runJest.js:404:19) at _run10000 (node_modules/@jest/core/build/cli/index.js:320:7) at runCLI (node_modules/@jest/core/build/cli/index.js:173:3) Test Suites: 1 failed, 1 total Tests: 1 failed, 1 total Snapshots: 0 total Time: 2.093 s Ran all test suites matching /theme-schema.test.js/i.Those individual fixes are documented in these commits:
Fix missing property object types for errors like:
Remove type conflict in fontWeight for this error:
Refactor fontSizes.fluid into non-union type for this error:
Fix core/archives non-property block having additionalProperties: false for this error:
Some additional fixes in Fix stylesProperties type and Fix styleProperties border direction properties that cropped up after the previous fixes, mostly addressing additional
propertiesobject types missing. This also fixes theborderautogenerated documentation fortop/bottom/left/right.Nested styling changes
To support nested styling, some new definitions were added:
settingsBlocksPropertiesCompletenow wraps asettingsBlocksPropertiesdefinition using the style mentioned in the current PR by @ajlende.settingsNestedPropertiesCompletewas added, which wrapssettingsPropertiesandsettingsBlocksProperties.settingsBlocksProperties, blocks which support nesting usesettingsNestedPropertiesComplete, and all other core blocks default to usingsettingsProperties.There is some duplication due to
draft-04compatibility. For example, thecore/archivesblock settings definitions are repeated in these places:settingsBlocksProperties(used bysettingsBlocksPropertiesCompleteandsettingsNestedPropertiesComplete).settingsBlocksPropertiesComplete(used by the top levelsettings.blocksdefinition)settingsNestedPropertiesComplete(used by blocks that support nesting)As far as we're aware, this is the most succinct way to represent these nested properties in
draft-04. The previous version of this PR useddraft-07andpropertyNames, but after discussion with @ajlende it's been rewritten to bedraft-04compatible.Testing Instructions
To test theme validation, run:
Schema changes are manually testable by editing an existing theme.json file and setting the schema to the new
theme.jsonschema in this branch:Screenshots or screencast
Here's a quick demo showing nested block settings working with the
draft-07schema with autocompletion in VS Code:theme-json-block-nesting.mp4