@dile/crud 2.9.5 → 2.10.1
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.
- package/components/steps-form/index.js +3 -0
- package/components/steps-form/src/DileStepForm.js +202 -0
- package/components/steps-form/src/DileStepNextPrev.js +134 -0
- package/components/steps-form/src/DileStepsForm.js +347 -0
- package/components/steps-form/step-form.js +5 -0
- package/components/steps-form/step-next-prev.js +5 -0
- package/components/steps-form/steps-form.js +5 -0
- package/lib/i18n/en.js +6 -1
- package/lib/i18n/es.js +6 -1
- package/package.json +3 -3
- package/components/action/src/DileCrudSingleActions.spec.js +0 -126
- package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-closing-dialog-with-ESC-and-reopening-without-data-shows-reset-form-1.png +0 -0
- package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-initialData-is-cleared-after-being-applied-1.png +0 -0
- package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-initialData-is-only-applied-once-and-cleared-after-opening-1.png +0 -0
- package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-triggerAction-behavior-matches-original-when-no-initialData-1.png +0 -0
- package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-triggerAction-opens-confirm-dialog-1.png +0 -0
- package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-triggerAction-with-initialData-sets-form-values-1.png +0 -0
- package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-triggerAction-with-initialData-sets-form-values-before-opening-dialog-1.png +0 -0
- package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-triggerAction-without-initialData-resets-form-1.png +0 -0
- package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-triggerAction-without-initialData-resets-form-to-empty-1.png +0 -0
- package/components/ajax/ajax.spec.js +0 -35
- package/components/ajax-select-crud/ajax-select-crud-overlay.spec.js +0 -186
- package/components/crud/__screenshots__/crud.spec.js/dile-crud-list-grid-view-switch-defaults-to-the-item-template-and-toggles-to-the-grid-on-click-when-both-views-are-configured-1.png +0 -0
- package/components/crud/__screenshots__/crud.spec.js/dile-crud-list-grid-view-switch-does-not-render-the-switch-button-when-customization-disableListWiewSwitch-is-true-1.png +0 -0
- package/components/crud/crud.spec.js +0 -453
- package/components/file-uploader/file-uploader.spec.js +0 -32
- 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
- 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
- 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
- package/components/list/__screenshots__/crud-list.spec.js/dile-crud-list-scope-changes-and-page-reset-keeps-the-current-page-on-refresh---and-goes-back-to-page-1-on-refresh---resetPage--true----1.png +0 -0
- package/components/list/__screenshots__/crud-list.spec.js/dile-crud-list-scope-changes-and-page-reset-reloads-from-the-first-page-with-the-new-scope-when-belongsTo-and-relationId-change-1.png +0 -0
- package/components/list/__screenshots__/crud-list.spec.js/dile-crud-list-scope-changes-and-page-reset-reloads-when-only-relationId-changes-1.png +0 -0
- package/components/list/crud-data-grid.spec.js +0 -207
- package/components/list/crud-item-actions.spec.js +0 -122
- package/components/list/crud-list.spec.js +0 -455
- package/components/many-relation/many-relation.spec.js +0 -154
- package/components/select-ajax-simple/select-ajax-simple.spec.js +0 -133
- package/components/single/src/DileCrudSingle.spec.js +0 -151
- package/components/single/src/__screenshots__/DileCrudSingle.spec.js/DileCrudSingle-triggerAction-and-triggerDirectAction-triggerAction-delegates-to-singleActionsElement-triggerAction-1.png +0 -0
- package/components/single/src/__screenshots__/DileCrudSingle.spec.js/DileCrudSingle-triggerAction-and-triggerDirectAction-triggerAction-without-initialData-works-1.png +0 -0
- package/components/single/src/__screenshots__/DileCrudSingle.spec.js/DileCrudSingle-triggerAction-and-triggerDirectAction-triggerDirectAction-delegates-to-singleActionsElement-triggerAction-1.png +0 -0
- package/components/single/src/__screenshots__/DileCrudSingle.spec.js/DileCrudSingle-triggerAction-and-triggerDirectAction-triggerDirectAction-without-initialData-still-works--backward-compatibility--1.png +0 -0
- package/components/ui/__screenshots__/crud-sort-form.spec.js/dile-crud-sort-form-clicking-the-label-of-an-inactive-option-switches-to-it-using-its-own-configured-direction--without-flipping-it-1.png +0 -0
- package/components/ui/__screenshots__/crud-sort-form.spec.js/dile-crud-sort-form-clicking-the-label-of-the-already-active-option-toggles-its-direction-instead-of-doing-nothing-1.png +0 -0
- 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
- package/components/ui/crud-sort-form.spec.js +0 -132
- package/lib/CrudConfigBuilder.spec.js +0 -32
- package/lib/deepMerge.spec.js +0 -45
- package/lib/listViewMode.spec.js +0 -73
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import './select-ajax-simple.js';
|
|
3
|
-
|
|
4
|
-
const MOCK_DATA = [
|
|
5
|
-
{ id: 1, name: 'Fantasy' },
|
|
6
|
-
{ id: 2, name: 'Strategy' },
|
|
7
|
-
];
|
|
8
|
-
|
|
9
|
-
function mockAxiosSuccess(data = MOCK_DATA) {
|
|
10
|
-
window.axiosInstance = {
|
|
11
|
-
get: vi.fn().mockResolvedValue({ status: 200, data: { data } }),
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function mockAxiosError() {
|
|
16
|
-
window.axiosInstance = {
|
|
17
|
-
get: vi.fn().mockRejectedValue(new Error('Network error')),
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async function renderEl(html) {
|
|
22
|
-
document.body.innerHTML = html;
|
|
23
|
-
const el = document.body.querySelector('dile-select-ajax-simple');
|
|
24
|
-
await el.updateComplete;
|
|
25
|
-
// Wait for async loadData to complete and re-render
|
|
26
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
27
|
-
await el.updateComplete;
|
|
28
|
-
return el;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
describe('dile-select-ajax-simple', () => {
|
|
32
|
-
beforeEach(() => {
|
|
33
|
-
mockAxiosSuccess();
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
afterEach(() => {
|
|
37
|
-
document.body.innerHTML = '';
|
|
38
|
-
delete window.axiosInstance;
|
|
39
|
-
vi.restoreAllMocks();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('shows a spinner while loading', async () => {
|
|
43
|
-
document.body.innerHTML = '<dile-select-ajax-simple endpoint="/api/tags" name="tag"></dile-select-ajax-simple>';
|
|
44
|
-
const el = document.body.querySelector('dile-select-ajax-simple');
|
|
45
|
-
await el.updateComplete;
|
|
46
|
-
|
|
47
|
-
expect(el.shadowRoot.querySelector('dile-spinner-horizontal')).toBeTruthy();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('renders options after successful data load', async () => {
|
|
51
|
-
const el = await renderEl('<dile-select-ajax-simple endpoint="/api/tags" name="tag"></dile-select-ajax-simple>');
|
|
52
|
-
|
|
53
|
-
const options = el.shadowRoot.querySelectorAll('option');
|
|
54
|
-
// placeholder + 2 data options
|
|
55
|
-
expect(options.length).toBe(3);
|
|
56
|
-
expect(options[1].textContent).toBe('Fantasy');
|
|
57
|
-
expect(options[2].textContent).toBe('Strategy');
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('uses displayProperty and idProperty for option rendering', async () => {
|
|
61
|
-
window.axiosInstance = {
|
|
62
|
-
get: vi.fn().mockResolvedValue({
|
|
63
|
-
status: 200,
|
|
64
|
-
data: { data: [{ code: 'FR', title: 'France' }] }
|
|
65
|
-
}),
|
|
66
|
-
};
|
|
67
|
-
const el = await renderEl(
|
|
68
|
-
'<dile-select-ajax-simple endpoint="/api/countries" name="country" displayProperty="title" idProperty="code"></dile-select-ajax-simple>'
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
const options = el.shadowRoot.querySelectorAll('option');
|
|
72
|
-
expect(options[1].value).toBe('FR');
|
|
73
|
-
expect(options[1].textContent).toBe('France');
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('shows error message when request fails', async () => {
|
|
77
|
-
mockAxiosError();
|
|
78
|
-
const el = await renderEl('<dile-select-ajax-simple endpoint="/api/tags" name="tag"></dile-select-ajax-simple>');
|
|
79
|
-
|
|
80
|
-
expect(el.shadowRoot.querySelector('p.error')).toBeTruthy();
|
|
81
|
-
expect(el.shadowRoot.querySelector('dile-select')).toBeNull();
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('shows empty message when response has no items', async () => {
|
|
85
|
-
window.axiosInstance = {
|
|
86
|
-
get: vi.fn().mockResolvedValue({ status: 200, data: { data: [] } }),
|
|
87
|
-
};
|
|
88
|
-
const el = await renderEl('<dile-select-ajax-simple endpoint="/api/tags" name="tag"></dile-select-ajax-simple>');
|
|
89
|
-
|
|
90
|
-
expect(el.shadowRoot.querySelector('p.empty')).toBeTruthy();
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('dispatches element-changed when an option is selected', async () => {
|
|
94
|
-
const el = await renderEl('<dile-select-ajax-simple endpoint="/api/tags" name="tag"></dile-select-ajax-simple>');
|
|
95
|
-
|
|
96
|
-
let detail = null;
|
|
97
|
-
el.addEventListener('element-changed', (e) => { detail = e.detail; });
|
|
98
|
-
|
|
99
|
-
el.value = '1';
|
|
100
|
-
await el.updateComplete;
|
|
101
|
-
|
|
102
|
-
expect(detail).not.toBeNull();
|
|
103
|
-
expect(detail.name).toBe('tag');
|
|
104
|
-
expect(detail.value).toBe('1');
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('supports getSelectResultList as custom extractor', async () => {
|
|
108
|
-
window.axiosInstance = {
|
|
109
|
-
get: vi.fn().mockResolvedValue({ status: 200, data: { result: { items: MOCK_DATA } } }),
|
|
110
|
-
};
|
|
111
|
-
// Set getSelectResultList before connectedCallback fires
|
|
112
|
-
const el = document.createElement('dile-select-ajax-simple');
|
|
113
|
-
el.getSelectResultList = (json) => json.result.items;
|
|
114
|
-
el.endpoint = '/api/tags';
|
|
115
|
-
el.name = 'tag';
|
|
116
|
-
document.body.appendChild(el);
|
|
117
|
-
await el.updateComplete;
|
|
118
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
119
|
-
await el.updateComplete;
|
|
120
|
-
|
|
121
|
-
const options = el.shadowRoot.querySelectorAll('option');
|
|
122
|
-
expect(options.length).toBe(3);
|
|
123
|
-
expect(options[1].textContent).toBe('Fantasy');
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('renders label when provided', async () => {
|
|
127
|
-
const el = await renderEl('<dile-select-ajax-simple endpoint="/api/tags" name="tag" label="Category"></dile-select-ajax-simple>');
|
|
128
|
-
|
|
129
|
-
const label = el.shadowRoot.querySelector('label');
|
|
130
|
-
expect(label).toBeTruthy();
|
|
131
|
-
expect(label.textContent).toBe('Category');
|
|
132
|
-
});
|
|
133
|
-
});
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, afterEach, vi } from 'vitest';
|
|
2
|
-
import { html, LitElement } from 'lit';
|
|
3
|
-
import { DileForm } from '@dile/ui/mixins/form';
|
|
4
|
-
import '../crud-single.js';
|
|
5
|
-
|
|
6
|
-
// Create a test form component
|
|
7
|
-
class TestActionForm extends DileForm(LitElement) {
|
|
8
|
-
render() {
|
|
9
|
-
return html`
|
|
10
|
-
<input name="productUuid" />
|
|
11
|
-
<input name="units" type="number" value="1" />
|
|
12
|
-
`;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
if (!customElements.get('test-action-form')) {
|
|
17
|
-
customElements.define('test-action-form', TestActionForm);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
describe('DileCrudSingle.triggerAction and triggerDirectAction', () => {
|
|
21
|
-
afterEach(() => {
|
|
22
|
-
document.body.innerHTML = '';
|
|
23
|
-
delete window.axiosInstance;
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
function mockAxios({ get = vi.fn(() => Promise.resolve({ status: 200, data: {} })) } = {}) {
|
|
27
|
-
window.axiosInstance = { get };
|
|
28
|
-
return window.axiosInstance;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const testActions = [
|
|
32
|
-
{
|
|
33
|
-
name: 'CreateInvoiceAction',
|
|
34
|
-
label: 'Create Invoice',
|
|
35
|
-
destructive: false,
|
|
36
|
-
},
|
|
37
|
-
];
|
|
38
|
-
|
|
39
|
-
function createFormTemplate() {
|
|
40
|
-
return (selection, element) => html`
|
|
41
|
-
${selection === 'CreateInvoiceAction'
|
|
42
|
-
? html`<test-action-form action="CreateInvoiceAction"></test-action-form>`
|
|
43
|
-
: ''
|
|
44
|
-
}
|
|
45
|
-
`;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const testConfig = {
|
|
49
|
-
endpoint: 'https://example.test/api/items',
|
|
50
|
-
actions: {
|
|
51
|
-
single: testActions,
|
|
52
|
-
directSingleActions: [],
|
|
53
|
-
list: [],
|
|
54
|
-
},
|
|
55
|
-
customization: {
|
|
56
|
-
disableEdit: true,
|
|
57
|
-
disableListActionsOnSingleComponent: true,
|
|
58
|
-
},
|
|
59
|
-
labels: {
|
|
60
|
-
startUpdateAction: 'Edit',
|
|
61
|
-
},
|
|
62
|
-
templates: {
|
|
63
|
-
detail: (item) => html`<div class="item-detail">${item?.name || ''}</div>`,
|
|
64
|
-
formSingleActions: createFormTemplate(),
|
|
65
|
-
relations: () => html``,
|
|
66
|
-
updateForm: () => html``,
|
|
67
|
-
},
|
|
68
|
-
formIds: {
|
|
69
|
-
updateForm: 'update-form',
|
|
70
|
-
},
|
|
71
|
-
translations: {
|
|
72
|
-
element_actions: 'Actions',
|
|
73
|
-
},
|
|
74
|
-
responseAdapter: {
|
|
75
|
-
setResponse: () => {},
|
|
76
|
-
getData: () => ({ id: 1, name: 'Test Item' }),
|
|
77
|
-
getActionResponseMessage: () => 'Success',
|
|
78
|
-
getActionResponseActionName: () => 'CreateInvoiceAction',
|
|
79
|
-
getActionResponseActionData: () => ({}),
|
|
80
|
-
},
|
|
81
|
-
destructiveActionNames: [],
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
async function createCrudSingle() {
|
|
85
|
-
mockAxios({
|
|
86
|
-
get: vi.fn(() => Promise.resolve({
|
|
87
|
-
status: 200,
|
|
88
|
-
data: { data: { id: 1, name: 'Test Item' } }
|
|
89
|
-
}))
|
|
90
|
-
});
|
|
91
|
-
const el = document.createElement('dile-crud-single');
|
|
92
|
-
el.relatedId = '1';
|
|
93
|
-
el.config = testConfig;
|
|
94
|
-
el.element = { id: 1, name: 'Test Item' };
|
|
95
|
-
document.body.appendChild(el);
|
|
96
|
-
await el.updateComplete;
|
|
97
|
-
return el;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
it('triggerAction delegates to singleActionsElement.triggerAction', async () => {
|
|
101
|
-
const el = await createCrudSingle();
|
|
102
|
-
|
|
103
|
-
const singleActionsElement = el.singleActionsElement;
|
|
104
|
-
const triggerActionSpy = vi.spyOn(singleActionsElement, 'triggerAction');
|
|
105
|
-
|
|
106
|
-
const initialData = { productUuid: 'uuid-123', units: 5 };
|
|
107
|
-
el.triggerAction('CreateInvoiceAction', initialData);
|
|
108
|
-
|
|
109
|
-
expect(triggerActionSpy).toHaveBeenCalledWith('CreateInvoiceAction', initialData);
|
|
110
|
-
triggerActionSpy.mockRestore();
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('triggerDirectAction delegates to singleActionsElement.triggerAction', async () => {
|
|
114
|
-
const el = await createCrudSingle();
|
|
115
|
-
|
|
116
|
-
const singleActionsElement = el.singleActionsElement;
|
|
117
|
-
const triggerActionSpy = vi.spyOn(singleActionsElement, 'triggerAction');
|
|
118
|
-
|
|
119
|
-
const initialData = { productUuid: 'uuid-456', units: 2 };
|
|
120
|
-
el.triggerDirectAction('CreateInvoiceAction', initialData);
|
|
121
|
-
|
|
122
|
-
expect(triggerActionSpy).toHaveBeenCalledWith('CreateInvoiceAction', initialData);
|
|
123
|
-
triggerActionSpy.mockRestore();
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('triggerDirectAction without initialData still works (backward compatibility)', async () => {
|
|
127
|
-
const el = await createCrudSingle();
|
|
128
|
-
|
|
129
|
-
const singleActionsElement = el.singleActionsElement;
|
|
130
|
-
const triggerActionSpy = vi.spyOn(singleActionsElement, 'triggerAction');
|
|
131
|
-
|
|
132
|
-
el.triggerDirectAction('CreateInvoiceAction');
|
|
133
|
-
|
|
134
|
-
expect(triggerActionSpy).toHaveBeenCalledWith('CreateInvoiceAction', undefined);
|
|
135
|
-
triggerActionSpy.mockRestore();
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it('triggerAction without initialData works', async () => {
|
|
139
|
-
const el = await createCrudSingle();
|
|
140
|
-
|
|
141
|
-
const singleActionsElement = el.singleActionsElement;
|
|
142
|
-
const triggerActionSpy = vi.spyOn(singleActionsElement, 'triggerAction');
|
|
143
|
-
|
|
144
|
-
el.triggerAction('CreateInvoiceAction');
|
|
145
|
-
|
|
146
|
-
expect(triggerActionSpy).toHaveBeenCalledWith('CreateInvoiceAction', undefined);
|
|
147
|
-
triggerActionSpy.mockRestore();
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
|
|
Binary file
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
-
import './crud-sort-form.js';
|
|
3
|
-
|
|
4
|
-
describe('dile-crud-sort-form', () => {
|
|
5
|
-
afterEach(() => {
|
|
6
|
-
document.body.innerHTML = '';
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
const sortOptions = [
|
|
10
|
-
{ name: 'name', label: 'Name', direction: 'asc' },
|
|
11
|
-
{ name: 'id', label: 'Id', direction: 'asc' },
|
|
12
|
-
];
|
|
13
|
-
|
|
14
|
-
async function createSortForm(props = {}) {
|
|
15
|
-
const el = document.createElement('dile-crud-sort-form');
|
|
16
|
-
el.sortOptions = props.sortOptions ?? sortOptions;
|
|
17
|
-
if (props.sortField !== undefined) {
|
|
18
|
-
el.sortField = props.sortField;
|
|
19
|
-
}
|
|
20
|
-
document.body.appendChild(el);
|
|
21
|
-
await el.updateComplete;
|
|
22
|
-
return el;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
it('renders one radio/order-switch pair per configured sort option', async () => {
|
|
26
|
-
const el = await createSortForm();
|
|
27
|
-
expect(el.shadowRoot.querySelectorAll('dile-radio').length).toBe(2);
|
|
28
|
-
expect(el.shadowRoot.querySelectorAll('dile-order-switch').length).toBe(2);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('getDirection returns the configured direction for a known field', async () => {
|
|
32
|
-
const el = await createSortForm();
|
|
33
|
-
await el.shadowRoot.querySelector('dile-order-switch[name="name"]').updateComplete;
|
|
34
|
-
expect(el.getDirection('name')).toBe('asc');
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('getDirection returns undefined instead of throwing for a field with no matching order-switch', async () => {
|
|
38
|
-
// Reproduces a DataGrid column marked sortable whose field isn't listed in config.sort.options.
|
|
39
|
-
const el = await createSortForm();
|
|
40
|
-
expect(() => el.getDirection('continent')).not.toThrow();
|
|
41
|
-
expect(el.getDirection('continent')).toBeUndefined();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('getDirection returns undefined for a falsy field without touching the DOM', async () => {
|
|
45
|
-
const el = await createSortForm();
|
|
46
|
-
expect(el.getDirection(null)).toBeUndefined();
|
|
47
|
-
expect(el.getDirection('')).toBeUndefined();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
// "year" has a static configured direction of "desc" so it can expose the regression:
|
|
51
|
-
// syncing an "asc" sort must not get silently corrected back to "desc".
|
|
52
|
-
const yearOptions = [{ name: 'year', label: 'Year', direction: 'desc' }];
|
|
53
|
-
|
|
54
|
-
it('syncSort reflects an externally-applied sort without re-dispatching sort-changed', async () => {
|
|
55
|
-
const el = await createSortForm({ sortOptions: yearOptions });
|
|
56
|
-
|
|
57
|
-
let dispatchCount = 0;
|
|
58
|
-
el.addEventListener('sort-changed', () => { dispatchCount++; });
|
|
59
|
-
|
|
60
|
-
el.syncSort('year', 'asc');
|
|
61
|
-
await el.updateComplete;
|
|
62
|
-
|
|
63
|
-
expect(el.sortField).toBe('year');
|
|
64
|
-
expect(el.sortDirection).toBe('asc');
|
|
65
|
-
expect(dispatchCount).toBe(0);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('a genuine radio-group selection (not preceded by syncSort) still defaults to the option configured direction', async () => {
|
|
69
|
-
const el = await createSortForm({ sortOptions: yearOptions });
|
|
70
|
-
|
|
71
|
-
let receivedDetail = null;
|
|
72
|
-
el.addEventListener('sort-changed', (e) => { receivedDetail = e.detail; });
|
|
73
|
-
|
|
74
|
-
const radioGroup = el.shadowRoot.querySelector('dile-radio-group');
|
|
75
|
-
radioGroup.dispatchEvent(new CustomEvent('dile-radio-group-changed', {
|
|
76
|
-
detail: { name: 'selector', value: 'year' },
|
|
77
|
-
}));
|
|
78
|
-
await el.updateComplete;
|
|
79
|
-
|
|
80
|
-
expect(el.sortField).toBe('year');
|
|
81
|
-
expect(el.sortDirection).toBe('desc');
|
|
82
|
-
expect(receivedDetail).toEqual({ sortField: 'year', sortDirection: 'desc' });
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('clicking the label of an inactive option switches to it using its own configured direction, without flipping it', async () => {
|
|
86
|
-
const el = await createSortForm({
|
|
87
|
-
sortOptions: [
|
|
88
|
-
{ name: 'name', label: 'Name', direction: 'asc' },
|
|
89
|
-
{ name: 'year', label: 'Year', direction: 'desc' },
|
|
90
|
-
],
|
|
91
|
-
sortField: 'name',
|
|
92
|
-
});
|
|
93
|
-
el.sortDirection = 'asc';
|
|
94
|
-
await el.updateComplete;
|
|
95
|
-
|
|
96
|
-
let receivedDetail = null;
|
|
97
|
-
el.addEventListener('sort-changed', (e) => { receivedDetail = e.detail; });
|
|
98
|
-
|
|
99
|
-
el.shadowRoot.querySelector('dile-order-switch[name="year"]').shadowRoot.querySelector('a').click();
|
|
100
|
-
await el.updateComplete;
|
|
101
|
-
|
|
102
|
-
expect(el.sortField).toBe('year');
|
|
103
|
-
expect(el.sortDirection).toBe('desc'); // year's own configured default, not flipped
|
|
104
|
-
expect(receivedDetail).toEqual({ sortField: 'year', sortDirection: 'desc' });
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('clicking the label of the already-active option toggles its direction instead of doing nothing', async () => {
|
|
108
|
-
// Reproduces the reported confusion: clicking "Name" again while already sorted by
|
|
109
|
-
// name silently did nothing, because the label always re-announced the same value.
|
|
110
|
-
const el = await createSortForm({
|
|
111
|
-
sortOptions: [{ name: 'name', label: 'Name', direction: 'asc' }],
|
|
112
|
-
sortField: 'name',
|
|
113
|
-
});
|
|
114
|
-
el.sortDirection = 'asc';
|
|
115
|
-
await el.updateComplete;
|
|
116
|
-
|
|
117
|
-
let receivedDetail = null;
|
|
118
|
-
el.addEventListener('sort-changed', (e) => { receivedDetail = e.detail; });
|
|
119
|
-
|
|
120
|
-
const label = () => el.shadowRoot.querySelector('dile-order-switch[name="name"]').shadowRoot.querySelector('a');
|
|
121
|
-
|
|
122
|
-
label().click();
|
|
123
|
-
await el.updateComplete;
|
|
124
|
-
expect(el.sortField).toBe('name');
|
|
125
|
-
expect(el.sortDirection).toBe('desc'); // toggled, not a no-op
|
|
126
|
-
expect(receivedDetail).toEqual({ sortField: 'name', sortDirection: 'desc' });
|
|
127
|
-
|
|
128
|
-
label().click();
|
|
129
|
-
await el.updateComplete;
|
|
130
|
-
expect(el.sortDirection).toBe('asc'); // toggles back on a second click
|
|
131
|
-
});
|
|
132
|
-
});
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { CrudConfigBuilder } from './CrudConfigBuilder.js';
|
|
3
|
-
|
|
4
|
-
describe('CrudConfigBuilder', () => {
|
|
5
|
-
it('merges customConfig.grid.columns over the default null grid', () => {
|
|
6
|
-
const columns = [{ field: 'id', header: 'ID' }];
|
|
7
|
-
|
|
8
|
-
const config = new CrudConfigBuilder('https://example.test/api/items', {
|
|
9
|
-
grid: { columns },
|
|
10
|
-
}).getConfig();
|
|
11
|
-
|
|
12
|
-
expect(config.grid.columns).toEqual(columns);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('sets the endpoint from the constructor argument', () => {
|
|
16
|
-
const config = new CrudConfigBuilder('https://example.test/api/items', {}).getConfig();
|
|
17
|
-
|
|
18
|
-
expect(config.endpoint).toBe('https://example.test/api/items');
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('getConfig(endpoint) returns a copy with an overridden endpoint without mutating the original config', () => {
|
|
22
|
-
const builder = new CrudConfigBuilder('https://example.test/api/items', {
|
|
23
|
-
grid: { columns: [{ field: 'id' }] },
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
const relatedConfig = builder.getConfig('https://example.test/api/items/1/related');
|
|
27
|
-
|
|
28
|
-
expect(relatedConfig.endpoint).toBe('https://example.test/api/items/1/related');
|
|
29
|
-
expect(builder.getConfig().endpoint).toBe('https://example.test/api/items');
|
|
30
|
-
expect(relatedConfig.grid.columns).toEqual([{ field: 'id' }]);
|
|
31
|
-
});
|
|
32
|
-
});
|
package/lib/deepMerge.spec.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { deepMerge } from './deepMerge.js';
|
|
3
|
-
|
|
4
|
-
describe('deepMerge', () => {
|
|
5
|
-
it('replaces a null target property with a plain object from source', () => {
|
|
6
|
-
const target = { grid: null };
|
|
7
|
-
const source = { grid: { columns: [{ field: 'id' }] } };
|
|
8
|
-
|
|
9
|
-
const result = deepMerge(target, source);
|
|
10
|
-
|
|
11
|
-
expect(result.grid).toEqual({ columns: [{ field: 'id' }] });
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('preserves array order and content when merging a columns array under a previously-null key', () => {
|
|
15
|
-
const columns = [
|
|
16
|
-
{ field: 'id', header: 'ID' },
|
|
17
|
-
{ field: 'name', header: 'Name' },
|
|
18
|
-
{ field: 'continent', header: 'Continent' },
|
|
19
|
-
];
|
|
20
|
-
|
|
21
|
-
const result = deepMerge({ grid: null }, { grid: { columns } });
|
|
22
|
-
|
|
23
|
-
expect(result.grid.columns).toEqual(columns);
|
|
24
|
-
expect(result.grid.columns).not.toBe(columns);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('does not mutate the source when merging into a null target key', () => {
|
|
28
|
-
const source = { grid: { columns: [{ field: 'id' }] } };
|
|
29
|
-
|
|
30
|
-
const result = deepMerge({ grid: null }, source);
|
|
31
|
-
result.grid.columns.push({ field: 'extra' });
|
|
32
|
-
|
|
33
|
-
expect(source.grid.columns.length).toBe(1);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('overwrites configured overwriteProperties instances as-is instead of merging them', () => {
|
|
37
|
-
class Adapter {}
|
|
38
|
-
const targetAdapter = new Adapter();
|
|
39
|
-
const sourceAdapter = new Adapter();
|
|
40
|
-
|
|
41
|
-
const result = deepMerge({ responseAdapter: targetAdapter }, { responseAdapter: sourceAdapter });
|
|
42
|
-
|
|
43
|
-
expect(result.responseAdapter).toBe(sourceAdapter);
|
|
44
|
-
});
|
|
45
|
-
});
|
package/lib/listViewMode.spec.js
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { html } from 'lit';
|
|
3
|
-
import { CrudConfigBuilder } from './CrudConfigBuilder.js';
|
|
4
|
-
import { hasGridView, hasItemView, canSwitchListView } from './listViewMode.js';
|
|
5
|
-
|
|
6
|
-
describe('listViewMode', () => {
|
|
7
|
-
it('detects a grid view from templates.grid', () => {
|
|
8
|
-
const config = new CrudConfigBuilder('https://example.test/api', {
|
|
9
|
-
templates: { grid: () => html`` },
|
|
10
|
-
}).getConfig();
|
|
11
|
-
|
|
12
|
-
expect(hasGridView(config)).toBe(true);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('detects a grid view from grid.columns', () => {
|
|
16
|
-
const config = new CrudConfigBuilder('https://example.test/api', {
|
|
17
|
-
grid: { columns: [{ field: 'id', header: 'ID' }] },
|
|
18
|
-
}).getConfig();
|
|
19
|
-
|
|
20
|
-
expect(hasGridView(config)).toBe(true);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('reports no grid view when neither is configured', () => {
|
|
24
|
-
const config = new CrudConfigBuilder('https://example.test/api', {
|
|
25
|
-
templates: { item: () => html`` },
|
|
26
|
-
}).getConfig();
|
|
27
|
-
|
|
28
|
-
expect(hasGridView(config)).toBe(false);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('detects an item view only when templates.item was explicitly configured', () => {
|
|
32
|
-
const withItem = new CrudConfigBuilder('https://example.test/api', {
|
|
33
|
-
templates: { item: () => html`` },
|
|
34
|
-
}).getConfig();
|
|
35
|
-
const withoutItem = new CrudConfigBuilder('https://example.test/api', {
|
|
36
|
-
grid: { columns: [{ field: 'id', header: 'ID' }] },
|
|
37
|
-
}).getConfig();
|
|
38
|
-
|
|
39
|
-
expect(hasItemView(withItem)).toBe(true);
|
|
40
|
-
expect(hasItemView(withoutItem)).toBe(false);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('allows switching only when both views are configured and the switch is not disabled', () => {
|
|
44
|
-
const both = new CrudConfigBuilder('https://example.test/api', {
|
|
45
|
-
grid: { columns: [{ field: 'id', header: 'ID' }] },
|
|
46
|
-
templates: { item: () => html`` },
|
|
47
|
-
}).getConfig();
|
|
48
|
-
|
|
49
|
-
expect(canSwitchListView(both)).toBe(true);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('disallows switching when customization.disableListWiewSwitch is true', () => {
|
|
53
|
-
const config = new CrudConfigBuilder('https://example.test/api', {
|
|
54
|
-
grid: { columns: [{ field: 'id', header: 'ID' }] },
|
|
55
|
-
templates: { item: () => html`` },
|
|
56
|
-
customization: { disableListWiewSwitch: true },
|
|
57
|
-
}).getConfig();
|
|
58
|
-
|
|
59
|
-
expect(canSwitchListView(config)).toBe(false);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('disallows switching when only one view is configured', () => {
|
|
63
|
-
const gridOnly = new CrudConfigBuilder('https://example.test/api', {
|
|
64
|
-
grid: { columns: [{ field: 'id', header: 'ID' }] },
|
|
65
|
-
}).getConfig();
|
|
66
|
-
const itemOnly = new CrudConfigBuilder('https://example.test/api', {
|
|
67
|
-
templates: { item: () => html`` },
|
|
68
|
-
}).getConfig();
|
|
69
|
-
|
|
70
|
-
expect(canSwitchListView(gridOnly)).toBe(false);
|
|
71
|
-
expect(canSwitchListView(itemOnly)).toBe(false);
|
|
72
|
-
});
|
|
73
|
-
});
|