@dile/crud 2.6.4 → 2.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (22) hide show
  1. package/components/crud/crud.spec.js +142 -0
  2. package/components/crud/src/DileCrud.js +23 -3
  3. package/components/list/__screenshots__/crud-data-grid.spec.js/dile-crud-data-grid-always-forces-sort-mode-to-external-regardless-of-config-1.png +0 -0
  4. package/components/list/__screenshots__/crud-data-grid.spec.js/dile-crud-data-grid-forwards-stickyFirstColumn--striped--responsiveMode-and-emptyMessage-with-sensible-defaults-1.png +0 -0
  5. package/components/list/__screenshots__/crud-data-grid.spec.js/dile-crud-data-grid-leaves-computeRowId-unset-when-config-computeItemId-is-not-a-function-1.png +0 -0
  6. package/components/list/crud-data-grid.js +5 -0
  7. package/components/list/crud-data-grid.spec.js +207 -0
  8. package/components/list/crud-item-actions.js +5 -0
  9. package/components/list/crud-item-actions.spec.js +122 -0
  10. package/components/list/crud-list.spec.js +319 -0
  11. package/components/list/index.js +4 -2
  12. package/components/list/src/DileCrudDataGrid.js +93 -0
  13. package/components/list/src/DileCrudItemActions.js +191 -0
  14. package/components/list/src/DileCrudList.js +62 -11
  15. package/components/list/src/DileCrudListItem.js +78 -124
  16. package/components/ui/__screenshots__/crud-sort-form.spec.js/dile-crud-sort-form-syncSort-suppresses-exactly-one-radio-group-echo--preserving-the-synced-direction-instead-of-falling-back-to-the-option-static-default-1.png +0 -0
  17. package/components/ui/crud-sort-form.spec.js +132 -0
  18. package/components/ui/src/DileCrudSortForm.js +24 -5
  19. package/lib/CrudConfigBuilder.spec.js +32 -0
  20. package/lib/deepMerge.spec.js +45 -0
  21. package/lib/defaultConfig.js +4 -1
  22. package/package.json +3 -3
@@ -132,3 +132,145 @@ describe('dile-crud initial filters', () => {
132
132
  expect(select.value).toBe('');
133
133
  });
134
134
  });
135
+
136
+ describe('dile-crud sort synchronization', () => {
137
+ afterEach(() => {
138
+ document.body.innerHTML = '';
139
+ });
140
+
141
+ it('syncs dile-crud-sort-form when the list sort changes (e.g. from a DataGrid column header)', async () => {
142
+ const config = new CrudConfigBuilder('https://example.test/api/customers', {
143
+ customization: {
144
+ disableInsert: true,
145
+ disableHelp: true,
146
+ disableKeywordSearch: true,
147
+ disableSort: false,
148
+ disableFilter: true,
149
+ disablePagination: true,
150
+ hideCheckboxSelection: true,
151
+ },
152
+ sort: {
153
+ options: [
154
+ { name: 'name', label: 'Name', direction: 'asc' },
155
+ ],
156
+ initialSortField: null,
157
+ },
158
+ }).getConfig();
159
+
160
+ const el = document.createElement('dile-crud');
161
+ el.config = config;
162
+ document.body.appendChild(el);
163
+ await el.updateComplete;
164
+ await Promise.resolve();
165
+
166
+ expect(el.sortFormElement).toBeTruthy();
167
+
168
+ el.listElement.setSort({ sortField: 'name', sortDirection: 'desc' });
169
+ await el.updateComplete;
170
+
171
+ expect(el.sortFormElement.sortField).toBe('name');
172
+ expect(el.sortFormElement.sortDirection).toBe('desc');
173
+ });
174
+
175
+ it('ignores a grid sort field that is not listed in config.sort.options instead of crashing the sort-form', async () => {
176
+ // Reproduces a DataGrid column marked `sortable: true` for a field (e.g. "continent")
177
+ // that has no corresponding entry in config.sort.options.
178
+ const config = new CrudConfigBuilder('https://example.test/api/countries', {
179
+ customization: {
180
+ disableInsert: true,
181
+ disableHelp: true,
182
+ disableKeywordSearch: true,
183
+ disableSort: false,
184
+ disableFilter: true,
185
+ disablePagination: true,
186
+ hideCheckboxSelection: true,
187
+ },
188
+ grid: {
189
+ columns: [
190
+ { field: 'name', header: 'Name', sortable: true },
191
+ { field: 'continent', header: 'Continent', sortable: true },
192
+ ],
193
+ },
194
+ sort: {
195
+ options: [
196
+ { name: 'name', label: 'Name', direction: 'asc' },
197
+ ],
198
+ initialSortField: 'name',
199
+ },
200
+ }).getConfig();
201
+
202
+ const el = document.createElement('dile-crud');
203
+ el.config = config;
204
+ document.body.appendChild(el);
205
+ await el.updateComplete;
206
+ await Promise.resolve();
207
+
208
+ const initialSortField = el.sortFormElement.sortField;
209
+
210
+ expect(() => {
211
+ el.listElement.dispatchEvent(new CustomEvent('crud-list-sort-changed', {
212
+ bubbles: true,
213
+ composed: true,
214
+ detail: { sortField: 'continent', sortDirection: 'asc' },
215
+ }));
216
+ }).not.toThrow();
217
+ await el.updateComplete;
218
+
219
+ // The sort-form's selection is left untouched rather than pointed at an unknown option.
220
+ expect(el.sortFormElement.sortField).toBe(initialSortField);
221
+ });
222
+
223
+ it('does not let the sort-form echo override a grid-driven sort with the field static configured default direction', async () => {
224
+ // "year" is configured with a static default direction of "desc". A DataGrid column
225
+ // header click always starts at "asc" on first click — reproduces the exact scenario
226
+ // where the sort silently snapped back to "desc" and repeated clicks appeared to
227
+ // "lose" the requested order.
228
+ const config = new CrudConfigBuilder('https://example.test/api/board-games', {
229
+ customization: {
230
+ disableInsert: true,
231
+ disableHelp: true,
232
+ disableKeywordSearch: true,
233
+ disableSort: false,
234
+ disableFilter: true,
235
+ disablePagination: true,
236
+ hideCheckboxSelection: true,
237
+ },
238
+ grid: {
239
+ columns: [
240
+ { field: 'year', header: 'Year', sortable: true },
241
+ ],
242
+ },
243
+ sort: {
244
+ options: [
245
+ { name: 'year', label: 'Year', direction: 'desc' },
246
+ ],
247
+ initialSortField: 'year',
248
+ },
249
+ }).getConfig();
250
+
251
+ const el = document.createElement('dile-crud');
252
+ el.config = config;
253
+ document.body.appendChild(el);
254
+ await el.updateComplete;
255
+ await Promise.resolve();
256
+
257
+ let lastSetSort = null;
258
+ el.listElement.setSort = (sortObj) => {
259
+ lastSetSort = sortObj;
260
+ };
261
+
262
+ // Simulate what DileCrudList.setSort() dispatches after a grid header click requesting "asc".
263
+ el.listElement.dispatchEvent(new CustomEvent('crud-list-sort-changed', {
264
+ bubbles: true,
265
+ composed: true,
266
+ detail: { sortField: 'year', sortDirection: 'asc' },
267
+ }));
268
+ await el.updateComplete;
269
+ await new Promise((resolve) => setTimeout(resolve, 0)); // flush any echo microtasks
270
+
271
+ expect(el.sortFormElement.sortField).toBe('year');
272
+ expect(el.sortFormElement.sortDirection).toBe('asc');
273
+ // The echo must not have triggered a second, corrupting setSort() call.
274
+ expect(lastSetSort).toBeNull();
275
+ });
276
+ });
@@ -124,6 +124,9 @@ export class DileCrud extends DileI18nMixin(DileCrudMixin(LitElement)) {
124
124
  get filtersElement() {
125
125
  return this.shadowRoot.getElementById('elfilters');
126
126
  }
127
+ get sortFormElement() {
128
+ return this.shadowRoot.getElementById('elsort');
129
+ }
127
130
 
128
131
  // TEMPLATES
129
132
 
@@ -145,6 +148,7 @@ export class DileCrud extends DileI18nMixin(DileCrudMixin(LitElement)) {
145
148
  @item-checkbox-changed=${this.itemCheckboxChanged}
146
149
  @dile-chip-icon-click=${this.removeFilter}
147
150
  @crud-list-all-ids-selected=${this.crudSelectAll}
151
+ @crud-list-sort-changed=${this.listSortChanged}
148
152
  >
149
153
  ${this.listTemplate}
150
154
  </div>
@@ -265,14 +269,15 @@ export class DileCrud extends DileI18nMixin(DileCrudMixin(LitElement)) {
265
269
  ${this.config.customization.disableSort
266
270
  ? ''
267
271
  : html`
268
- <dile-crud-sort-form
272
+ <dile-crud-sort-form
273
+ id="elsort"
269
274
  class="action-controller"
270
- .sortOptions=${this.config.sort.options || []}
275
+ .sortOptions=${this.config.sort.options || []}
271
276
  sortField="${this.config.sort.initialSortField}"
272
277
  sortDirection="${this.config.sort.initialSortDirection || 'desc'}"
273
278
  @sort-changed=${this.sortFormChanged}
274
279
  language="${this.language}"
275
- ></dile-crud-sort-form>
280
+ ></dile-crud-sort-form>
276
281
  `
277
282
  }
278
283
  </div>
@@ -343,6 +348,21 @@ export class DileCrud extends DileI18nMixin(DileCrudMixin(LitElement)) {
343
348
  this.listElement.setSort(e.detail);
344
349
  }
345
350
 
351
+ listSortChanged(e) {
352
+ if (!this.sortFormElement) {
353
+ return;
354
+ }
355
+ const sortField = e.detail?.sortField;
356
+ const isKnownSortOption = !sortField || (this.config.sort?.options || []).some(option => option.name === sortField);
357
+ if (!isKnownSortOption) {
358
+ // The sort came from a source the sort-form doesn't know about (e.g. a DataGrid
359
+ // column not listed in config.sort.options) — leave the form's selection as-is
360
+ // instead of pointing it at an option it can't render.
361
+ return;
362
+ }
363
+ this.sortFormElement.syncSort(sortField, e.detail?.sortDirection);
364
+ }
365
+
346
366
  pageSizeChanged(e) {
347
367
  this.listElement.setPageSize(e.detail.pageSize);
348
368
  }
@@ -0,0 +1,5 @@
1
+ import { DileCrudDataGrid } from './src/DileCrudDataGrid.js';
2
+
3
+ if (!customElements.get('dile-crud-data-grid')) {
4
+ customElements.define('dile-crud-data-grid', DileCrudDataGrid);
5
+ }
@@ -0,0 +1,207 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import { CrudConfigBuilder } from '../../lib/CrudConfigBuilder.js';
3
+ import './crud-data-grid.js';
4
+ import '@dile/ui/components/data-grid/data-grid.js';
5
+
6
+ describe('dile-crud-data-grid', () => {
7
+ afterEach(() => {
8
+ document.body.innerHTML = '';
9
+ });
10
+
11
+ const sampleColumns = [
12
+ { field: 'id', header: 'ID' },
13
+ { field: 'name', header: 'Name' },
14
+ ];
15
+
16
+ const sampleItems = [
17
+ { id: 1, name: 'Item A' },
18
+ { id: 2, name: 'Item B', deleted_at: '2026-01-01' },
19
+ ];
20
+
21
+ async function createGrid(customConfig = {}, props = {}) {
22
+ const config = new CrudConfigBuilder('https://example.test/api/items', customConfig).getConfig();
23
+ const el = document.createElement('dile-crud-data-grid');
24
+ el.config = config;
25
+ el.items = props.items ?? sampleItems;
26
+ el.selectedIds = props.selectedIds ?? [];
27
+ if (props.sort !== undefined) {
28
+ el.sort = props.sort;
29
+ }
30
+ document.body.appendChild(el);
31
+ await el.updateComplete;
32
+ return el;
33
+ }
34
+
35
+ function innerGrid(el) {
36
+ return el.shadowRoot.querySelector('dile-data-grid');
37
+ }
38
+
39
+ it('builds columns from config.grid.columns and auto-appends an Actions column by default', async () => {
40
+ const el = await createGrid({ grid: { columns: sampleColumns } });
41
+
42
+ expect(el.columns.length).toBe(3);
43
+ expect(el.columns[0]).toEqual(sampleColumns[0]);
44
+ expect(el.columns[1]).toEqual(sampleColumns[1]);
45
+ expect(el.columns[2].header).toBe('Actions');
46
+ expect(el.columns[2].align).toBe('right');
47
+ expect(el.columns[2].width).toBe('110px');
48
+ expect(el.columns[2].hideCardLabel).toBe(true);
49
+ });
50
+
51
+ it('does not append an Actions column when config.grid.hideActionsColumn is true', async () => {
52
+ const el = await createGrid({ grid: { columns: sampleColumns, hideActionsColumn: true } });
53
+
54
+ expect(el.columns.length).toBe(2);
55
+ });
56
+
57
+ it('does not append an Actions column when actions are globally disabled', async () => {
58
+ const el = await createGrid({
59
+ grid: { columns: sampleColumns },
60
+ customization: { disableListActions: true },
61
+ });
62
+
63
+ expect(el.columns.length).toBe(2);
64
+ });
65
+
66
+ it('honors actionsColumnHeader, actionsColumnWidth and actionsColumnAlign overrides', async () => {
67
+ const el = await createGrid({
68
+ grid: {
69
+ columns: sampleColumns,
70
+ actionsColumnHeader: 'Ops',
71
+ actionsColumnWidth: '150px',
72
+ actionsColumnAlign: 'center',
73
+ },
74
+ });
75
+
76
+ const actionsColumn = el.columns[el.columns.length - 1];
77
+ expect(actionsColumn.header).toBe('Ops');
78
+ expect(actionsColumn.width).toBe('150px');
79
+ expect(actionsColumn.align).toBe('center');
80
+ });
81
+
82
+ it('falls back to config.labels.actionsHeader when grid.actionsColumnHeader is not set', async () => {
83
+ const el = await createGrid({
84
+ grid: { columns: sampleColumns },
85
+ labels: { actionsHeader: 'Manage' },
86
+ });
87
+
88
+ const actionsColumn = el.columns[el.columns.length - 1];
89
+ expect(actionsColumn.header).toBe('Manage');
90
+ });
91
+
92
+ it('renders dile-crud-item-actions in the Actions column wired with .item and .config', async () => {
93
+ const el = await createGrid({ grid: { columns: sampleColumns } });
94
+
95
+ const grid = innerGrid(el);
96
+ const actionsElements = grid.shadowRoot.querySelectorAll('dile-crud-item-actions');
97
+ expect(actionsElements.length).toBe(sampleItems.length);
98
+ expect(actionsElements[0].item).toEqual(sampleItems[0]);
99
+ expect(actionsElements[0].config).toBe(el.config);
100
+ });
101
+
102
+ it('derives selectable from !config.customization.hideCheckboxSelection by default', async () => {
103
+ const elHidden = await createGrid({ grid: { columns: sampleColumns }, customization: { hideCheckboxSelection: true } });
104
+ expect(innerGrid(elHidden).selectable).toBe(false);
105
+
106
+ const elShown = await createGrid({ grid: { columns: sampleColumns }, customization: { hideCheckboxSelection: false } });
107
+ expect(innerGrid(elShown).selectable).toBe(true);
108
+ });
109
+
110
+ it('lets config.grid.selectable override the customization.hideCheckboxSelection default', async () => {
111
+ const elForcedOff = await createGrid({
112
+ grid: { columns: sampleColumns, selectable: false },
113
+ customization: { hideCheckboxSelection: false },
114
+ });
115
+ expect(innerGrid(elForcedOff).selectable).toBe(false);
116
+
117
+ const elForcedOn = await createGrid({
118
+ grid: { columns: sampleColumns, selectable: true },
119
+ customization: { hideCheckboxSelection: true },
120
+ });
121
+ expect(innerGrid(elForcedOn).selectable).toBe(true);
122
+ });
123
+
124
+ it('forwards stickyFirstColumn, striped, responsiveMode and emptyMessage with sensible defaults', async () => {
125
+ const elDefaults = await createGrid({ grid: { columns: sampleColumns } });
126
+ const gridDefaults = innerGrid(elDefaults);
127
+ expect(gridDefaults.stickyFirstColumn).toBe(true);
128
+ expect(gridDefaults.striped).toBe(true);
129
+ expect(gridDefaults.responsiveMode).toBe('auto');
130
+ expect(gridDefaults.emptyMessage).toBe('No data available');
131
+
132
+ const elOverrides = await createGrid({
133
+ grid: {
134
+ columns: sampleColumns,
135
+ stickyFirstColumn: false,
136
+ striped: false,
137
+ responsiveMode: 'cards',
138
+ emptyMessage: 'Nothing here',
139
+ },
140
+ });
141
+ const gridOverrides = innerGrid(elOverrides);
142
+ expect(gridOverrides.stickyFirstColumn).toBe(false);
143
+ expect(gridOverrides.striped).toBe(false);
144
+ expect(gridOverrides.responsiveMode).toBe('cards');
145
+ expect(gridOverrides.emptyMessage).toBe('Nothing here');
146
+ });
147
+
148
+ it('always forces sort-mode to external regardless of config', async () => {
149
+ const el = await createGrid({ grid: { columns: sampleColumns } });
150
+ expect(innerGrid(el).sortMode).toBe('external');
151
+ });
152
+
153
+ it('forwards the sort property as sortField/sortDirection so the header keeps its sorted state', async () => {
154
+ const el = await createGrid({ grid: { columns: sampleColumns } }, { sort: { sortField: 'name', sortDirection: 'desc' } });
155
+ expect(innerGrid(el).sortField).toBe('name');
156
+ expect(innerGrid(el).sortDirection).toBe('desc');
157
+ });
158
+
159
+ it('defaults sortField/sortDirection to empty strings when sort is not set', async () => {
160
+ const el = await createGrid({ grid: { columns: sampleColumns } });
161
+ expect(innerGrid(el).sortField).toBe('');
162
+ expect(innerGrid(el).sortDirection).toBe('');
163
+ });
164
+
165
+ it('wires computeRowId from config.computeItemId so selection matches custom ids', async () => {
166
+ const el = await createGrid(
167
+ {
168
+ grid: { columns: sampleColumns, selectable: true },
169
+ computeItemId: (item) => `custom-${item.id}`,
170
+ },
171
+ { selectedIds: ['custom-2'] }
172
+ );
173
+
174
+ const checkboxes = innerGrid(el).shadowRoot.querySelectorAll('dile-checkbox');
175
+ expect(checkboxes[0].checked).toBe(false);
176
+ expect(checkboxes[1].checked).toBe(true);
177
+ });
178
+
179
+ it('leaves computeRowId unset when config.computeItemId is not a function', async () => {
180
+ const el = document.createElement('dile-crud-data-grid');
181
+ el.config = { grid: { columns: sampleColumns } };
182
+ el.items = sampleItems;
183
+ document.body.appendChild(el);
184
+ await el.updateComplete;
185
+
186
+ expect(el.computeRowId).toBeNull();
187
+ });
188
+
189
+ it('applies the is-deleted row class to rows whose deleted_at is truthy', async () => {
190
+ const el = await createGrid({ grid: { columns: sampleColumns } });
191
+
192
+ const rows = innerGrid(el).shadowRoot.querySelectorAll('tbody tr');
193
+ expect(rows[0].classList.contains('is-deleted')).toBe(false);
194
+ expect(rows[1].classList.contains('is-deleted')).toBe(true);
195
+ });
196
+
197
+ it('does not throw and still appends the Actions column when config is null', async () => {
198
+ const el = document.createElement('dile-crud-data-grid');
199
+ el.items = sampleItems;
200
+ document.body.appendChild(el);
201
+ await el.updateComplete;
202
+
203
+ expect(el.columns.length).toBe(1);
204
+ expect(el.columns[0].header).toBe('Actions');
205
+ expect(innerGrid(el)).toBeTruthy();
206
+ });
207
+ });
@@ -0,0 +1,5 @@
1
+ import { DileCrudItemActions } from './src/DileCrudItemActions.js';
2
+
3
+ if (!customElements.get('dile-crud-item-actions')) {
4
+ customElements.define('dile-crud-item-actions', DileCrudItemActions);
5
+ }
@@ -0,0 +1,122 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import { html } from 'lit';
3
+ import { CrudConfigBuilder } from '../../lib/CrudConfigBuilder.js';
4
+ import { DileCrudItemActions } from './src/DileCrudItemActions.js';
5
+ import './crud-item-actions.js';
6
+
7
+ describe('dile-crud-item-actions', () => {
8
+ afterEach(() => {
9
+ document.body.innerHTML = '';
10
+ });
11
+
12
+ const sampleItem = { id: 10, name: 'Sample Item', deleted_at: null };
13
+ const deletedItem = { id: 20, name: 'Deleted Item', deleted_at: '2026-01-01' };
14
+
15
+ it('renders edit and delete buttons by default', async () => {
16
+ const el = document.createElement('dile-crud-item-actions');
17
+ el.item = sampleItem;
18
+ document.body.appendChild(el);
19
+ await el.updateComplete;
20
+
21
+ const editBtn = el.shadowRoot.querySelector('dile-button.edit');
22
+ const deleteBtn = el.shadowRoot.querySelector('dile-button.delete');
23
+ const restoreBtn = el.shadowRoot.querySelector('dile-button.restore');
24
+
25
+ expect(editBtn).toBeTruthy();
26
+ expect(deleteBtn).toBeTruthy();
27
+ expect(restoreBtn).toBeNull();
28
+ });
29
+
30
+ it('renders restore button when item has deleted_at or isDeleted is true', async () => {
31
+ const el = document.createElement('dile-crud-item-actions');
32
+ el.item = deletedItem;
33
+ document.body.appendChild(el);
34
+ await el.updateComplete;
35
+
36
+ const editBtn = el.shadowRoot.querySelector('dile-button.edit');
37
+ const deleteBtn = el.shadowRoot.querySelector('dile-button.delete');
38
+ const restoreBtn = el.shadowRoot.querySelector('dile-button.restore');
39
+
40
+ expect(editBtn).toBeNull();
41
+ expect(deleteBtn).toBeNull();
42
+ expect(restoreBtn).toBeTruthy();
43
+ });
44
+
45
+ it('respects global customization and item-level callbacks from config', async () => {
46
+ const config = new CrudConfigBuilder('https://example.test/api/items', {
47
+ customization: {
48
+ disableEdit: false,
49
+ disableDelete: true,
50
+ },
51
+ isItemEditable: (item) => item.id !== 99,
52
+ computeItemId: (item) => `custom-${item.id}`,
53
+ }).getConfig();
54
+
55
+ const el = document.createElement('dile-crud-item-actions');
56
+ el.item = { id: 99, name: 'Not editable' };
57
+ el.config = config;
58
+ document.body.appendChild(el);
59
+ await el.updateComplete;
60
+
61
+ // Both edit (via isItemEditable returning false) and delete (via disableDelete: true) should be hidden
62
+ expect(el.shadowRoot.querySelector('dile-button.edit')).toBeNull();
63
+ expect(el.shadowRoot.querySelector('dile-button.delete')).toBeNull();
64
+ });
65
+
66
+ it('hides all actions when disableListActions is true in config', async () => {
67
+ const config = new CrudConfigBuilder('https://example.test/api/items', {
68
+ customization: {
69
+ disableListActions: true,
70
+ },
71
+ }).getConfig();
72
+
73
+ expect(DileCrudItemActions.hasActions(config)).toBe(false);
74
+
75
+ const el = document.createElement('dile-crud-item-actions');
76
+ el.item = sampleItem;
77
+ el.config = config;
78
+ document.body.appendChild(el);
79
+ await el.updateComplete;
80
+
81
+ expect(el.shadowRoot.querySelector('dile-button.edit')).toBeNull();
82
+ expect(el.shadowRoot.querySelector('dile-button.delete')).toBeNull();
83
+ expect(el.shadowRoot.querySelector('dile-button.restore')).toBeNull();
84
+ });
85
+
86
+ it('DileCrudItemActions.hasActions returns false if edit, delete and restore are all disabled', () => {
87
+ const config = new CrudConfigBuilder('https://example.test/api/items', {
88
+ customization: {
89
+ disableEdit: true,
90
+ disableDelete: true,
91
+ disableRestore: true,
92
+ },
93
+ }).getConfig();
94
+
95
+ expect(DileCrudItemActions.hasActions(config)).toBe(false);
96
+ });
97
+
98
+ it('dispatches crud-item-edit with computed itemId from config', async () => {
99
+ const config = new CrudConfigBuilder('https://example.test/api/items', {
100
+ computeItemId: (item) => `computed-${item.id}`,
101
+ }).getConfig();
102
+
103
+ const el = document.createElement('dile-crud-item-actions');
104
+ el.item = sampleItem;
105
+ el.config = config;
106
+ document.body.appendChild(el);
107
+ await el.updateComplete;
108
+
109
+ let emittedDetail = null;
110
+ el.addEventListener('crud-item-edit', (e) => {
111
+ emittedDetail = e.detail;
112
+ });
113
+
114
+ const editBtn = el.shadowRoot.querySelector('dile-button.edit');
115
+ editBtn.click();
116
+
117
+ expect(emittedDetail).toEqual({
118
+ item: sampleItem,
119
+ itemId: 'computed-10',
120
+ });
121
+ });
122
+ });