@dile/crud 2.8.5 → 2.8.7

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 (18) hide show
  1. package/components/action/src/DileCrudSingleActions.js +30 -2
  2. package/components/action/src/DileCrudSingleActions.spec.js +126 -0
  3. 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
  4. package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-initialData-is-cleared-after-being-applied-1.png +0 -0
  5. package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-initialData-is-only-applied-once-and-cleared-after-opening-1.png +0 -0
  6. package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-triggerAction-behavior-matches-original-when-no-initialData-1.png +0 -0
  7. package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-triggerAction-opens-confirm-dialog-1.png +0 -0
  8. package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-triggerAction-with-initialData-sets-form-values-1.png +0 -0
  9. package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-triggerAction-with-initialData-sets-form-values-before-opening-dialog-1.png +0 -0
  10. package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-triggerAction-without-initialData-resets-form-1.png +0 -0
  11. package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-triggerAction-without-initialData-resets-form-to-empty-1.png +0 -0
  12. package/components/single/src/DileCrudSingle.js +6 -2
  13. package/components/single/src/DileCrudSingle.spec.js +151 -0
  14. package/components/single/src/__screenshots__/DileCrudSingle.spec.js/DileCrudSingle-triggerAction-and-triggerDirectAction-triggerAction-delegates-to-singleActionsElement-triggerAction-1.png +0 -0
  15. package/components/single/src/__screenshots__/DileCrudSingle.spec.js/DileCrudSingle-triggerAction-and-triggerDirectAction-triggerAction-without-initialData-works-1.png +0 -0
  16. package/components/single/src/__screenshots__/DileCrudSingle.spec.js/DileCrudSingle-triggerAction-and-triggerDirectAction-triggerDirectAction-delegates-to-singleActionsElement-triggerAction-1.png +0 -0
  17. package/components/single/src/__screenshots__/DileCrudSingle.spec.js/DileCrudSingle-triggerAction-and-triggerDirectAction-triggerDirectAction-without-initialData-still-works--backward-compatibility--1.png +0 -0
  18. package/package.json +3 -3
@@ -47,6 +47,7 @@ export class DileCrudSingleActions extends DileCrudActions {
47
47
  super();
48
48
  this.actions = [];
49
49
  this.directSingleActions = [];
50
+ this._initialData = null;
50
51
  }
51
52
 
52
53
  get actionListTemplate() {
@@ -94,14 +95,41 @@ export class DileCrudSingleActions extends DileCrudActions {
94
95
  }
95
96
  }
96
97
 
97
- triggerAction(actionName) {
98
+ async showAction() {
99
+ super.showAction();
100
+ // Wait for the form to be rendered after the confirm opens
101
+ let form = this.selectedActionForm;
102
+ let attempts = 0;
103
+ while (!form && attempts < 50) {
104
+ await this.updateComplete;
105
+ await new Promise(r => setTimeout(r, 10));
106
+ form = this.selectedActionForm;
107
+ attempts++;
108
+ }
109
+
110
+ if (form) {
111
+ if (this._initialData) {
112
+ await form.setData(this._initialData);
113
+ this._initialData = null;
114
+ } else {
115
+ // Reset form if no initial data
116
+ await form.resetData();
117
+ }
118
+ }
119
+ }
120
+
121
+ async triggerAction(actionName, initialData) {
98
122
  this.selection = actionName;
99
123
  this.computeDestructive();
100
124
  const action = this.actions.find(a => a.name === actionName);
101
125
  if (action && action.onClick) {
102
126
  action.onClick(this.element);
103
127
  } else {
104
- this.showAction();
128
+ // Store initial data for showAction to apply
129
+ if (initialData) {
130
+ this._initialData = initialData;
131
+ }
132
+ await this.showAction();
105
133
  }
106
134
  }
107
135
  }
@@ -0,0 +1,126 @@
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-actions.js';
5
+
6
+ // Create a test form component that extends DileForm
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('DileCrudSingleActions with triggerAction', () => {
21
+ afterEach(() => {
22
+ document.body.innerHTML = '';
23
+ });
24
+
25
+ const testActions = [
26
+ {
27
+ name: 'CreateInvoiceAction',
28
+ label: 'Create Invoice',
29
+ destructive: false,
30
+ },
31
+ ];
32
+
33
+ function createFormTemplate(selection) {
34
+ return (selection, element) => html`
35
+ ${selection === 'CreateInvoiceAction'
36
+ ? html`<test-action-form action="CreateInvoiceAction"></test-action-form>`
37
+ : ''
38
+ }
39
+ `;
40
+ }
41
+
42
+ async function createSingleActions() {
43
+ const el = document.createElement('dile-crud-single-actions');
44
+ el.actions = testActions;
45
+ el.directSingleActions = [];
46
+ el.formActionsTemplate = createFormTemplate(testActions[0].name);
47
+ el.actionIds = [1];
48
+ el.element = { id: 1, name: 'Test Item' };
49
+ el.endpoint = 'https://example.test/api';
50
+ el.language = 'en';
51
+ document.body.appendChild(el);
52
+ await el.updateComplete;
53
+ return el;
54
+ }
55
+
56
+ async function waitForForm(el, maxWait = 2000) {
57
+ const startTime = Date.now();
58
+ while (Date.now() - startTime < maxWait) {
59
+ const form = el.selectedActionForm;
60
+ if (form) {
61
+ await form.updateComplete;
62
+ return form;
63
+ }
64
+ await new Promise(r => setTimeout(r, 50));
65
+ }
66
+ return null;
67
+ }
68
+
69
+ it('triggerAction with initialData sets form values', async () => {
70
+ const el = await createSingleActions();
71
+
72
+ const initialData = { productUuid: 'uuid-123', units: '5' };
73
+ el.triggerAction('CreateInvoiceAction', initialData);
74
+
75
+ const form = await waitForForm(el);
76
+ expect(form).toBeTruthy();
77
+
78
+ const productInput = form.shadowRoot.querySelector('[name="productUuid"]');
79
+ const unitsInput = form.shadowRoot.querySelector('[name="units"]');
80
+
81
+ expect(productInput.value).toBe('uuid-123');
82
+ expect(unitsInput.value).toBe('5');
83
+ });
84
+
85
+ it('triggerAction without initialData resets form to empty', async () => {
86
+ const el = await createSingleActions();
87
+
88
+ // First open with initial data
89
+ el.triggerAction('CreateInvoiceAction', { productUuid: 'uuid-old', units: '10' });
90
+ let form = await waitForForm(el);
91
+ expect(form.shadowRoot.querySelector('[name="productUuid"]').value).toBe('uuid-old');
92
+
93
+ // Close the dialog
94
+ const confirm = el.confirmElement;
95
+ confirm.close();
96
+ await new Promise(r => setTimeout(r, 100));
97
+
98
+ // Open again without initial data - should reset
99
+ el.triggerAction('CreateInvoiceAction');
100
+ form = await waitForForm(el);
101
+
102
+ // Form should be empty after reset
103
+ expect(form.shadowRoot.querySelector('[name="productUuid"]').value).toBe('');
104
+ });
105
+
106
+ it('triggerAction opens confirm dialog', async () => {
107
+ const el = await createSingleActions();
108
+
109
+ el.triggerAction('CreateInvoiceAction');
110
+ await waitForForm(el);
111
+
112
+ const confirm = el.confirmElement;
113
+ expect(confirm).toBeTruthy();
114
+ });
115
+
116
+ it('initialData is cleared after being applied', async () => {
117
+ const el = await createSingleActions();
118
+
119
+ el.triggerAction('CreateInvoiceAction', { productUuid: 'uuid-first' });
120
+ await waitForForm(el);
121
+
122
+ // _initialData should be null after being used
123
+ expect(el._initialData).toBeNull();
124
+ });
125
+ });
126
+
@@ -174,8 +174,12 @@ export class DileCrudSingle extends DileI18nMixin(DileCrudMixin(LitElement)) {
174
174
  this.detailElement.refresh();
175
175
  }
176
176
 
177
- triggerDirectAction(actionName) {
178
- this.singleActionsElement.triggerAction(actionName);
177
+ triggerDirectAction(actionName, initialData) {
178
+ this.singleActionsElement.triggerAction(actionName, initialData);
179
+ }
180
+
181
+ triggerAction(actionName, initialData) {
182
+ this.singleActionsElement.triggerAction(actionName, initialData);
179
183
  }
180
184
 
181
185
  elementLoaded(e) {
@@ -0,0 +1,151 @@
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
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dile/crud",
3
- "version": "2.8.5",
3
+ "version": "2.8.7",
4
4
  "description": "Components to create a generic crud system based on Web Components and Lit",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -25,12 +25,12 @@
25
25
  },
26
26
  "homepage": "https://dile-components.com/",
27
27
  "dependencies": {
28
- "@dile/ui": "^3.13.1",
28
+ "@dile/ui": "^3.14.0",
29
29
  "axios": "^1.19.0",
30
30
  "lit": "^2.7.0 || ^3.0.0"
31
31
  },
32
32
  "publishConfig": {
33
33
  "access": "public"
34
34
  },
35
- "gitHead": "ca740f524209d56344b4b0090d9a82c8f1c0782f"
35
+ "gitHead": "2dbcfcfc126715e1f21c28806714f5f59b89d93b"
36
36
  }