JS: findWidgetById

File: tests/js/unit/page-builder/findWidgetById.test.js Tests: findWidgetById in admin/assets/js/src/page-builder/utils/findWidgetById.js


What this function does

findWidgetById( sections, widgetId ) walks the entire page layout tree and returns the widget object with the matching ID, or null if it is not found.

It is called whenever the builder needs to act on a specific widget — opening its settings panel, deleting it, or moving it during drag-and-drop.


The column model: items[]

Top-level columns now use a unified items[] array that can hold widget items and nested row items in any order:

sections[]
  └─ rows[]
       └─ columns[]
            └─ items[]
                 ├─ { kind: 'widget', id, type, values }        ← widget at top-level
                 └─ { kind: 'nestedRow', id, columns[] }
                       └─ nestedColumn.widgets[]                 ← widgets inside nested rows

findWidgetById must search both paths:

  1. Iterate col.items[] and return any item where kind === 'widget' and id matches.
  2. For kind === 'nestedRow' items, iterate the nested row's columns[].widgets[] and check for a match there.

A simple flat search over a single array would silently miss widgets inside nested rows. That is the specific bug this test is designed to catch if the traversal logic is ever simplified incorrectly.


Tests

Finds a widget in a flat column

const widget   = { kind: 'widget', id: 'w-flat', type: 'text_block', values: {} };
const sections = [ section( [ row( [ column( [ widget ] ) ] ) ] ) ];

expect( findWidgetById( sections, 'w-flat' ) ).toBe( widget );

The straightforward case — widget item is directly inside column.items[].


Finds a widget inside a nested row

const widget = { id: 'w-nested', type: 'text_block', values: {} };
const nestedRowItem = {
    kind:    'nestedRow',
    id:      'nr-1',
    columns: [ { id: 'nc-1', widgets: [ widget ] } ],
};
const sections = [ section( [ row( [ column( [ nestedRowItem ] ) ] ) ] ) ];

expect( findWidgetById( sections, 'w-nested' ) ).toBe( widget );

The column's items[] contains a nestedRow item, not a direct widget. The function must follow the nestedRow.columns[].widgets[] path to find the widget.


Finds a widget above a nested row in the same column

const widgetAbove  = { kind: 'widget', id: 'w-above', ... };
const widgetInside = { id: 'w-inside', ... };
const nestedRowItem = { kind: 'nestedRow', id: 'nr-2', columns: [ { id: 'nc-2', widgets: [ widgetInside ] } ] };

// Column has a widget item first, then a nested row item.
const sections = [ section( [ row( [ column( [ widgetAbove, nestedRowItem ] ) ] ) ] ) ];

expect( findWidgetById( sections, 'w-above' ) ).toBe( widgetAbove );
expect( findWidgetById( sections, 'w-inside' ) ).toBe( widgetInside );

Confirms the function correctly handles columns where widgets and nested rows are mixed together — both paths are reachable within the same items[] iteration.


Returns null when the ID does not exist

const sections = [ section( [ row( [ column( [ makeWidget( 'w-other' ) ] ) ] ) ] ) ];

expect( findWidgetById( sections, 'does-not-exist' ) ).toBeNull();

Confirms the function returns null cleanly rather than throwing when the ID is not present anywhere in the tree.