From 17f99898398cd5ebea22c9c541d39ed3e74bde9b Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 17 Nov 2025 17:13:39 +0100 Subject: [PATCH 01/30] Block Bindings: Add unit test coverage for source --- .../editor/src/bindings/test/term-data.js | 776 ++++++++++++++++++ 1 file changed, 776 insertions(+) create mode 100644 packages/editor/src/bindings/test/term-data.js diff --git a/packages/editor/src/bindings/test/term-data.js b/packages/editor/src/bindings/test/term-data.js new file mode 100644 index 00000000000000..c57a6cdf113312 --- /dev/null +++ b/packages/editor/src/bindings/test/term-data.js @@ -0,0 +1,776 @@ +/** + * WordPress dependencies + */ +import { store as blockEditorStore } from '@wordpress/block-editor'; +import { store as coreDataStore } from '@wordpress/core-data'; + +/** + * Internal dependencies + */ +import termDataBindings from '../term-data'; + +describe( 'term-data bindings', () => { + describe( 'getValues', () => { + describe( 'for regular blocks using block context', () => { + let select; + beforeAll( () => { + select = ( store ) => { + if ( store === blockEditorStore ) { + return { + getBlockName: ( clientId ) => + clientId === '123abc456' + ? 'core/paragraph' + : undefined, + getBlockAttributes: () => ( {} ), + }; + } + if ( store === coreDataStore ) { + return { + getEntityRecord: ( kind, taxonomy, termId ) => { + if ( + kind === 'taxonomy' && + taxonomy === 'category' && + termId === 123 + ) { + return { + id: 123, + name: 'Technology', + slug: 'technology', + link: 'https://example.com/category/technology', + description: 'All about technology', + parent: 0, + count: 42, + }; + } + return null; + }, + }; + } + }; + } ); + + it( 'should return entity field values when they exist', () => { + const values = termDataBindings.getValues( { + select, + context: { + taxonomy: 'category', + termId: 123, + }, + bindings: { + content: { + source: 'core/term-data', + args: { field: 'name' }, + }, + url: { + source: 'core/term-data', + args: { field: 'link' }, + }, + description: { + source: 'core/term-data', + args: { field: 'description' }, + }, + }, + clientId: '123abc456', + } ); + + expect( values ).toStrictEqual( { + content: 'Technology', + url: 'https://example.com/category/technology', + description: 'All about technology', + } ); + } ); + + it( 'should return term ID from context when entity exists', () => { + const values = termDataBindings.getValues( { + select, + context: { + taxonomy: 'category', + termId: 123, + }, + bindings: { + id: { + source: 'core/term-data', + args: { field: 'id' }, + }, + }, + clientId: '123abc456', + } ); + + expect( values.id ).toBe( 123 ); + } ); + + it( 'should format count field with parentheses', () => { + const values = termDataBindings.getValues( { + select, + context: { + taxonomy: 'category', + termId: 123, + }, + bindings: { + count: { + source: 'core/term-data', + args: { field: 'count' }, + }, + }, + clientId: '123abc456', + } ); + + expect( values.count ).toBe( '(42)' ); + } ); + + it( 'should fall back to field key when entity does not exist', () => { + const values = termDataBindings.getValues( { + select, + context: { + taxonomy: 'category', + termId: 456, + }, + bindings: { + content: { + source: 'core/term-data', + args: { field: 'name' }, + }, + url: { + source: 'core/term-data', + args: { field: 'link' }, + }, + description: { + source: 'core/term-data', + args: { field: 'description' }, + }, + }, + clientId: '123abc456', + } ); + + expect( values ).toStrictEqual( { + content: 'name', + url: 'link', + description: 'description', + } ); + } ); + + it( 'should fall back to field key for unknown fields', () => { + const values = termDataBindings.getValues( { + select, + context: { + taxonomy: 'category', + termId: 123, + }, + bindings: { + content: { + source: 'core/term-data', + args: { field: 'unknown' }, + }, + }, + clientId: '123abc456', + } ); + + expect( values.content ).toBe( 'unknown' ); + } ); + } ); + + describe( 'when termData is provided in context', () => { + let select; + beforeAll( () => { + select = ( store ) => { + if ( store === blockEditorStore ) { + return { + getBlockName: () => 'core/paragraph', + getBlockAttributes: () => ( {} ), + }; + } + if ( store === coreDataStore ) { + return { + getEntityRecord: () => null, + }; + } + }; + } ); + + it( 'should use termData from context when entity record is not available', () => { + const values = termDataBindings.getValues( { + select, + context: { + taxonomy: 'category', + termId: 123, + termData: { + term_id: 123, + name: 'Design', + slug: 'design', + link: 'https://example.com/category/design', + description: 'Design resources', + parent: 0, + count: 15, + }, + }, + bindings: { + content: { + source: 'core/term-data', + args: { field: 'name' }, + }, + url: { + source: 'core/term-data', + args: { field: 'link' }, + }, + }, + clientId: '123abc456', + } ); + + expect( values ).toStrictEqual( { + content: 'Design', + url: 'https://example.com/category/design', + } ); + } ); + + it( 'should use termData when taxonomy and termId are not provided', () => { + const values = termDataBindings.getValues( { + select, + context: { + termData: { + term_id: 789, + name: 'News', + slug: 'news', + link: 'https://example.com/category/news', + }, + }, + bindings: { + id: { + source: 'core/term-data', + args: { field: 'id' }, + }, + content: { + source: 'core/term-data', + args: { field: 'name' }, + }, + }, + clientId: '123abc456', + } ); + + expect( values ).toStrictEqual( { + id: 789, + content: 'News', + } ); + } ); + } ); + + describe( 'for navigation blocks using block attributes', () => { + it( 'should use block attributes for navigation-link blocks', () => { + const select = ( store ) => { + if ( store === blockEditorStore ) { + return { + getBlockName: () => 'core/navigation-link', + getBlockAttributes: () => ( { + id: 456, + type: 'category', + } ), + }; + } + if ( store === coreDataStore ) { + return { + getEntityRecord: ( kind, taxonomy, termId ) => { + if ( + kind === 'taxonomy' && + taxonomy === 'category' && + termId === 456 + ) { + return { + id: 456, + name: 'Programming', + link: 'https://example.com/category/programming', + }; + } + return null; + }, + }; + } + }; + + const values = termDataBindings.getValues( { + select, + context: { + taxonomy: 'post_tag', + termId: 999, + }, + bindings: { + content: { + source: 'core/term-data', + args: { field: 'name' }, + }, + url: { + source: 'core/term-data', + args: { field: 'link' }, + }, + }, + clientId: '123abc456', + } ); + + expect( values ).toStrictEqual( { + content: 'Programming', + url: 'https://example.com/category/programming', + } ); + } ); + + it( 'should use block attributes for navigation-submenu blocks', () => { + const select = ( store ) => { + if ( store === blockEditorStore ) { + return { + getBlockName: () => 'core/navigation-submenu', + getBlockAttributes: () => ( { + id: 789, + type: 'category', + } ), + }; + } + if ( store === coreDataStore ) { + return { + getEntityRecord: ( kind, taxonomy, termId ) => { + if ( + kind === 'taxonomy' && + taxonomy === 'category' && + termId === 789 + ) { + return { + name: 'Tutorials', + }; + } + return null; + }, + }; + } + }; + + const values = termDataBindings.getValues( { + select, + context: {}, + bindings: { + content: { + source: 'core/term-data', + args: { field: 'name' }, + }, + }, + clientId: '123abc456', + } ); + + expect( values.content ).toBe( 'Tutorials' ); + } ); + + it( 'should convert "tag" type to "post_tag" taxonomy', () => { + const select = ( store ) => { + if ( store === blockEditorStore ) { + return { + getBlockName: () => 'core/navigation-link', + getBlockAttributes: () => ( { + id: 321, + type: 'tag', + } ), + }; + } + if ( store === coreDataStore ) { + return { + getEntityRecord: ( kind, taxonomy, termId ) => { + if ( + kind === 'taxonomy' && + taxonomy === 'post_tag' && + termId === 321 + ) { + return { + name: 'JavaScript', + }; + } + return null; + }, + }; + } + }; + + const values = termDataBindings.getValues( { + select, + context: {}, + bindings: { + content: { + source: 'core/term-data', + args: { field: 'name' }, + }, + }, + clientId: '123abc456', + } ); + + expect( values.content ).toBe( 'JavaScript' ); + } ); + } ); + } ); + + describe( 'setValues', () => { + it( 'should return false as terms are not editable', () => { + const result = termDataBindings.setValues( { + dispatch: jest.fn(), + context: { taxonomy: 'category', termId: 123 }, + bindings: { + content: { + args: { field: 'name' }, + }, + }, + } ); + + expect( result ).toBe( false ); + } ); + } ); + + describe( 'canUserEditValue', () => { + let select; + + it( 'should return false for navigation-link blocks', () => { + select = ( store ) => { + if ( store === blockEditorStore ) { + return { + getBlockName: () => 'core/navigation-link', + getSelectedBlockClientId: () => '123abc456', + }; + } + if ( store === coreDataStore ) { + return { + getEntityRecord: () => ( { + name: 'Test Category', + } ), + }; + } + }; + + const canEdit = termDataBindings.canUserEditValue( { + select, + context: { taxonomy: 'category', termId: 123 }, + args: { field: 'name' }, + } ); + + expect( canEdit ).toBe( false ); + } ); + + it( 'should return false for navigation-submenu blocks', () => { + select = ( store ) => { + if ( store === blockEditorStore ) { + return { + getBlockName: () => 'core/navigation-submenu', + getSelectedBlockClientId: () => '123abc456', + }; + } + if ( store === coreDataStore ) { + return { + getEntityRecord: () => ( { + name: 'Test Category', + } ), + }; + } + }; + + const canEdit = termDataBindings.canUserEditValue( { + select, + context: { taxonomy: 'category', termId: 123 }, + args: { field: 'name' }, + } ); + + expect( canEdit ).toBe( false ); + } ); + + it( 'should return false when termQuery is present in context', () => { + select = ( store ) => { + if ( store === blockEditorStore ) { + return { + getBlockName: () => 'core/paragraph', + getSelectedBlockClientId: () => '123abc456', + }; + } + if ( store === coreDataStore ) { + return { + getEntityRecord: () => ( { + name: 'Test Category', + } ), + }; + } + }; + + const canEdit = termDataBindings.canUserEditValue( { + select, + context: { + taxonomy: 'category', + termId: 123, + termQuery: { per_page: 10 }, + }, + args: { field: 'name' }, + } ); + + expect( canEdit ).toBe( false ); + } ); + + it( 'should return false when taxonomy is not defined', () => { + select = ( store ) => { + if ( store === blockEditorStore ) { + return { + getBlockName: () => 'core/paragraph', + getSelectedBlockClientId: () => '123abc456', + }; + } + if ( store === coreDataStore ) { + return { + getEntityRecord: () => null, + }; + } + }; + + const canEdit = termDataBindings.canUserEditValue( { + select, + context: { termId: 123 }, + args: { field: 'name' }, + } ); + + expect( canEdit ).toBe( false ); + } ); + + it( 'should return false when termId is not defined', () => { + select = ( store ) => { + if ( store === blockEditorStore ) { + return { + getBlockName: () => 'core/paragraph', + getSelectedBlockClientId: () => '123abc456', + }; + } + if ( store === coreDataStore ) { + return { + getEntityRecord: () => null, + }; + } + }; + + const canEdit = termDataBindings.canUserEditValue( { + select, + context: { taxonomy: 'category' }, + args: { field: 'name' }, + } ); + + expect( canEdit ).toBe( false ); + } ); + + it( 'should return false when field value is undefined', () => { + select = ( store ) => { + if ( store === blockEditorStore ) { + return { + getBlockName: () => 'core/paragraph', + getSelectedBlockClientId: () => '123abc456', + }; + } + if ( store === coreDataStore ) { + return { + getEntityRecord: () => null, + }; + } + }; + + const canEdit = termDataBindings.canUserEditValue( { + select, + context: { taxonomy: 'category', termId: 123 }, + args: { field: 'unknown_field' }, + } ); + + expect( canEdit ).toBe( false ); + } ); + + it( 'should return false even when field value exists', () => { + select = ( store ) => { + if ( store === blockEditorStore ) { + return { + getBlockName: () => 'core/paragraph', + getSelectedBlockClientId: () => '123abc456', + }; + } + if ( store === coreDataStore ) { + return { + getEntityRecord: () => ( { + name: 'Test Category', + slug: 'test-category', + } ), + }; + } + }; + + const canEdit = termDataBindings.canUserEditValue( { + select, + context: { taxonomy: 'category', termId: 123 }, + args: { field: 'name' }, + } ); + + expect( canEdit ).toBe( false ); + } ); + } ); + + describe( 'getFieldsList', () => { + it( 'should return the list of available term data fields when term data exists', () => { + const select = ( store ) => { + if ( store === blockEditorStore ) { + return { + getSelectedBlockClientId: () => '123abc456', + getBlockName: () => 'core/paragraph', + getBlockAttributes: () => ( {} ), + }; + } + if ( store === coreDataStore ) { + return { + getEntityRecord: ( kind, taxonomy, termId ) => { + if ( + kind === 'taxonomy' && + taxonomy === 'category' && + termId === 123 + ) { + return { + id: 123, + name: 'Technology', + slug: 'technology', + link: 'https://example.com/category/technology', + description: 'All about technology', + parent: 0, + count: 42, + }; + } + return null; + }, + }; + } + }; + + const fields = termDataBindings.getFieldsList( { + select, + context: { taxonomy: 'category', termId: 123 }, + } ); + + expect( fields ).toEqual( [ + { + label: 'Term ID', + type: 'string', + args: { field: 'id' }, + }, + { + label: 'Name', + type: 'string', + args: { field: 'name' }, + }, + { + label: 'Slug', + type: 'string', + args: { field: 'slug' }, + }, + { + label: 'Link', + type: 'string', + args: { field: 'link' }, + }, + { + label: 'Description', + type: 'string', + args: { field: 'description' }, + }, + { + label: 'Parent ID', + type: 'string', + args: { field: 'parent' }, + }, + { + label: 'Count', + type: 'string', + args: { field: 'count' }, + }, + ] ); + } ); + + it( 'should return empty array when no term data is available', () => { + const select = ( store ) => { + if ( store === blockEditorStore ) { + return { + getSelectedBlockClientId: () => '123abc456', + getBlockName: () => 'core/paragraph', + getBlockAttributes: () => ( {} ), + }; + } + if ( store === coreDataStore ) { + return { + getEntityRecord: () => null, + }; + } + }; + + const fields = termDataBindings.getFieldsList( { + select, + context: {}, + } ); + + expect( fields ).toEqual( [] ); + } ); + + it( 'should return fields when using termData from context', () => { + const select = ( store ) => { + if ( store === blockEditorStore ) { + return { + getSelectedBlockClientId: () => '123abc456', + getBlockName: () => 'core/paragraph', + getBlockAttributes: () => ( {} ), + }; + } + if ( store === coreDataStore ) { + return { + getEntityRecord: () => null, + }; + } + }; + + const fields = termDataBindings.getFieldsList( { + select, + context: { + termData: { + term_id: 456, + name: 'Design', + slug: 'design', + link: 'https://example.com/category/design', + description: 'Design resources', + parent: 0, + count: 15, + }, + }, + } ); + + expect( fields ).toEqual( [ + { + label: 'Term ID', + type: 'string', + args: { field: 'id' }, + }, + { + label: 'Name', + type: 'string', + args: { field: 'name' }, + }, + { + label: 'Slug', + type: 'string', + args: { field: 'slug' }, + }, + { + label: 'Link', + type: 'string', + args: { field: 'link' }, + }, + { + label: 'Description', + type: 'string', + args: { field: 'description' }, + }, + { + label: 'Parent ID', + type: 'string', + args: { field: 'parent' }, + }, + { + label: 'Count', + type: 'string', + args: { field: 'count' }, + }, + ] ); + } ); + } ); +} ); From 60d228f73361f49f60f8e0f152893c2c670c386b Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 17 Nov 2025 17:19:24 +0100 Subject: [PATCH 02/30] Hoist select for getFieldsList tests --- .../editor/src/bindings/test/term-data.js | 37 +++---------------- 1 file changed, 5 insertions(+), 32 deletions(-) diff --git a/packages/editor/src/bindings/test/term-data.js b/packages/editor/src/bindings/test/term-data.js index c57a6cdf113312..07caae9f473372 100644 --- a/packages/editor/src/bindings/test/term-data.js +++ b/packages/editor/src/bindings/test/term-data.js @@ -602,8 +602,9 @@ describe( 'term-data bindings', () => { } ); describe( 'getFieldsList', () => { - it( 'should return the list of available term data fields when term data exists', () => { - const select = ( store ) => { + let select; + beforeAll( () => { + select = ( store ) => { if ( store === blockEditorStore ) { return { getSelectedBlockClientId: () => '123abc456', @@ -634,7 +635,9 @@ describe( 'term-data bindings', () => { }; } }; + } ); + it( 'should return the list of available term data fields when term data exists', () => { const fields = termDataBindings.getFieldsList( { select, context: { taxonomy: 'category', termId: 123 }, @@ -680,21 +683,6 @@ describe( 'term-data bindings', () => { } ); it( 'should return empty array when no term data is available', () => { - const select = ( store ) => { - if ( store === blockEditorStore ) { - return { - getSelectedBlockClientId: () => '123abc456', - getBlockName: () => 'core/paragraph', - getBlockAttributes: () => ( {} ), - }; - } - if ( store === coreDataStore ) { - return { - getEntityRecord: () => null, - }; - } - }; - const fields = termDataBindings.getFieldsList( { select, context: {}, @@ -704,21 +692,6 @@ describe( 'term-data bindings', () => { } ); it( 'should return fields when using termData from context', () => { - const select = ( store ) => { - if ( store === blockEditorStore ) { - return { - getSelectedBlockClientId: () => '123abc456', - getBlockName: () => 'core/paragraph', - getBlockAttributes: () => ( {} ), - }; - } - if ( store === coreDataStore ) { - return { - getEntityRecord: () => null, - }; - } - }; - const fields = termDataBindings.getFieldsList( { select, context: { From 56be9ae4ee15a90c802f91165021aaf6500ad11c Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 18 Nov 2025 13:45:28 +0100 Subject: [PATCH 03/30] Check all field values, remove redundant tests --- .../editor/src/bindings/test/term-data.js | 60 +++++++------------ 1 file changed, 21 insertions(+), 39 deletions(-) diff --git a/packages/editor/src/bindings/test/term-data.js b/packages/editor/src/bindings/test/term-data.js index 07caae9f473372..ce9c60c73e95c8 100644 --- a/packages/editor/src/bindings/test/term-data.js +++ b/packages/editor/src/bindings/test/term-data.js @@ -57,11 +57,19 @@ describe( 'term-data bindings', () => { termId: 123, }, bindings: { - content: { + id: { + source: 'core/term-data', + args: { field: 'id' }, + }, + name: { source: 'core/term-data', args: { field: 'name' }, }, - url: { + slug: { + source: 'core/term-data', + args: { field: 'slug' }, + }, + link: { source: 'core/term-data', args: { field: 'link' }, }, @@ -69,44 +77,10 @@ describe( 'term-data bindings', () => { source: 'core/term-data', args: { field: 'description' }, }, - }, - clientId: '123abc456', - } ); - - expect( values ).toStrictEqual( { - content: 'Technology', - url: 'https://example.com/category/technology', - description: 'All about technology', - } ); - } ); - - it( 'should return term ID from context when entity exists', () => { - const values = termDataBindings.getValues( { - select, - context: { - taxonomy: 'category', - termId: 123, - }, - bindings: { - id: { + parent: { source: 'core/term-data', - args: { field: 'id' }, + args: { field: 'parent' }, }, - }, - clientId: '123abc456', - } ); - - expect( values.id ).toBe( 123 ); - } ); - - it( 'should format count field with parentheses', () => { - const values = termDataBindings.getValues( { - select, - context: { - taxonomy: 'category', - termId: 123, - }, - bindings: { count: { source: 'core/term-data', args: { field: 'count' }, @@ -115,7 +89,15 @@ describe( 'term-data bindings', () => { clientId: '123abc456', } ); - expect( values.count ).toBe( '(42)' ); + expect( values ).toStrictEqual( { + id: 123, + name: 'Technology', + slug: 'technology', + link: 'https://example.com/category/technology', + description: 'All about technology', + parent: 0, + count: '(42)', + } ); } ); it( 'should fall back to field key when entity does not exist', () => { From 910318e71a2292bd0ff87b2655e2d5c58744f8a0 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 18 Nov 2025 13:46:42 +0100 Subject: [PATCH 04/30] Cover all fields in field key fallback test --- .../editor/src/bindings/test/term-data.js | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/packages/editor/src/bindings/test/term-data.js b/packages/editor/src/bindings/test/term-data.js index ce9c60c73e95c8..7b9c5e14d1181e 100644 --- a/packages/editor/src/bindings/test/term-data.js +++ b/packages/editor/src/bindings/test/term-data.js @@ -108,11 +108,19 @@ describe( 'term-data bindings', () => { termId: 456, }, bindings: { - content: { + id: { + source: 'core/term-data', + args: { field: 'id' }, + }, + name: { source: 'core/term-data', args: { field: 'name' }, }, - url: { + slug: { + source: 'core/term-data', + args: { field: 'slug' }, + }, + link: { source: 'core/term-data', args: { field: 'link' }, }, @@ -120,14 +128,26 @@ describe( 'term-data bindings', () => { source: 'core/term-data', args: { field: 'description' }, }, + parent: { + source: 'core/term-data', + args: { field: 'parent' }, + }, + count: { + source: 'core/term-data', + args: { field: 'count' }, + }, }, clientId: '123abc456', } ); expect( values ).toStrictEqual( { - content: 'name', - url: 'link', + id: 'id', + name: 'name', + slug: 'slug', + link: 'link', description: 'description', + parent: 'parent', + count: 'count', } ); } ); From d45fba8f455fb28bd55c792f7c26eb60a235682c Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 19 Nov 2025 12:06:14 +0100 Subject: [PATCH 05/30] Fall back to undefined --- packages/editor/src/bindings/test/term-data.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/src/bindings/test/term-data.js b/packages/editor/src/bindings/test/term-data.js index 7b9c5e14d1181e..9fa78b28330eae 100644 --- a/packages/editor/src/bindings/test/term-data.js +++ b/packages/editor/src/bindings/test/term-data.js @@ -42,7 +42,7 @@ describe( 'term-data bindings', () => { count: 42, }; } - return null; + return undefined; }, }; } From 176fd43c519a9098e7ddce4dab785f8d6924fa6a Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 19 Nov 2025 12:07:03 +0100 Subject: [PATCH 06/30] Check for fallback to field name --- .../editor/src/bindings/test/term-data.js | 26 +++++-------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/packages/editor/src/bindings/test/term-data.js b/packages/editor/src/bindings/test/term-data.js index 9fa78b28330eae..0df4a7aa76d3d4 100644 --- a/packages/editor/src/bindings/test/term-data.js +++ b/packages/editor/src/bindings/test/term-data.js @@ -49,7 +49,7 @@ describe( 'term-data bindings', () => { }; } ); - it( 'should return entity field values when they exist', () => { + it( 'should return entity field values when they exist, and field name for unknown fields', () => { const values = termDataBindings.getValues( { select, context: { @@ -85,6 +85,10 @@ describe( 'term-data bindings', () => { source: 'core/term-data', args: { field: 'count' }, }, + content: { + source: 'core/term-data', + args: { field: 'unknown' }, + }, }, clientId: '123abc456', } ); @@ -97,6 +101,7 @@ describe( 'term-data bindings', () => { description: 'All about technology', parent: 0, count: '(42)', + content: 'unknown', } ); } ); @@ -150,25 +155,6 @@ describe( 'term-data bindings', () => { count: 'count', } ); } ); - - it( 'should fall back to field key for unknown fields', () => { - const values = termDataBindings.getValues( { - select, - context: { - taxonomy: 'category', - termId: 123, - }, - bindings: { - content: { - source: 'core/term-data', - args: { field: 'unknown' }, - }, - }, - clientId: '123abc456', - } ); - - expect( values.content ).toBe( 'unknown' ); - } ); } ); describe( 'when termData is provided in context', () => { From eddc7d17fd4c69a8c67f474296c361c82a570f1c Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 19 Nov 2025 15:55:19 +0100 Subject: [PATCH 07/30] Expect getValues() to fall back to field labels if present --- .../editor/src/bindings/test/term-data.js | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/editor/src/bindings/test/term-data.js b/packages/editor/src/bindings/test/term-data.js index 0df4a7aa76d3d4..cf6d304009bc7b 100644 --- a/packages/editor/src/bindings/test/term-data.js +++ b/packages/editor/src/bindings/test/term-data.js @@ -105,7 +105,7 @@ describe( 'term-data bindings', () => { } ); } ); - it( 'should fall back to field key when entity does not exist', () => { + it( 'should fall back to field label when entity does not exist, and to field name for unknown fields', () => { const values = termDataBindings.getValues( { select, context: { @@ -141,18 +141,23 @@ describe( 'term-data bindings', () => { source: 'core/term-data', args: { field: 'count' }, }, + content: { + source: 'core/term-data', + args: { field: 'unknown' }, + }, }, clientId: '123abc456', } ); expect( values ).toStrictEqual( { - id: 'id', - name: 'name', - slug: 'slug', - link: 'link', - description: 'description', - parent: 'parent', - count: 'count', + id: 'Term ID', + name: 'Name', + slug: 'Slug', + link: 'Link', + description: 'Description', + parent: 'Parent ID', + count: 'Count', + content: 'unknown', } ); } ); } ); From 170f85588bdca6f37dfaa4b5656905e6038a0931 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 3 Nov 2025 12:34:01 +0100 Subject: [PATCH 08/30] Block Bindings: Untangle core/term-data source --- packages/editor/src/bindings/term-data.js | 244 ++++++++-------------- 1 file changed, 92 insertions(+), 152 deletions(-) diff --git a/packages/editor/src/bindings/term-data.js b/packages/editor/src/bindings/term-data.js index 8fcb617f570c15..7b46720f7d3dae 100644 --- a/packages/editor/src/bindings/term-data.js +++ b/packages/editor/src/bindings/term-data.js @@ -11,131 +11,43 @@ const NAVIGATION_BLOCK_TYPES = [ 'core/navigation-submenu', ]; -/** - * Creates the data fields object with the given term data values and ID value. - * - * @param {Object} termDataValues The term data values. - * @param {string|number} idValue The ID value to use. - * @return {Object} The data fields object. - */ -function createDataFields( termDataValues, idValue ) { - return { - id: { - label: __( 'Term ID' ), - value: idValue, - type: 'string', - }, - name: { - label: __( 'Name' ), - value: termDataValues?.name, - type: 'string', - }, - slug: { - label: __( 'Slug' ), - value: termDataValues?.slug, - type: 'string', - }, - link: { - label: __( 'Link' ), - value: termDataValues?.link, - type: 'string', - }, - description: { - label: __( 'Description' ), - value: termDataValues?.description, - type: 'string', - }, - parent: { - label: __( 'Parent ID' ), - value: termDataValues?.parent, - type: 'string', - }, - count: { - label: __( 'Count' ), - value: `(${ termDataValues?.count ?? 0 })`, - type: 'string', - }, - }; -} - -/** - * Gets a list of term data fields with their values and labels - * to be consumed in the needed callbacks. - * If the value is not available based on context, like in templates, - * it falls back to the default value, label, or key. - * - * @param {Object} select The select function from the data store. - * @param {Object} context The context provided. - * @param {string} clientId The block client ID used to read attributes. - * @return {Object} List of term data fields with their value and label. - * - * @example - * ```js - * { - * name: { - * label: 'Term Name', - * value: 'Category Name', - * }, - * count: { - * label: 'Term Count', - * value: 5, - * }, - * ... - * } - * ``` - */ -function getTermDataFields( select, context, clientId ) { - const { getEntityRecord } = select( coreDataStore ); - const { getBlockAttributes, getBlockName } = select( blockEditorStore ); - - let termDataValues, dataFields; - - /* - * BACKWARDS COMPATIBILITY: Hardcoded exception for navigation blocks. - * Required for WordPress 6.9+ navigation blocks. DO NOT REMOVE. - */ - const blockName = getBlockName?.( clientId ); - const isNavigationBlock = NAVIGATION_BLOCK_TYPES.includes( blockName ); - - let termId, taxonomy; - - if ( isNavigationBlock ) { - // Navigation blocks: read from block attributes - const blockAttributes = getBlockAttributes?.( clientId ); - termId = blockAttributes?.id; - const typeFromAttributes = blockAttributes?.type; - taxonomy = - typeFromAttributes === 'tag' ? 'post_tag' : typeFromAttributes; - } else { - // All other blocks: use context - termId = context?.termId; - taxonomy = context?.taxonomy; - } - - if ( taxonomy && termId ) { - termDataValues = getEntityRecord( 'taxonomy', taxonomy, termId ); - - if ( ! termDataValues && context?.termData ) { - termDataValues = context.termData; - } - - if ( termDataValues ) { - dataFields = createDataFields( termDataValues, termId ); - } - } else if ( context?.termData ) { - termDataValues = context.termData; - dataFields = createDataFields( - termDataValues, - termDataValues?.term_id - ); - } - - if ( ! dataFields || ! Object.keys( dataFields ).length ) { - return null; - } - - return dataFields; -} +const termDataFields = [ + { + label: __( 'Term ID' ), + args: { field: 'id' }, + type: 'string', + }, + { + label: __( 'Name' ), + args: { field: 'name' }, + type: 'string', + }, + { + label: __( 'Slug' ), + args: { field: 'slug' }, + type: 'string', + }, + { + label: __( 'Link' ), + args: { field: 'link' }, + type: 'string', + }, + { + label: __( 'Description' ), + args: { field: 'description' }, + type: 'string', + }, + { + label: __( 'Parent ID' ), + args: { field: 'parent' }, + type: 'string', + }, + { + label: __( 'Count' ), + args: { field: 'count' }, // TODO: Fallback to zero + type: 'string', + }, +]; /** * @type {WPBlockBindingsSource} @@ -144,15 +56,60 @@ export default { name: 'core/term-data', usesContext: [ 'taxonomy', 'termId', 'termData' ], getValues( { select, context, bindings, clientId } ) { - const dataFields = getTermDataFields( select, context, clientId ); + const allowedFields = termDataFields.map( + ( field ) => field.args.field + ); + + /* + * BACKWARDS COMPATIBILITY: Hardcoded exception for navigation blocks. + * Required for WordPress 6.9+ navigation blocks. DO NOT REMOVE. + */ + const { getBlockAttributes, getBlockName } = select( blockEditorStore ); + const blockName = getBlockName?.( clientId ); + const isNavigationBlock = NAVIGATION_BLOCK_TYPES.includes( blockName ); + + let termId, taxonomy, termDataValues; + + if ( isNavigationBlock ) { + // Navigation blocks: read from block attributes + const blockAttributes = getBlockAttributes?.( clientId ); + termId = blockAttributes?.id; + const typeFromAttributes = blockAttributes?.type; + taxonomy = + typeFromAttributes === 'tag' ? 'post_tag' : typeFromAttributes; + } else if ( context.termId && context.taxonomy ) { + // All other blocks: use context + termId = context.termId; + taxonomy = context.taxonomy; + } else if ( context.termData ) { + // Fallback to context termData if available + termId = context.termData.term_id; + taxonomy = context.termData.taxonomy; + + termDataValues = context.termData; // TODO: Match field names. term_id -> id + } + + if ( taxonomy && termId && ! termDataValues ) { + const { getEntityRecord } = select( coreDataStore ); + termDataValues = getEntityRecord( 'taxonomy', taxonomy, termId ); + + if ( ! termDataValues && context?.termData ) { + termDataValues = context.termData; + } + } const newValues = {}; - for ( const [ attributeName, source ] of Object.entries( bindings ) ) { - // Use the value, the field label, or the field key. - const fieldKey = source.args.field; - const { value: fieldValue, label: fieldLabel } = - dataFields?.[ fieldKey ] || {}; - newValues[ attributeName ] = fieldValue ?? fieldLabel ?? fieldKey; + for ( const [ attributeName, binding ] of Object.entries( bindings ) ) { + if ( ! allowedFields.includes( binding.args.field ) ) { + newValues[ attributeName ] = {}; + continue; + } + + newValues[ attributeName ] = + termDataValues?.[ binding.args.field ] ?? + termDataFields.find( + ( field ) => field.args.field === binding.args.field + ).label; } return newValues; }, @@ -161,7 +118,7 @@ export default { // Terms are typically not editable through block bindings in most contexts. return false; }, - canUserEditValue( { select, context, args } ) { + canUserEditValue( { select, context } ) { const { getBlockName, getSelectedBlockClientId } = select( blockEditorStore ); @@ -184,26 +141,9 @@ export default { return false; } - const fieldValue = getTermDataFields( select, context, undefined )?.[ - args.field - ]?.value; - // Empty string or `false` could be a valid value, so we need to check if the field value is undefined. - if ( fieldValue === undefined ) { - return false; - } - return false; }, - getFieldsList( { select, context } ) { - const clientId = select( blockEditorStore ).getSelectedBlockClientId(); - const termDataFields = getTermDataFields( select, context, clientId ); - if ( ! termDataFields ) { - return []; - } - return Object.entries( termDataFields ).map( ( [ key, field ] ) => ( { - label: field.label, - type: field.type, - args: { field: key }, - } ) ); + getFieldsList() { + return termDataFields; }, }; From 3d4dcc58a0eff81f43cee5c3f0e70bfdd7c2e5a6 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 19 Nov 2025 16:07:43 +0100 Subject: [PATCH 09/30] Fall back to field name instead of empty object --- packages/editor/src/bindings/term-data.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/src/bindings/term-data.js b/packages/editor/src/bindings/term-data.js index 7b46720f7d3dae..263ce93f6674d0 100644 --- a/packages/editor/src/bindings/term-data.js +++ b/packages/editor/src/bindings/term-data.js @@ -101,7 +101,7 @@ export default { const newValues = {}; for ( const [ attributeName, binding ] of Object.entries( bindings ) ) { if ( ! allowedFields.includes( binding.args.field ) ) { - newValues[ attributeName ] = {}; + newValues[ attributeName ] = binding.args.field; continue; } From fe1ce94d7237a5dfd37c6cd90c9806c952939979 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 19 Nov 2025 16:09:09 +0100 Subject: [PATCH 10/30] Refactor for legibility --- packages/editor/src/bindings/term-data.js | 25 ++++++++++++----------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/editor/src/bindings/term-data.js b/packages/editor/src/bindings/term-data.js index 263ce93f6674d0..88fc6e10d7c09a 100644 --- a/packages/editor/src/bindings/term-data.js +++ b/packages/editor/src/bindings/term-data.js @@ -56,10 +56,6 @@ export default { name: 'core/term-data', usesContext: [ 'taxonomy', 'termId', 'termData' ], getValues( { select, context, bindings, clientId } ) { - const allowedFields = termDataFields.map( - ( field ) => field.args.field - ); - /* * BACKWARDS COMPATIBILITY: Hardcoded exception for navigation blocks. * Required for WordPress 6.9+ navigation blocks. DO NOT REMOVE. @@ -100,16 +96,21 @@ export default { const newValues = {}; for ( const [ attributeName, binding ] of Object.entries( bindings ) ) { - if ( ! allowedFields.includes( binding.args.field ) ) { + const termDataField = termDataFields.find( + ( field ) => field.args.field === binding.args.field + ); + + if ( ! termDataField ) { + // If the field is unknown, return the field name. newValues[ attributeName ] = binding.args.field; - continue; + } else if ( ! termDataValues ) { + // If the term data does not exist, return the field label. + newValues[ attributeName ] = termDataField.label; + } else { + // If the term data exists, return the term data value. + newValues[ attributeName ] = + termDataValues[ binding.args.field ]; } - - newValues[ attributeName ] = - termDataValues?.[ binding.args.field ] ?? - termDataFields.find( - ( field ) => field.args.field === binding.args.field - ).label; } return newValues; }, From 01d5be3a67da0cb3ec9daf0acc941fb23fc022a0 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 19 Nov 2025 16:12:44 +0100 Subject: [PATCH 11/30] Wrap term count in parens --- packages/editor/src/bindings/term-data.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/editor/src/bindings/term-data.js b/packages/editor/src/bindings/term-data.js index 88fc6e10d7c09a..2fb87ef019f779 100644 --- a/packages/editor/src/bindings/term-data.js +++ b/packages/editor/src/bindings/term-data.js @@ -106,6 +106,10 @@ export default { } else if ( ! termDataValues ) { // If the term data does not exist, return the field label. newValues[ attributeName ] = termDataField.label; + } else if ( binding.args.field === 'count' ) { + // Return the term count value in parentheses. + newValues[ attributeName ] = + '(' + termDataValues[ binding.args.field ] + ')'; } else { // If the term data exists, return the term data value. newValues[ attributeName ] = From 46b3aad0b63cc57307767bd934d83c2c3da664eb Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 19 Nov 2025 16:20:51 +0100 Subject: [PATCH 12/30] Return empty array from getFieldsList() if taxonomy/termId or termData isn't provided by context --- packages/editor/src/bindings/term-data.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/bindings/term-data.js b/packages/editor/src/bindings/term-data.js index 2fb87ef019f779..e63e74a5dbd41b 100644 --- a/packages/editor/src/bindings/term-data.js +++ b/packages/editor/src/bindings/term-data.js @@ -148,7 +148,15 @@ export default { return false; }, - getFieldsList() { - return termDataFields; + getFieldsList( { context } ) { + if ( ! context ) { + return termDataFields; + } + + if ( ( context.taxonomy && context.termId ) || context.termData ) { + return termDataFields; + } + + return []; }, }; From 12bca886ede81491969c3bc1ddba11dac6f486ce Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 19 Nov 2025 16:22:50 +0100 Subject: [PATCH 13/30] Make test more descriptive --- packages/editor/src/bindings/test/term-data.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/bindings/test/term-data.js b/packages/editor/src/bindings/test/term-data.js index cf6d304009bc7b..5badc63181256c 100644 --- a/packages/editor/src/bindings/test/term-data.js +++ b/packages/editor/src/bindings/test/term-data.js @@ -675,10 +675,10 @@ describe( 'term-data bindings', () => { ] ); } ); - it( 'should return empty array when no term data is available', () => { + it( 'should return empty array when neither termId nor termData is provided from context', () => { const fields = termDataBindings.getFieldsList( { select, - context: {}, + context: { taxonomy: 'category' }, } ); expect( fields ).toEqual( [] ); From 521f6c97093fa273d1b11d0af847df331237ade8 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 19 Nov 2025 16:23:33 +0100 Subject: [PATCH 14/30] Tweak test description --- packages/editor/src/bindings/test/term-data.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/src/bindings/test/term-data.js b/packages/editor/src/bindings/test/term-data.js index 5badc63181256c..6ac65c07ed4406 100644 --- a/packages/editor/src/bindings/test/term-data.js +++ b/packages/editor/src/bindings/test/term-data.js @@ -630,7 +630,7 @@ describe( 'term-data bindings', () => { }; } ); - it( 'should return the list of available term data fields when term data exists', () => { + it( 'should return the list of available term data fields when taxonomy and termId are provided by context', () => { const fields = termDataBindings.getFieldsList( { select, context: { taxonomy: 'category', termId: 123 }, From 259958070ef6ee7b4ccd8e204dcb06b213939f0c Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 19 Nov 2025 16:28:50 +0100 Subject: [PATCH 15/30] Remove obsolete TODO --- packages/editor/src/bindings/term-data.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/src/bindings/term-data.js b/packages/editor/src/bindings/term-data.js index e63e74a5dbd41b..15521096f523ce 100644 --- a/packages/editor/src/bindings/term-data.js +++ b/packages/editor/src/bindings/term-data.js @@ -44,7 +44,7 @@ const termDataFields = [ }, { label: __( 'Count' ), - args: { field: 'count' }, // TODO: Fallback to zero + args: { field: 'count' }, type: 'string', }, ]; From c51128b9b481a9663ca1c63c641078b610c6fcf8 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 19 Nov 2025 17:14:59 +0100 Subject: [PATCH 16/30] Tweak Navigation block test --- packages/editor/src/bindings/test/term-data.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/editor/src/bindings/test/term-data.js b/packages/editor/src/bindings/test/term-data.js index 6ac65c07ed4406..d59202d41daf61 100644 --- a/packages/editor/src/bindings/test/term-data.js +++ b/packages/editor/src/bindings/test/term-data.js @@ -247,7 +247,7 @@ describe( 'term-data bindings', () => { } ); describe( 'for navigation blocks using block attributes', () => { - it( 'should use block attributes for navigation-link blocks', () => { + it( 'should use block attributes instead of context', () => { const select = ( store ) => { if ( store === blockEditorStore ) { return { @@ -280,10 +280,7 @@ describe( 'term-data bindings', () => { const values = termDataBindings.getValues( { select, - context: { - taxonomy: 'post_tag', - termId: 999, - }, + context: {}, bindings: { content: { source: 'core/term-data', From 2fed16914b6a302a390a5da7caad3956d2f2e90e Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 19 Nov 2025 17:15:29 +0100 Subject: [PATCH 17/30] Remove redundant Navigation Submenu test --- .../editor/src/bindings/test/term-data.js | 44 ------------------- 1 file changed, 44 deletions(-) diff --git a/packages/editor/src/bindings/test/term-data.js b/packages/editor/src/bindings/test/term-data.js index d59202d41daf61..076367edb9bd70 100644 --- a/packages/editor/src/bindings/test/term-data.js +++ b/packages/editor/src/bindings/test/term-data.js @@ -300,50 +300,6 @@ describe( 'term-data bindings', () => { } ); } ); - it( 'should use block attributes for navigation-submenu blocks', () => { - const select = ( store ) => { - if ( store === blockEditorStore ) { - return { - getBlockName: () => 'core/navigation-submenu', - getBlockAttributes: () => ( { - id: 789, - type: 'category', - } ), - }; - } - if ( store === coreDataStore ) { - return { - getEntityRecord: ( kind, taxonomy, termId ) => { - if ( - kind === 'taxonomy' && - taxonomy === 'category' && - termId === 789 - ) { - return { - name: 'Tutorials', - }; - } - return null; - }, - }; - } - }; - - const values = termDataBindings.getValues( { - select, - context: {}, - bindings: { - content: { - source: 'core/term-data', - args: { field: 'name' }, - }, - }, - clientId: '123abc456', - } ); - - expect( values.content ).toBe( 'Tutorials' ); - } ); - it( 'should convert "tag" type to "post_tag" taxonomy', () => { const select = ( store ) => { if ( store === blockEditorStore ) { From ff7a920d08d98bf1bccb4ee0e2b8e45802a82e5b Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 19 Nov 2025 17:42:26 +0100 Subject: [PATCH 18/30] Compare to const imported from term-data.js --- packages/editor/src/bindings/term-data.js | 2 +- .../editor/src/bindings/test/term-data.js | 78 +------------------ 2 files changed, 4 insertions(+), 76 deletions(-) diff --git a/packages/editor/src/bindings/term-data.js b/packages/editor/src/bindings/term-data.js index 15521096f523ce..97694f5a481129 100644 --- a/packages/editor/src/bindings/term-data.js +++ b/packages/editor/src/bindings/term-data.js @@ -11,7 +11,7 @@ const NAVIGATION_BLOCK_TYPES = [ 'core/navigation-submenu', ]; -const termDataFields = [ +export const termDataFields = [ { label: __( 'Term ID' ), args: { field: 'id' }, diff --git a/packages/editor/src/bindings/test/term-data.js b/packages/editor/src/bindings/test/term-data.js index 076367edb9bd70..0bdf06f42aad4e 100644 --- a/packages/editor/src/bindings/test/term-data.js +++ b/packages/editor/src/bindings/test/term-data.js @@ -7,7 +7,7 @@ import { store as coreDataStore } from '@wordpress/core-data'; /** * Internal dependencies */ -import termDataBindings from '../term-data'; +import { default as termDataBindings, termDataFields } from '../term-data'; describe( 'term-data bindings', () => { describe( 'getValues', () => { @@ -589,43 +589,7 @@ describe( 'term-data bindings', () => { context: { taxonomy: 'category', termId: 123 }, } ); - expect( fields ).toEqual( [ - { - label: 'Term ID', - type: 'string', - args: { field: 'id' }, - }, - { - label: 'Name', - type: 'string', - args: { field: 'name' }, - }, - { - label: 'Slug', - type: 'string', - args: { field: 'slug' }, - }, - { - label: 'Link', - type: 'string', - args: { field: 'link' }, - }, - { - label: 'Description', - type: 'string', - args: { field: 'description' }, - }, - { - label: 'Parent ID', - type: 'string', - args: { field: 'parent' }, - }, - { - label: 'Count', - type: 'string', - args: { field: 'count' }, - }, - ] ); + expect( fields ).toEqual( termDataFields ); } ); it( 'should return empty array when neither termId nor termData is provided from context', () => { @@ -653,43 +617,7 @@ describe( 'term-data bindings', () => { }, } ); - expect( fields ).toEqual( [ - { - label: 'Term ID', - type: 'string', - args: { field: 'id' }, - }, - { - label: 'Name', - type: 'string', - args: { field: 'name' }, - }, - { - label: 'Slug', - type: 'string', - args: { field: 'slug' }, - }, - { - label: 'Link', - type: 'string', - args: { field: 'link' }, - }, - { - label: 'Description', - type: 'string', - args: { field: 'description' }, - }, - { - label: 'Parent ID', - type: 'string', - args: { field: 'parent' }, - }, - { - label: 'Count', - type: 'string', - args: { field: 'count' }, - }, - ] ); + expect( fields ).toEqual( termDataFields ); } ); } ); } ); From db85574ef873d487d918ca39c56ba5859a038eec Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 19 Nov 2025 18:06:52 +0100 Subject: [PATCH 19/30] Add test coverage for Navigation block --- packages/editor/src/bindings/term-data.js | 19 +- .../editor/src/bindings/test/term-data.js | 201 ++++++++++++------ 2 files changed, 159 insertions(+), 61 deletions(-) diff --git a/packages/editor/src/bindings/term-data.js b/packages/editor/src/bindings/term-data.js index 97694f5a481129..5946df564a81c6 100644 --- a/packages/editor/src/bindings/term-data.js +++ b/packages/editor/src/bindings/term-data.js @@ -148,7 +148,24 @@ export default { return false; }, - getFieldsList( { context } ) { + getFieldsList( { context, select } ) { + const { getBlockAttributes, getBlockName, getSelectedBlockClientId } = + select( blockEditorStore ); + const clientId = getSelectedBlockClientId(); + const blockName = getBlockName( clientId ); + + if ( NAVIGATION_BLOCK_TYPES.includes( blockName ) ) { + // Navigation blocks: read from block attributes + const blockAttributes = getBlockAttributes( clientId ); + if ( + ! blockAttributes || + ! blockAttributes.id || + ! blockAttributes.type + ) { + return []; + } + } + if ( ! context ) { return termDataFields; } diff --git a/packages/editor/src/bindings/test/term-data.js b/packages/editor/src/bindings/test/term-data.js index 0bdf06f42aad4e..7119ac765b4a7c 100644 --- a/packages/editor/src/bindings/test/term-data.js +++ b/packages/editor/src/bindings/test/term-data.js @@ -548,76 +548,157 @@ describe( 'term-data bindings', () => { } ); describe( 'getFieldsList', () => { - let select; - beforeAll( () => { - select = ( store ) => { - if ( store === blockEditorStore ) { - return { - getSelectedBlockClientId: () => '123abc456', - getBlockName: () => 'core/paragraph', - getBlockAttributes: () => ( {} ), - }; - } - if ( store === coreDataStore ) { - return { - getEntityRecord: ( kind, taxonomy, termId ) => { - if ( - kind === 'taxonomy' && - taxonomy === 'category' && - termId === 123 - ) { - return { - id: 123, - name: 'Technology', - slug: 'technology', - link: 'https://example.com/category/technology', - description: 'All about technology', - parent: 0, - count: 42, - }; - } - return null; - }, - }; - } - }; - } ); + describe( 'when a Navigation block is selected', () => { + it( 'should return the list of available term data fields if id and type attributes are present', () => { + const select = ( store ) => { + if ( store === blockEditorStore ) { + return { + getSelectedBlockClientId: () => '123abc456', + getBlockName: () => 'core/navigation-link', + getBlockAttributes: () => ( { + id: 123, + type: 'category', + } ), + }; + } + if ( store === coreDataStore ) { + return { + getEntityRecord: ( kind, taxonomy, termId ) => { + if ( + kind === 'taxonomy' && + taxonomy === 'category' && + termId === 123 + ) { + return { + id: 123, + name: 'Technology', + slug: 'technology', + link: 'https://example.com/category/technology', + description: 'All about technology', + parent: 0, + count: 42, + }; + } + return null; + }, + }; + } + }; + const fields = termDataBindings.getFieldsList( { select } ); - it( 'should return the list of available term data fields when taxonomy and termId are provided by context', () => { - const fields = termDataBindings.getFieldsList( { - select, - context: { taxonomy: 'category', termId: 123 }, + expect( fields ).toEqual( termDataFields ); } ); - expect( fields ).toEqual( termDataFields ); + it( 'should return an empty array if id or type attributes are missing', () => { + const select = ( store ) => { + if ( store === blockEditorStore ) { + return { + getSelectedBlockClientId: () => '123abc456', + getBlockName: () => 'core/navigation-link', + getBlockAttributes: () => ( { type: 'category' } ), + }; + } + if ( store === coreDataStore ) { + return { + getEntityRecord: ( kind, taxonomy, termId ) => { + if ( + kind === 'taxonomy' && + taxonomy === 'category' && + termId === 123 + ) { + return { + id: 123, + name: 'Technology', + slug: 'technology', + link: 'https://example.com/category/technology', + description: 'All about technology', + parent: 0, + count: 42, + }; + } + return null; + }, + }; + } + }; + const fields = termDataBindings.getFieldsList( { select } ); + + expect( fields ).toEqual( [] ); + } ); } ); - it( 'should return empty array when neither termId nor termData is provided from context', () => { - const fields = termDataBindings.getFieldsList( { - select, - context: { taxonomy: 'category' }, + describe( 'when a non-Navigation block is selected', () => { + let select; + beforeAll( () => { + select = ( store ) => { + if ( store === blockEditorStore ) { + return { + getSelectedBlockClientId: () => '123abc456', + getBlockName: () => 'core/paragraph', + getBlockAttributes: () => ( {} ), + }; + } + if ( store === coreDataStore ) { + return { + getEntityRecord: ( kind, taxonomy, termId ) => { + if ( + kind === 'taxonomy' && + taxonomy === 'category' && + termId === 123 + ) { + return { + id: 123, + name: 'Technology', + slug: 'technology', + link: 'https://example.com/category/technology', + description: 'All about technology', + parent: 0, + count: 42, + }; + } + return null; + }, + }; + } + }; } ); - expect( fields ).toEqual( [] ); - } ); + it( 'should return the list of available term data fields when taxonomy and termId are provided by context', () => { + const fields = termDataBindings.getFieldsList( { + select, + context: { taxonomy: 'category', termId: 123 }, + } ); - it( 'should return fields when using termData from context', () => { - const fields = termDataBindings.getFieldsList( { - select, - context: { - termData: { - term_id: 456, - name: 'Design', - slug: 'design', - link: 'https://example.com/category/design', - description: 'Design resources', - parent: 0, - count: 15, - }, - }, + expect( fields ).toEqual( termDataFields ); } ); - expect( fields ).toEqual( termDataFields ); + it( 'should return empty array when neither termId nor termData is provided from context', () => { + const fields = termDataBindings.getFieldsList( { + select, + context: { taxonomy: 'category' }, + } ); + + expect( fields ).toEqual( [] ); + } ); + + it( 'should return fields when using termData from context', () => { + const fields = termDataBindings.getFieldsList( { + select, + context: { + termData: { + term_id: 456, + name: 'Design', + slug: 'design', + link: 'https://example.com/category/design', + description: 'Design resources', + parent: 0, + count: 15, + }, + }, + } ); + + expect( fields ).toEqual( termDataFields ); + } ); } ); } ); } ); From 54f9a6bb56b9ecc97959d1ba930e00ca9d15997c Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 19 Nov 2025 18:10:40 +0100 Subject: [PATCH 20/30] Reuse variable --- .../editor/src/bindings/test/term-data.js | 95 +++++-------------- 1 file changed, 23 insertions(+), 72 deletions(-) diff --git a/packages/editor/src/bindings/test/term-data.js b/packages/editor/src/bindings/test/term-data.js index 7119ac765b4a7c..dcb39b517a5d31 100644 --- a/packages/editor/src/bindings/test/term-data.js +++ b/packages/editor/src/bindings/test/term-data.js @@ -10,6 +10,25 @@ import { store as coreDataStore } from '@wordpress/core-data'; import { default as termDataBindings, termDataFields } from '../term-data'; describe( 'term-data bindings', () => { + const getEntityRecordMock = ( kind, taxonomy, termId ) => { + if ( + kind === 'taxonomy' && + taxonomy === 'category' && + termId === 123 + ) { + return { + id: 123, + name: 'Technology', + slug: 'technology', + link: 'https://example.com/category/technology', + description: 'All about technology', + parent: 0, + count: 42, + }; + } + return undefined; + }; + describe( 'getValues', () => { describe( 'for regular blocks using block context', () => { let select; @@ -26,24 +45,7 @@ describe( 'term-data bindings', () => { } if ( store === coreDataStore ) { return { - getEntityRecord: ( kind, taxonomy, termId ) => { - if ( - kind === 'taxonomy' && - taxonomy === 'category' && - termId === 123 - ) { - return { - id: 123, - name: 'Technology', - slug: 'technology', - link: 'https://example.com/category/technology', - description: 'All about technology', - parent: 0, - count: 42, - }; - } - return undefined; - }, + getEntityRecord: getEntityRecordMock, }; } }; @@ -563,24 +565,7 @@ describe( 'term-data bindings', () => { } if ( store === coreDataStore ) { return { - getEntityRecord: ( kind, taxonomy, termId ) => { - if ( - kind === 'taxonomy' && - taxonomy === 'category' && - termId === 123 - ) { - return { - id: 123, - name: 'Technology', - slug: 'technology', - link: 'https://example.com/category/technology', - description: 'All about technology', - parent: 0, - count: 42, - }; - } - return null; - }, + getEntityRecord: getEntityRecordMock, }; } }; @@ -600,24 +585,7 @@ describe( 'term-data bindings', () => { } if ( store === coreDataStore ) { return { - getEntityRecord: ( kind, taxonomy, termId ) => { - if ( - kind === 'taxonomy' && - taxonomy === 'category' && - termId === 123 - ) { - return { - id: 123, - name: 'Technology', - slug: 'technology', - link: 'https://example.com/category/technology', - description: 'All about technology', - parent: 0, - count: 42, - }; - } - return null; - }, + getEntityRecord: getEntityRecordMock, }; } }; @@ -640,24 +608,7 @@ describe( 'term-data bindings', () => { } if ( store === coreDataStore ) { return { - getEntityRecord: ( kind, taxonomy, termId ) => { - if ( - kind === 'taxonomy' && - taxonomy === 'category' && - termId === 123 - ) { - return { - id: 123, - name: 'Technology', - slug: 'technology', - link: 'https://example.com/category/technology', - description: 'All about technology', - parent: 0, - count: 42, - }; - } - return null; - }, + getEntityRecord: getEntityRecordMock, }; } }; From dbba6bf2fa7eefe464d5e48a873fc8ee6b17356b Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 19 Nov 2025 18:11:58 +0100 Subject: [PATCH 21/30] Remove setValues and canUserEditValue tests --- .../editor/src/bindings/test/term-data.js | 201 ------------------ 1 file changed, 201 deletions(-) diff --git a/packages/editor/src/bindings/test/term-data.js b/packages/editor/src/bindings/test/term-data.js index dcb39b517a5d31..f199963b80d2b0 100644 --- a/packages/editor/src/bindings/test/term-data.js +++ b/packages/editor/src/bindings/test/term-data.js @@ -348,207 +348,6 @@ describe( 'term-data bindings', () => { } ); } ); - describe( 'setValues', () => { - it( 'should return false as terms are not editable', () => { - const result = termDataBindings.setValues( { - dispatch: jest.fn(), - context: { taxonomy: 'category', termId: 123 }, - bindings: { - content: { - args: { field: 'name' }, - }, - }, - } ); - - expect( result ).toBe( false ); - } ); - } ); - - describe( 'canUserEditValue', () => { - let select; - - it( 'should return false for navigation-link blocks', () => { - select = ( store ) => { - if ( store === blockEditorStore ) { - return { - getBlockName: () => 'core/navigation-link', - getSelectedBlockClientId: () => '123abc456', - }; - } - if ( store === coreDataStore ) { - return { - getEntityRecord: () => ( { - name: 'Test Category', - } ), - }; - } - }; - - const canEdit = termDataBindings.canUserEditValue( { - select, - context: { taxonomy: 'category', termId: 123 }, - args: { field: 'name' }, - } ); - - expect( canEdit ).toBe( false ); - } ); - - it( 'should return false for navigation-submenu blocks', () => { - select = ( store ) => { - if ( store === blockEditorStore ) { - return { - getBlockName: () => 'core/navigation-submenu', - getSelectedBlockClientId: () => '123abc456', - }; - } - if ( store === coreDataStore ) { - return { - getEntityRecord: () => ( { - name: 'Test Category', - } ), - }; - } - }; - - const canEdit = termDataBindings.canUserEditValue( { - select, - context: { taxonomy: 'category', termId: 123 }, - args: { field: 'name' }, - } ); - - expect( canEdit ).toBe( false ); - } ); - - it( 'should return false when termQuery is present in context', () => { - select = ( store ) => { - if ( store === blockEditorStore ) { - return { - getBlockName: () => 'core/paragraph', - getSelectedBlockClientId: () => '123abc456', - }; - } - if ( store === coreDataStore ) { - return { - getEntityRecord: () => ( { - name: 'Test Category', - } ), - }; - } - }; - - const canEdit = termDataBindings.canUserEditValue( { - select, - context: { - taxonomy: 'category', - termId: 123, - termQuery: { per_page: 10 }, - }, - args: { field: 'name' }, - } ); - - expect( canEdit ).toBe( false ); - } ); - - it( 'should return false when taxonomy is not defined', () => { - select = ( store ) => { - if ( store === blockEditorStore ) { - return { - getBlockName: () => 'core/paragraph', - getSelectedBlockClientId: () => '123abc456', - }; - } - if ( store === coreDataStore ) { - return { - getEntityRecord: () => null, - }; - } - }; - - const canEdit = termDataBindings.canUserEditValue( { - select, - context: { termId: 123 }, - args: { field: 'name' }, - } ); - - expect( canEdit ).toBe( false ); - } ); - - it( 'should return false when termId is not defined', () => { - select = ( store ) => { - if ( store === blockEditorStore ) { - return { - getBlockName: () => 'core/paragraph', - getSelectedBlockClientId: () => '123abc456', - }; - } - if ( store === coreDataStore ) { - return { - getEntityRecord: () => null, - }; - } - }; - - const canEdit = termDataBindings.canUserEditValue( { - select, - context: { taxonomy: 'category' }, - args: { field: 'name' }, - } ); - - expect( canEdit ).toBe( false ); - } ); - - it( 'should return false when field value is undefined', () => { - select = ( store ) => { - if ( store === blockEditorStore ) { - return { - getBlockName: () => 'core/paragraph', - getSelectedBlockClientId: () => '123abc456', - }; - } - if ( store === coreDataStore ) { - return { - getEntityRecord: () => null, - }; - } - }; - - const canEdit = termDataBindings.canUserEditValue( { - select, - context: { taxonomy: 'category', termId: 123 }, - args: { field: 'unknown_field' }, - } ); - - expect( canEdit ).toBe( false ); - } ); - - it( 'should return false even when field value exists', () => { - select = ( store ) => { - if ( store === blockEditorStore ) { - return { - getBlockName: () => 'core/paragraph', - getSelectedBlockClientId: () => '123abc456', - }; - } - if ( store === coreDataStore ) { - return { - getEntityRecord: () => ( { - name: 'Test Category', - slug: 'test-category', - } ), - }; - } - }; - - const canEdit = termDataBindings.canUserEditValue( { - select, - context: { taxonomy: 'category', termId: 123 }, - args: { field: 'name' }, - } ); - - expect( canEdit ).toBe( false ); - } ); - } ); - describe( 'getFieldsList', () => { describe( 'when a Navigation block is selected', () => { it( 'should return the list of available term data fields if id and type attributes are present', () => { From eac7e3eb269b4fe23438f55b3d50384238ca8e8c Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 24 Nov 2025 13:29:59 +0100 Subject: [PATCH 22/30] Remove unnecessary optional chaining --- packages/editor/src/bindings/term-data.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/editor/src/bindings/term-data.js b/packages/editor/src/bindings/term-data.js index 5946df564a81c6..795c91cc16d11d 100644 --- a/packages/editor/src/bindings/term-data.js +++ b/packages/editor/src/bindings/term-data.js @@ -61,14 +61,14 @@ export default { * Required for WordPress 6.9+ navigation blocks. DO NOT REMOVE. */ const { getBlockAttributes, getBlockName } = select( blockEditorStore ); - const blockName = getBlockName?.( clientId ); + const blockName = getBlockName( clientId ); const isNavigationBlock = NAVIGATION_BLOCK_TYPES.includes( blockName ); let termId, taxonomy, termDataValues; if ( isNavigationBlock ) { // Navigation blocks: read from block attributes - const blockAttributes = getBlockAttributes?.( clientId ); + const blockAttributes = getBlockAttributes( clientId ); termId = blockAttributes?.id; const typeFromAttributes = blockAttributes?.type; taxonomy = @@ -128,7 +128,7 @@ export default { select( blockEditorStore ); const clientId = getSelectedBlockClientId(); - const blockName = getBlockName?.( clientId ); + const blockName = getBlockName( clientId ); // Navigaton block types are read-only. // See https://github.com/WordPress/gutenberg/pull/72165. From d6ef32d77739f85f8d714317d2e7cb6a9c99c7d6 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 24 Nov 2025 13:33:24 +0100 Subject: [PATCH 23/30] Reorganize logic a bit --- packages/editor/src/bindings/term-data.js | 35 +++++++++++------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/packages/editor/src/bindings/term-data.js b/packages/editor/src/bindings/term-data.js index 795c91cc16d11d..a98c8936fde973 100644 --- a/packages/editor/src/bindings/term-data.js +++ b/packages/editor/src/bindings/term-data.js @@ -56,6 +56,8 @@ export default { name: 'core/term-data', usesContext: [ 'taxonomy', 'termId', 'termData' ], getValues( { select, context, bindings, clientId } ) { + const { getEntityRecord } = select( coreDataStore ); + /* * BACKWARDS COMPATIBILITY: Hardcoded exception for navigation blocks. * Required for WordPress 6.9+ navigation blocks. DO NOT REMOVE. @@ -64,34 +66,31 @@ export default { const blockName = getBlockName( clientId ); const isNavigationBlock = NAVIGATION_BLOCK_TYPES.includes( blockName ); - let termId, taxonomy, termDataValues; + let termDataValues; if ( isNavigationBlock ) { // Navigation blocks: read from block attributes const blockAttributes = getBlockAttributes( clientId ); - termId = blockAttributes?.id; const typeFromAttributes = blockAttributes?.type; - taxonomy = + const taxonomy = typeFromAttributes === 'tag' ? 'post_tag' : typeFromAttributes; + termDataValues = getEntityRecord( + 'taxonomy', + taxonomy, + blockAttributes?.id + ); } else if ( context.termId && context.taxonomy ) { // All other blocks: use context - termId = context.termId; - taxonomy = context.taxonomy; - } else if ( context.termData ) { - // Fallback to context termData if available - termId = context.termData.term_id; - taxonomy = context.termData.taxonomy; - - termDataValues = context.termData; // TODO: Match field names. term_id -> id + termDataValues = getEntityRecord( + 'taxonomy', + context.taxonomy, + context.termId + ); } - if ( taxonomy && termId && ! termDataValues ) { - const { getEntityRecord } = select( coreDataStore ); - termDataValues = getEntityRecord( 'taxonomy', taxonomy, termId ); - - if ( ! termDataValues && context?.termData ) { - termDataValues = context.termData; - } + // Fall back to context termData if available. + if ( ! termDataValues && context?.termData && ! isNavigationBlock ) { + termDataValues = context.termData; } const newValues = {}; From c6f0f623d95d8dec1e4156be6d6792dd32ca5042 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 24 Nov 2025 13:36:44 +0100 Subject: [PATCH 24/30] id, not termId --- packages/editor/src/bindings/test/term-data.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/editor/src/bindings/test/term-data.js b/packages/editor/src/bindings/test/term-data.js index f199963b80d2b0..25a295c4347dbd 100644 --- a/packages/editor/src/bindings/test/term-data.js +++ b/packages/editor/src/bindings/test/term-data.js @@ -189,7 +189,7 @@ describe( 'term-data bindings', () => { taxonomy: 'category', termId: 123, termData: { - term_id: 123, + id: 123, name: 'Design', slug: 'design', link: 'https://example.com/category/design', @@ -222,7 +222,7 @@ describe( 'term-data bindings', () => { select, context: { termData: { - term_id: 789, + id: 789, name: 'News', slug: 'news', link: 'https://example.com/category/news', @@ -436,7 +436,7 @@ describe( 'term-data bindings', () => { select, context: { termData: { - term_id: 456, + id: 456, name: 'Design', slug: 'design', link: 'https://example.com/category/design', From 7d779ba203117902152dc026dc97043478c91339 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 24 Nov 2025 13:42:51 +0100 Subject: [PATCH 25/30] Reuse termData --- .../editor/src/bindings/test/term-data.js | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/packages/editor/src/bindings/test/term-data.js b/packages/editor/src/bindings/test/term-data.js index 25a295c4347dbd..f35ccd693a64b6 100644 --- a/packages/editor/src/bindings/test/term-data.js +++ b/packages/editor/src/bindings/test/term-data.js @@ -166,6 +166,16 @@ describe( 'term-data bindings', () => { describe( 'when termData is provided in context', () => { let select; + + const termData = { + id: 456, + name: 'Design', + slug: 'design', + link: 'https://example.com/category/design', + description: 'Design resources', + parent: 0, + count: 15, + }; beforeAll( () => { select = ( store ) => { if ( store === blockEditorStore ) { @@ -187,16 +197,8 @@ describe( 'term-data bindings', () => { select, context: { taxonomy: 'category', - termId: 123, - termData: { - id: 123, - name: 'Design', - slug: 'design', - link: 'https://example.com/category/design', - description: 'Design resources', - parent: 0, - count: 15, - }, + termId: 456, + termData, }, bindings: { content: { @@ -221,12 +223,7 @@ describe( 'term-data bindings', () => { const values = termDataBindings.getValues( { select, context: { - termData: { - id: 789, - name: 'News', - slug: 'news', - link: 'https://example.com/category/news', - }, + termData, }, bindings: { id: { @@ -242,8 +239,8 @@ describe( 'term-data bindings', () => { } ); expect( values ).toStrictEqual( { - id: 789, - content: 'News', + id: 456, + content: 'Design', } ); } ); } ); From b981dfaecc29ca66914bf3bc49ac8ad53d532b60 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 24 Nov 2025 13:53:25 +0100 Subject: [PATCH 26/30] Rearrange tests --- .../editor/src/bindings/test/term-data.js | 376 +++++++++--------- 1 file changed, 189 insertions(+), 187 deletions(-) diff --git a/packages/editor/src/bindings/test/term-data.js b/packages/editor/src/bindings/test/term-data.js index f35ccd693a64b6..5fbd44829dcaf2 100644 --- a/packages/editor/src/bindings/test/term-data.js +++ b/packages/editor/src/bindings/test/term-data.js @@ -31,216 +31,218 @@ describe( 'term-data bindings', () => { describe( 'getValues', () => { describe( 'for regular blocks using block context', () => { - let select; - beforeAll( () => { - select = ( store ) => { - if ( store === blockEditorStore ) { - return { - getBlockName: ( clientId ) => - clientId === '123abc456' - ? 'core/paragraph' - : undefined, - getBlockAttributes: () => ( {} ), - }; - } - if ( store === coreDataStore ) { - return { - getEntityRecord: getEntityRecordMock, - }; - } - }; - } ); + describe( 'when termId and taxonomy are provided in context', () => { + let select; + beforeAll( () => { + select = ( store ) => { + if ( store === blockEditorStore ) { + return { + getBlockName: ( clientId ) => + clientId === '123abc456' + ? 'core/paragraph' + : undefined, + getBlockAttributes: () => ( {} ), + }; + } + if ( store === coreDataStore ) { + return { + getEntityRecord: getEntityRecordMock, + }; + } + }; + } ); - it( 'should return entity field values when they exist, and field name for unknown fields', () => { - const values = termDataBindings.getValues( { - select, - context: { - taxonomy: 'category', - termId: 123, - }, - bindings: { - id: { - source: 'core/term-data', - args: { field: 'id' }, - }, - name: { - source: 'core/term-data', - args: { field: 'name' }, - }, - slug: { - source: 'core/term-data', - args: { field: 'slug' }, - }, - link: { - source: 'core/term-data', - args: { field: 'link' }, - }, - description: { - source: 'core/term-data', - args: { field: 'description' }, - }, - parent: { - source: 'core/term-data', - args: { field: 'parent' }, - }, - count: { - source: 'core/term-data', - args: { field: 'count' }, + it( 'should return entity field values when they exist, and field name for unknown fields', () => { + const values = termDataBindings.getValues( { + select, + context: { + taxonomy: 'category', + termId: 123, }, - content: { - source: 'core/term-data', - args: { field: 'unknown' }, + bindings: { + id: { + source: 'core/term-data', + args: { field: 'id' }, + }, + name: { + source: 'core/term-data', + args: { field: 'name' }, + }, + slug: { + source: 'core/term-data', + args: { field: 'slug' }, + }, + link: { + source: 'core/term-data', + args: { field: 'link' }, + }, + description: { + source: 'core/term-data', + args: { field: 'description' }, + }, + parent: { + source: 'core/term-data', + args: { field: 'parent' }, + }, + count: { + source: 'core/term-data', + args: { field: 'count' }, + }, + content: { + source: 'core/term-data', + args: { field: 'unknown' }, + }, }, - }, - clientId: '123abc456', - } ); + clientId: '123abc456', + } ); - expect( values ).toStrictEqual( { - id: 123, - name: 'Technology', - slug: 'technology', - link: 'https://example.com/category/technology', - description: 'All about technology', - parent: 0, - count: '(42)', - content: 'unknown', + expect( values ).toStrictEqual( { + id: 123, + name: 'Technology', + slug: 'technology', + link: 'https://example.com/category/technology', + description: 'All about technology', + parent: 0, + count: '(42)', + content: 'unknown', + } ); } ); - } ); - it( 'should fall back to field label when entity does not exist, and to field name for unknown fields', () => { - const values = termDataBindings.getValues( { - select, - context: { - taxonomy: 'category', - termId: 456, - }, - bindings: { - id: { - source: 'core/term-data', - args: { field: 'id' }, - }, - name: { - source: 'core/term-data', - args: { field: 'name' }, - }, - slug: { - source: 'core/term-data', - args: { field: 'slug' }, - }, - link: { - source: 'core/term-data', - args: { field: 'link' }, - }, - description: { - source: 'core/term-data', - args: { field: 'description' }, - }, - parent: { - source: 'core/term-data', - args: { field: 'parent' }, + it( 'should fall back to field label when entity does not exist, and to field name for unknown fields', () => { + const values = termDataBindings.getValues( { + select, + context: { + taxonomy: 'category', + termId: 456, }, - count: { - source: 'core/term-data', - args: { field: 'count' }, - }, - content: { - source: 'core/term-data', - args: { field: 'unknown' }, + bindings: { + id: { + source: 'core/term-data', + args: { field: 'id' }, + }, + name: { + source: 'core/term-data', + args: { field: 'name' }, + }, + slug: { + source: 'core/term-data', + args: { field: 'slug' }, + }, + link: { + source: 'core/term-data', + args: { field: 'link' }, + }, + description: { + source: 'core/term-data', + args: { field: 'description' }, + }, + parent: { + source: 'core/term-data', + args: { field: 'parent' }, + }, + count: { + source: 'core/term-data', + args: { field: 'count' }, + }, + content: { + source: 'core/term-data', + args: { field: 'unknown' }, + }, }, - }, - clientId: '123abc456', - } ); + clientId: '123abc456', + } ); - expect( values ).toStrictEqual( { - id: 'Term ID', - name: 'Name', - slug: 'Slug', - link: 'Link', - description: 'Description', - parent: 'Parent ID', - count: 'Count', - content: 'unknown', + expect( values ).toStrictEqual( { + id: 'Term ID', + name: 'Name', + slug: 'Slug', + link: 'Link', + description: 'Description', + parent: 'Parent ID', + count: 'Count', + content: 'unknown', + } ); } ); } ); - } ); - describe( 'when termData is provided in context', () => { - let select; + describe( 'when termData is provided in context', () => { + let select; - const termData = { - id: 456, - name: 'Design', - slug: 'design', - link: 'https://example.com/category/design', - description: 'Design resources', - parent: 0, - count: 15, - }; - beforeAll( () => { - select = ( store ) => { - if ( store === blockEditorStore ) { - return { - getBlockName: () => 'core/paragraph', - getBlockAttributes: () => ( {} ), - }; - } - if ( store === coreDataStore ) { - return { - getEntityRecord: () => null, - }; - } + const termData = { + id: 456, + name: 'Design', + slug: 'design', + link: 'https://example.com/category/design', + description: 'Design resources', + parent: 0, + count: 15, }; - } ); + beforeAll( () => { + select = ( store ) => { + if ( store === blockEditorStore ) { + return { + getBlockName: () => 'core/paragraph', + getBlockAttributes: () => ( {} ), + }; + } + if ( store === coreDataStore ) { + return { + getEntityRecord: () => null, + }; + } + }; + } ); - it( 'should use termData from context when entity record is not available', () => { - const values = termDataBindings.getValues( { - select, - context: { - taxonomy: 'category', - termId: 456, - termData, - }, - bindings: { - content: { - source: 'core/term-data', - args: { field: 'name' }, + it( 'should use termData from context when entity record is not available', () => { + const values = termDataBindings.getValues( { + select, + context: { + taxonomy: 'category', + termId: 456, + termData, }, - url: { - source: 'core/term-data', - args: { field: 'link' }, + bindings: { + content: { + source: 'core/term-data', + args: { field: 'name' }, + }, + url: { + source: 'core/term-data', + args: { field: 'link' }, + }, }, - }, - clientId: '123abc456', - } ); + clientId: '123abc456', + } ); - expect( values ).toStrictEqual( { - content: 'Design', - url: 'https://example.com/category/design', + expect( values ).toStrictEqual( { + content: 'Design', + url: 'https://example.com/category/design', + } ); } ); - } ); - it( 'should use termData when taxonomy and termId are not provided', () => { - const values = termDataBindings.getValues( { - select, - context: { - termData, - }, - bindings: { - id: { - source: 'core/term-data', - args: { field: 'id' }, + it( 'should use termData when taxonomy and termId are not provided', () => { + const values = termDataBindings.getValues( { + select, + context: { + termData, }, - content: { - source: 'core/term-data', - args: { field: 'name' }, + bindings: { + id: { + source: 'core/term-data', + args: { field: 'id' }, + }, + content: { + source: 'core/term-data', + args: { field: 'name' }, + }, }, - }, - clientId: '123abc456', - } ); + clientId: '123abc456', + } ); - expect( values ).toStrictEqual( { - id: 456, - content: 'Design', + expect( values ).toStrictEqual( { + id: 456, + content: 'Design', + } ); } ); } ); } ); From dc632b79f9487cf2151cf2bdb4d4eef5b853660c Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 24 Nov 2025 13:55:38 +0100 Subject: [PATCH 27/30] No need for beforeAll --- .../editor/src/bindings/test/term-data.js | 95 +++++++++---------- 1 file changed, 43 insertions(+), 52 deletions(-) diff --git a/packages/editor/src/bindings/test/term-data.js b/packages/editor/src/bindings/test/term-data.js index 5fbd44829dcaf2..59b989667948b3 100644 --- a/packages/editor/src/bindings/test/term-data.js +++ b/packages/editor/src/bindings/test/term-data.js @@ -32,25 +32,22 @@ describe( 'term-data bindings', () => { describe( 'getValues', () => { describe( 'for regular blocks using block context', () => { describe( 'when termId and taxonomy are provided in context', () => { - let select; - beforeAll( () => { - select = ( store ) => { - if ( store === blockEditorStore ) { - return { - getBlockName: ( clientId ) => - clientId === '123abc456' - ? 'core/paragraph' - : undefined, - getBlockAttributes: () => ( {} ), - }; - } - if ( store === coreDataStore ) { - return { - getEntityRecord: getEntityRecordMock, - }; - } - }; - } ); + const select = ( store ) => { + if ( store === blockEditorStore ) { + return { + getBlockName: ( clientId ) => + clientId === '123abc456' + ? 'core/paragraph' + : undefined, + getBlockAttributes: () => ( {} ), + }; + } + if ( store === coreDataStore ) { + return { + getEntityRecord: getEntityRecordMock, + }; + } + }; it( 'should return entity field values when they exist, and field name for unknown fields', () => { const values = termDataBindings.getValues( { @@ -166,7 +163,19 @@ describe( 'term-data bindings', () => { } ); describe( 'when termData is provided in context', () => { - let select; + const select = ( store ) => { + if ( store === blockEditorStore ) { + return { + getBlockName: () => 'core/paragraph', + getBlockAttributes: () => ( {} ), + }; + } + if ( store === coreDataStore ) { + return { + getEntityRecord: () => null, + }; + } + }; const termData = { id: 456, @@ -177,21 +186,6 @@ describe( 'term-data bindings', () => { parent: 0, count: 15, }; - beforeAll( () => { - select = ( store ) => { - if ( store === blockEditorStore ) { - return { - getBlockName: () => 'core/paragraph', - getBlockAttributes: () => ( {} ), - }; - } - if ( store === coreDataStore ) { - return { - getEntityRecord: () => null, - }; - } - }; - } ); it( 'should use termData from context when entity record is not available', () => { const values = termDataBindings.getValues( { @@ -394,23 +388,20 @@ describe( 'term-data bindings', () => { } ); describe( 'when a non-Navigation block is selected', () => { - let select; - beforeAll( () => { - select = ( store ) => { - if ( store === blockEditorStore ) { - return { - getSelectedBlockClientId: () => '123abc456', - getBlockName: () => 'core/paragraph', - getBlockAttributes: () => ( {} ), - }; - } - if ( store === coreDataStore ) { - return { - getEntityRecord: getEntityRecordMock, - }; - } - }; - } ); + const select = ( store ) => { + if ( store === blockEditorStore ) { + return { + getSelectedBlockClientId: () => '123abc456', + getBlockName: () => 'core/paragraph', + getBlockAttributes: () => ( {} ), + }; + } + if ( store === coreDataStore ) { + return { + getEntityRecord: getEntityRecordMock, + }; + } + }; it( 'should return the list of available term data fields when taxonomy and termId are provided by context', () => { const fields = termDataBindings.getFieldsList( { From 91bacbe5a38d0d0dfe16593d9547683c06bfce40 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 24 Nov 2025 14:17:05 +0100 Subject: [PATCH 28/30] Check all fields --- .../editor/src/bindings/test/term-data.js | 176 +++++++----------- 1 file changed, 70 insertions(+), 106 deletions(-) diff --git a/packages/editor/src/bindings/test/term-data.js b/packages/editor/src/bindings/test/term-data.js index 59b989667948b3..860cf20ce67190 100644 --- a/packages/editor/src/bindings/test/term-data.js +++ b/packages/editor/src/bindings/test/term-data.js @@ -10,6 +10,41 @@ import { store as coreDataStore } from '@wordpress/core-data'; import { default as termDataBindings, termDataFields } from '../term-data'; describe( 'term-data bindings', () => { + const bindings = { + id: { + source: 'core/term-data', + args: { field: 'id' }, + }, + name: { + source: 'core/term-data', + args: { field: 'name' }, + }, + slug: { + source: 'core/term-data', + args: { field: 'slug' }, + }, + link: { + source: 'core/term-data', + args: { field: 'link' }, + }, + description: { + source: 'core/term-data', + args: { field: 'description' }, + }, + parent: { + source: 'core/term-data', + args: { field: 'parent' }, + }, + count: { + source: 'core/term-data', + args: { field: 'count' }, + }, + content: { + source: 'core/term-data', + args: { field: 'unknown' }, + }, + }; + const getEntityRecordMock = ( kind, taxonomy, termId ) => { if ( kind === 'taxonomy' && @@ -56,40 +91,7 @@ describe( 'term-data bindings', () => { taxonomy: 'category', termId: 123, }, - bindings: { - id: { - source: 'core/term-data', - args: { field: 'id' }, - }, - name: { - source: 'core/term-data', - args: { field: 'name' }, - }, - slug: { - source: 'core/term-data', - args: { field: 'slug' }, - }, - link: { - source: 'core/term-data', - args: { field: 'link' }, - }, - description: { - source: 'core/term-data', - args: { field: 'description' }, - }, - parent: { - source: 'core/term-data', - args: { field: 'parent' }, - }, - count: { - source: 'core/term-data', - args: { field: 'count' }, - }, - content: { - source: 'core/term-data', - args: { field: 'unknown' }, - }, - }, + bindings, clientId: '123abc456', } ); @@ -112,40 +114,7 @@ describe( 'term-data bindings', () => { taxonomy: 'category', termId: 456, }, - bindings: { - id: { - source: 'core/term-data', - args: { field: 'id' }, - }, - name: { - source: 'core/term-data', - args: { field: 'name' }, - }, - slug: { - source: 'core/term-data', - args: { field: 'slug' }, - }, - link: { - source: 'core/term-data', - args: { field: 'link' }, - }, - description: { - source: 'core/term-data', - args: { field: 'description' }, - }, - parent: { - source: 'core/term-data', - args: { field: 'parent' }, - }, - count: { - source: 'core/term-data', - args: { field: 'count' }, - }, - content: { - source: 'core/term-data', - args: { field: 'unknown' }, - }, - }, + bindings, clientId: '123abc456', } ); @@ -195,22 +164,19 @@ describe( 'term-data bindings', () => { termId: 456, termData, }, - bindings: { - content: { - source: 'core/term-data', - args: { field: 'name' }, - }, - url: { - source: 'core/term-data', - args: { field: 'link' }, - }, - }, + bindings, clientId: '123abc456', } ); expect( values ).toStrictEqual( { - content: 'Design', - url: 'https://example.com/category/design', + id: 456, + name: 'Design', + slug: 'design', + link: 'https://example.com/category/design', + description: 'Design resources', + parent: 0, + count: '(15)', + content: 'unknown', } ); } ); @@ -220,22 +186,19 @@ describe( 'term-data bindings', () => { context: { termData, }, - bindings: { - id: { - source: 'core/term-data', - args: { field: 'id' }, - }, - content: { - source: 'core/term-data', - args: { field: 'name' }, - }, - }, + bindings, clientId: '123abc456', } ); expect( values ).toStrictEqual( { id: 456, - content: 'Design', + name: 'Design', + slug: 'design', + link: 'https://example.com/category/design', + description: 'Design resources', + parent: 0, + count: '(15)', + content: 'unknown', } ); } ); } ); @@ -248,7 +211,7 @@ describe( 'term-data bindings', () => { return { getBlockName: () => 'core/navigation-link', getBlockAttributes: () => ( { - id: 456, + id: 789, type: 'category', } ), }; @@ -259,12 +222,16 @@ describe( 'term-data bindings', () => { if ( kind === 'taxonomy' && taxonomy === 'category' && - termId === 456 + termId === 789 ) { return { - id: 456, + id: 789, name: 'Programming', + slug: 'programming', link: 'https://example.com/category/programming', + description: 'Programming resources', + parent: 0, + count: 10, }; } return null; @@ -276,22 +243,19 @@ describe( 'term-data bindings', () => { const values = termDataBindings.getValues( { select, context: {}, - bindings: { - content: { - source: 'core/term-data', - args: { field: 'name' }, - }, - url: { - source: 'core/term-data', - args: { field: 'link' }, - }, - }, + bindings, clientId: '123abc456', } ); expect( values ).toStrictEqual( { - content: 'Programming', - url: 'https://example.com/category/programming', + id: 789, + name: 'Programming', + slug: 'programming', + link: 'https://example.com/category/programming', + description: 'Programming resources', + parent: 0, + count: '(10)', + content: 'unknown', } ); } ); From 4f0d9e89e6dea6a8202abcac7b834ea7530e7d48 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 24 Nov 2025 14:19:47 +0100 Subject: [PATCH 29/30] Fall back to field label --- packages/editor/src/bindings/term-data.js | 5 ++++- packages/editor/src/bindings/test/term-data.js | 10 ++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/editor/src/bindings/term-data.js b/packages/editor/src/bindings/term-data.js index a98c8936fde973..e13f4e76405d74 100644 --- a/packages/editor/src/bindings/term-data.js +++ b/packages/editor/src/bindings/term-data.js @@ -102,7 +102,10 @@ export default { if ( ! termDataField ) { // If the field is unknown, return the field name. newValues[ attributeName ] = binding.args.field; - } else if ( ! termDataValues ) { + } else if ( + ! termDataValues || + termDataValues[ binding.args.field ] === undefined + ) { // If the term data does not exist, return the field label. newValues[ attributeName ] = termDataField.label; } else if ( binding.args.field === 'count' ) { diff --git a/packages/editor/src/bindings/test/term-data.js b/packages/editor/src/bindings/test/term-data.js index 860cf20ce67190..3df62585c40e9e 100644 --- a/packages/editor/src/bindings/test/term-data.js +++ b/packages/editor/src/bindings/test/term-data.js @@ -56,7 +56,6 @@ describe( 'term-data bindings', () => { name: 'Technology', slug: 'technology', link: 'https://example.com/category/technology', - description: 'All about technology', parent: 0, count: 42, }; @@ -84,7 +83,7 @@ describe( 'term-data bindings', () => { } }; - it( 'should return entity field values when they exist, and field name for unknown fields', () => { + it( 'should return entity field values when they exist, fall back to field label, and to field name for unknown fields', () => { const values = termDataBindings.getValues( { select, context: { @@ -100,7 +99,7 @@ describe( 'term-data bindings', () => { name: 'Technology', slug: 'technology', link: 'https://example.com/category/technology', - description: 'All about technology', + description: 'Description', parent: 0, count: '(42)', content: 'unknown', @@ -149,7 +148,6 @@ describe( 'term-data bindings', () => { const termData = { id: 456, name: 'Design', - slug: 'design', link: 'https://example.com/category/design', description: 'Design resources', parent: 0, @@ -171,7 +169,7 @@ describe( 'term-data bindings', () => { expect( values ).toStrictEqual( { id: 456, name: 'Design', - slug: 'design', + slug: 'Slug', link: 'https://example.com/category/design', description: 'Design resources', parent: 0, @@ -193,7 +191,7 @@ describe( 'term-data bindings', () => { expect( values ).toStrictEqual( { id: 456, name: 'Design', - slug: 'design', + slug: 'Slug', link: 'https://example.com/category/design', description: 'Design resources', parent: 0, From 0f3e1df8ac6b1226f96fe37e7b9cc84c5b97bfaf Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 24 Nov 2025 14:58:04 +0100 Subject: [PATCH 30/30] Fix logic --- packages/editor/src/bindings/term-data.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/editor/src/bindings/term-data.js b/packages/editor/src/bindings/term-data.js index e13f4e76405d74..def4e7a8188ec8 100644 --- a/packages/editor/src/bindings/term-data.js +++ b/packages/editor/src/bindings/term-data.js @@ -166,10 +166,11 @@ export default { ) { return []; } + return termDataFields; } if ( ! context ) { - return termDataFields; + return []; } if ( ( context.taxonomy && context.termId ) || context.termData ) {