Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,40 @@
});
});

describe('select only', () => {
function getRow(id: string): HTMLElement {
return element.shadowRoot!.querySelector(`uui-table-row[data-sortable-id="${id}"]`) as HTMLElement;
}

it('turns every row into select-only when a selection exists', async () => {

Check failure on line 111 in src/Umbraco.Web.UI.Client/src/packages/core/components/table/table.element.test.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add at least one assertion to this test case.

See more on https://sonarcloud.io/project/issues?id=umbraco_Umbraco-CMS&issues=AZ_bl0-bnhynfjhWsBKJ&open=AZ_bl0-bnhynfjhWsBKJ&pullRequest=23593
element.items = items(['1', '2']);
element.selection = ['1'];
await element.updateComplete;

expect(getRow('1').hasAttribute('select-only')).to.be.true;
expect(getRow('2').hasAttribute('select-only')).to.be.true;
});

it('keeps a row interactive when it opts out, even while a selection exists', async () => {

Check failure on line 120 in src/Umbraco.Web.UI.Client/src/packages/core/components/table/table.element.test.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add at least one assertion to this test case.

See more on https://sonarcloud.io/project/issues?id=umbraco_Umbraco-CMS&issues=AZ_bl0-bnhynfjhWsBKK&open=AZ_bl0-bnhynfjhWsBKK&pullRequest=23593
element.items = [item('1'), item('2', { selectOnly: false })];
element.selection = ['1'];
await element.updateComplete;

expect(getRow('2').hasAttribute('select-only')).to.be.false;
// The row is still part of selection mode, so it presents its checkbox.
expect(getRow('2').hasAttribute('data-selection-mode')).to.be.true;
});

it('keeps a row interactive when it opts out of a select-only configuration', async () => {

Check failure on line 130 in src/Umbraco.Web.UI.Client/src/packages/core/components/table/table.element.test.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add at least one assertion to this test case.

See more on https://sonarcloud.io/project/issues?id=umbraco_Umbraco-CMS&issues=AZ_bl0-cnhynfjhWsBKL&open=AZ_bl0-cnhynfjhWsBKL&pullRequest=23593
element.config = { ...config, selectOnly: true };
element.items = [item('1'), item('2', { selectOnly: false })];
await element.updateComplete;

expect(getRow('1').hasAttribute('select-only')).to.be.true;
expect(getRow('2').hasAttribute('select-only')).to.be.false;
});
});

describe('header checkbox state', () => {
it('is checked when every selectable row on the current page is selected', async () => {
element.items = items(['1', '2', '3', '4']);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ export interface UmbTableItem {
data: Array<UmbTableItemData>;
selectable?: boolean;
active?: boolean;
/**
* Overrides the table-wide select-only behaviour for this row.
* `true` always makes the row select-only, `false` always keeps its content interactive.
* When left `undefined`, the row follows the table: select-only while a selection is in progress, or when the table is configured as select-only.
*/
selectOnly?: boolean;
/** When set, the row shows a children indicator. The nested options control what activating it does. */
childrenIndicator?: {
/** When set, the indicator becomes an anchor linking to this href. */
Expand Down Expand Up @@ -424,12 +430,14 @@ export class UmbTableElement extends UmbLitElement {

private _renderRow = (item: UmbTableItem) => {
const isItemSelectable = this.#isSelectableItem(item);
const selectionMode = this._selectionMode || this.config.selectOnly === true;
return html`
<uui-table-row
${ref(this.#getRowRenderedCallback(item))}
data-sortable-id=${item.id}
?selectable=${this.config.allowSelection && !this._sortable && isItemSelectable}
?select-only=${this._selectionMode || this.config.selectOnly}
?data-selection-mode=${selectionMode}
?select-only=${item.selectOnly ?? selectionMode}
?selected=${this._isSelected(item.id)}
?active=${item.active ?? false}
@selected=${() => this._selectRow(item)}
Expand Down Expand Up @@ -588,14 +596,14 @@ export class UmbTableElement extends UmbLitElement {
uui-table-row[selectable]:focus umb-icon,
uui-table-row[selectable]:focus-within umb-icon,
uui-table-row[selectable]:hover umb-icon,
uui-table-row[select-only] umb-icon {
uui-table-row[data-selection-mode] umb-icon {
display: none;
}

uui-table-row[selectable]:focus uui-checkbox,
uui-table-row[selectable]:focus-within uui-checkbox,
uui-table-row[selectable]:hover uui-checkbox,
uui-table-row[select-only] uui-checkbox {
uui-table-row[data-selection-mode] uui-checkbox {
display: inline-block;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { UmbDefaultTreeItemCardElement } from './default-tree-item-card.element.js';
import type { UmbTreeItemCardApi } from '../types.js';
import type { UmbTreeItemModel } from '../../types.js';
import { expect, fixture, html } from '@open-wc/testing';
import { UmbBooleanState, UmbStringState } from '@umbraco-cms/backoffice/observable-api';

class UmbTestTreeItemCardApi {
#isSelectable = new UmbBooleanState(false);
readonly isSelectable = this.#isSelectable.asObservable();

#isSelectableContext = new UmbBooleanState(false);
readonly isSelectableContext = this.#isSelectableContext.asObservable();

#selectOnly = new UmbBooleanState(false);
readonly selectOnly = this.#selectOnly.asObservable();

#isSelected = new UmbBooleanState(false);
readonly isSelected = this.#isSelected.asObservable();

#isActive = new UmbBooleanState(false);
readonly isActive = this.#isActive.asObservable();

#hasChildren = new UmbBooleanState(false);
readonly hasChildren = this.#hasChildren.asObservable();

#noAccess = new UmbBooleanState(false);
readonly noAccess = this.#noAccess.asObservable();

#path = new UmbStringState('');
readonly path = this.#path.asObservable();

#hasActions = new UmbBooleanState(false);
readonly hasActions = this.#hasActions.asObservable();

setSelectableContext(value: boolean) {
this.#isSelectableContext.setValue(value);
this.#isSelectable.setValue(value);
}

setSelectOnly(value: boolean) {
this.#selectOnly.setValue(value);
}

setHasChildren(value: boolean) {
this.#hasChildren.setValue(value);
}

open() {}
select() {}
deselect() {}
}
Comment thread
Copilot marked this conversation as resolved.

const item: UmbTreeItemModel = {
unique: 'a-1',
entityType: 'type-a',
name: 'A1',
hasChildren: false,
isFolder: false,
parent: { unique: null, entityType: 'type-a' },
};

describe('UmbDefaultTreeItemCardElement', () => {
let element: UmbDefaultTreeItemCardElement;
let api: UmbTestTreeItemCardApi;

beforeEach(async () => {
element = await fixture(html`<umb-default-tree-item-card></umb-default-tree-item-card>`);
api = new UmbTestTreeItemCardApi();
element.item = item;
element.api = api as unknown as UmbTreeItemCardApi;
await element.updateComplete;
});

function isSelectOnly(): boolean {
return element.shadowRoot!.querySelector('umb-figure-card')!.hasAttribute('select-only');
}

it('is defined with its own instance', () => {
expect(element).to.be.instanceOf(UmbDefaultTreeItemCardElement);
});

it('is not select-only outside a selectable context', async () => {

Check failure on line 82 in src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item-card/default/default-tree-item-card.element.test.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add at least one assertion to this test case.

See more on https://sonarcloud.io/project/issues?id=umbraco_Umbraco-CMS&issues=AZ_bl01KnhynfjhWsBKF&open=AZ_bl01KnhynfjhWsBKF&pullRequest=23593
await element.updateComplete;
expect(isSelectOnly()).to.be.false;
});

it('is select-only for an item without children in a selectable context', async () => {

Check failure on line 87 in src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item-card/default/default-tree-item-card.element.test.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add at least one assertion to this test case.

See more on https://sonarcloud.io/project/issues?id=umbraco_Umbraco-CMS&issues=AZ_bl01KnhynfjhWsBKG&open=AZ_bl01KnhynfjhWsBKG&pullRequest=23593
api.setSelectableContext(true);
await element.updateComplete;

expect(isSelectOnly()).to.be.true;
});

it('is not select-only for an item with children in a selectable context', async () => {

Check failure on line 94 in src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item-card/default/default-tree-item-card.element.test.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add at least one assertion to this test case.

See more on https://sonarcloud.io/project/issues?id=umbraco_Umbraco-CMS&issues=AZ_bl01KnhynfjhWsBKH&open=AZ_bl01KnhynfjhWsBKH&pullRequest=23593
api.setSelectableContext(true);
api.setHasChildren(true);
await element.updateComplete;

expect(isSelectOnly()).to.be.false;
});

it('is not select-only for an item with children while a selection is in progress', async () => {

Check failure on line 102 in src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item-card/default/default-tree-item-card.element.test.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add at least one assertion to this test case.

See more on https://sonarcloud.io/project/issues?id=umbraco_Umbraco-CMS&issues=AZ_bl01KnhynfjhWsBKI&open=AZ_bl01KnhynfjhWsBKI&pullRequest=23593
api.setSelectableContext(true);
api.setSelectOnly(true);
api.setHasChildren(true);
await element.updateComplete;

expect(isSelectOnly()).to.be.false;
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,15 @@ export class UmbDefaultTreeItemCardElement extends UmbLitElement {
override render() {
if (!this.item) return nothing;
const href = this._isSelectableContext ? undefined : this._path || undefined;
// select-only makes the entire card a select target, so it must never be applied to an item with
// children — that would leave no way to drill into it while a selection is in progress.
const selectOnly = !this._hasChildren && (this._selectOnly || this._isSelectableContext);
return html`
<umb-figure-card
name=${this.localize.string(this.item?.name ?? '')}
href=${ifDefined(href)}
?selectable=${this._isSelectable}
?select-only=${this._selectOnly || (!this._hasChildren && this._isSelectableContext)}
?select-only=${selectOnly}
?selected=${this._isSelected}
?active=${this._isActive}
?has-children=${this._hasChildren}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@
entityType: item.entityType,
childrenIndicator: item.hasChildren ? { href, onOpen } : undefined,
selectable: !noAccess && this._isSelectableItem(item as UmbTreeItemModel),
// select-only disables all row interaction, which would leave no way to drill into an item with
// children while a selection is in progress.
selectOnly: item.hasChildren ? false : undefined,

Check warning on line 214 in src/Umbraco.Web.UI.Client/src/packages/core/tree/view/table/table-tree-view.element.ts

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (v17/dev)

❌ Getting worse: Complex Method

UmbTableTreeViewElement.toTableRow increases in cyclomatic complexity from 14 to 15, threshold = 9 This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
active: isActive,
data: [
{
Expand Down
Loading