@nyaruka/temba-components 0.137.0 → 0.138.4
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/.devcontainer/Dockerfile +0 -9
- package/.devcontainer/devcontainer.json +8 -3
- package/.github/workflows/build.yml +6 -1
- package/.github/workflows/cla.yml +1 -1
- package/.github/workflows/publish.yml +6 -1
- package/CHANGELOG.md +42 -0
- package/dist/locales/es.js +5 -5
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/fr.js +5 -5
- package/dist/locales/fr.js.map +1 -1
- package/dist/locales/locale-codes.js +11 -2
- package/dist/locales/locale-codes.js.map +1 -1
- package/dist/locales/pt.js +5 -5
- package/dist/locales/pt.js.map +1 -1
- package/dist/temba-components.js +445 -278
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/display/FloatingTab.js +16 -8
- package/out-tsc/src/display/FloatingTab.js.map +1 -1
- package/out-tsc/src/flow/CanvasMenu.js +33 -15
- package/out-tsc/src/flow/CanvasMenu.js.map +1 -1
- package/out-tsc/src/flow/CanvasNode.js +49 -24
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +583 -70
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/NodeTypeSelector.js +13 -11
- package/out-tsc/src/flow/NodeTypeSelector.js.map +1 -1
- package/out-tsc/src/flow/Plumber.js +110 -64
- package/out-tsc/src/flow/Plumber.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_field.js +5 -1
- package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -1
- package/out-tsc/src/list/RunList.js +2 -1
- package/out-tsc/src/list/RunList.js.map +1 -1
- package/out-tsc/src/list/TicketList.js +2 -1
- package/out-tsc/src/list/TicketList.js.map +1 -1
- package/out-tsc/src/locales/es.js +5 -5
- package/out-tsc/src/locales/es.js.map +1 -1
- package/out-tsc/src/locales/fr.js +5 -5
- package/out-tsc/src/locales/fr.js.map +1 -1
- package/out-tsc/src/locales/locale-codes.js +11 -2
- package/out-tsc/src/locales/locale-codes.js.map +1 -1
- package/out-tsc/src/locales/pt.js +5 -5
- package/out-tsc/src/locales/pt.js.map +1 -1
- package/out-tsc/src/simulator/Simulator.js +11 -4
- package/out-tsc/src/simulator/Simulator.js.map +1 -1
- package/out-tsc/src/store/AppState.js +17 -2
- package/out-tsc/src/store/AppState.js.map +1 -1
- package/out-tsc/test/temba-contact-fields.test.js +3 -3
- package/out-tsc/test/temba-contact-fields.test.js.map +1 -1
- package/out-tsc/test/temba-flow-editor-node.test.js +3 -1
- package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
- package/out-tsc/test/temba-flow-editor-revisions.test.js +106 -0
- package/out-tsc/test/temba-flow-editor-revisions.test.js.map +1 -0
- package/out-tsc/test/temba-flow-editor.test.js +14 -10
- package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
- package/out-tsc/test/temba-flow-plumber-connections.test.js +7 -1
- package/out-tsc/test/temba-flow-plumber-connections.test.js.map +1 -1
- package/out-tsc/test/temba-flow-plumber.test.js +6 -0
- package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
- package/out-tsc/test/temba-select.test.js +1 -0
- package/out-tsc/test/temba-select.test.js.map +1 -1
- package/package.json +1 -1
- package/screenshots/truth/floating-tab/gray.png +0 -0
- package/screenshots/truth/floating-tab/green.png +0 -0
- package/screenshots/truth/floating-tab/purple.png +0 -0
- package/screenshots/truth/node-type-selector/action-mode.png +0 -0
- package/screenshots/truth/node-type-selector/split-mode.png +0 -0
- package/src/display/FloatingTab.ts +18 -8
- package/src/flow/CanvasMenu.ts +38 -16
- package/src/flow/CanvasNode.ts +62 -29
- package/src/flow/Editor.ts +699 -74
- package/src/flow/NodeTypeSelector.ts +13 -11
- package/src/flow/Plumber.ts +123 -69
- package/src/flow/actions/set_contact_field.ts +5 -1
- package/src/list/RunList.ts +2 -1
- package/src/list/TicketList.ts +2 -1
- package/src/locales/es.ts +18 -13
- package/src/locales/fr.ts +18 -13
- package/src/locales/locale-codes.ts +11 -2
- package/src/locales/pt.ts +18 -13
- package/src/simulator/Simulator.ts +11 -5
- package/src/store/AppState.ts +18 -2
- package/test/temba-contact-fields.test.ts +8 -3
- package/test/temba-flow-editor-node.test.ts +3 -1
- package/test/temba-flow-editor-revisions.test.ts +134 -0
- package/test/temba-flow-editor.test.ts +16 -10
- package/test/temba-flow-plumber-connections.test.ts +7 -1
- package/test/temba-flow-plumber.test.ts +6 -0
- package/test/temba-select.test.ts +1 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { html, fixture, expect } from '@open-wc/testing';
|
|
2
|
+
import { Editor } from '../src/flow/Editor';
|
|
3
|
+
import { stub, restore, SinonStub } from 'sinon';
|
|
4
|
+
|
|
5
|
+
customElements.define('temba-flow-editor-revisions', Editor);
|
|
6
|
+
|
|
7
|
+
describe('Editor Revisions', () => {
|
|
8
|
+
let element: Editor;
|
|
9
|
+
let fetchStub: SinonStub;
|
|
10
|
+
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
restore();
|
|
13
|
+
fetchStub = stub(window, 'fetch');
|
|
14
|
+
// Initialize without 'flow' attribute to prevent firstUpdated from calling getStore().getState()
|
|
15
|
+
element = await fixture(
|
|
16
|
+
html`<temba-flow-editor-revisions></temba-flow-editor-revisions>`
|
|
17
|
+
);
|
|
18
|
+
element.flow = 'test-flow';
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
restore();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should exclude the most recent revision from the list', async () => {
|
|
26
|
+
const mockRevisions = [
|
|
27
|
+
{
|
|
28
|
+
id: 3,
|
|
29
|
+
created_on: '2023-01-03',
|
|
30
|
+
user: { id: 1, first_name: 'A', last_name: 'B', username: 'ab' }
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 2,
|
|
34
|
+
created_on: '2023-01-02',
|
|
35
|
+
user: { id: 1, first_name: 'A', last_name: 'B', username: 'ab' }
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: 1,
|
|
39
|
+
created_on: '2023-01-01',
|
|
40
|
+
user: { id: 1, first_name: 'A', last_name: 'B', username: 'ab' }
|
|
41
|
+
}
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
// Mock the fetch response for the revisions list
|
|
45
|
+
const mockResponse = new Response(
|
|
46
|
+
JSON.stringify({ results: mockRevisions }),
|
|
47
|
+
{
|
|
48
|
+
status: 200,
|
|
49
|
+
headers: { 'Content-Type': 'application/json' }
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
fetchStub.resolves(mockResponse);
|
|
54
|
+
|
|
55
|
+
// Call fetchRevisions (private)
|
|
56
|
+
// Note: fetchRevisions calls fetchResults -> fetchResultsPage -> fetch
|
|
57
|
+
await (element as any).fetchRevisions();
|
|
58
|
+
|
|
59
|
+
const revisions = (element as any).revisions;
|
|
60
|
+
expect(revisions.length).to.equal(2);
|
|
61
|
+
expect(revisions[0].id).to.equal(2);
|
|
62
|
+
expect(revisions[1].id).to.equal(1);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should handle empty revisions list', async () => {
|
|
66
|
+
const mockResponse = new Response(JSON.stringify({ results: [] }), {
|
|
67
|
+
status: 200
|
|
68
|
+
});
|
|
69
|
+
fetchStub.resolves(mockResponse);
|
|
70
|
+
|
|
71
|
+
await (element as any).fetchRevisions();
|
|
72
|
+
const revisions = (element as any).revisions;
|
|
73
|
+
expect(revisions.length).to.equal(0);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should handle single revision in list', async () => {
|
|
77
|
+
const mockRevisions = [
|
|
78
|
+
{
|
|
79
|
+
id: 1,
|
|
80
|
+
created_on: '2023-01-01',
|
|
81
|
+
user: { id: 1, first_name: 'A', last_name: 'B', username: 'ab' }
|
|
82
|
+
}
|
|
83
|
+
];
|
|
84
|
+
const mockResponse = new Response(
|
|
85
|
+
JSON.stringify({ results: mockRevisions }),
|
|
86
|
+
{ status: 200 }
|
|
87
|
+
);
|
|
88
|
+
fetchStub.resolves(mockResponse);
|
|
89
|
+
|
|
90
|
+
await (element as any).fetchRevisions();
|
|
91
|
+
const revisions = (element as any).revisions;
|
|
92
|
+
expect(revisions.length).to.equal(0);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should have purple color for revisions tab and blue for selected item', async () => {
|
|
96
|
+
// Force revisions window to show
|
|
97
|
+
(element as any).revisionsWindowHidden = false;
|
|
98
|
+
(element as any).localizationWindowHidden = true;
|
|
99
|
+
|
|
100
|
+
// Mock revisions so we can see list items
|
|
101
|
+
const mockRevisions = [
|
|
102
|
+
{
|
|
103
|
+
id: 2,
|
|
104
|
+
created_on: '2023-01-02',
|
|
105
|
+
user: { id: 1, first_name: 'A', last_name: 'B', username: 'ab' }
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: 1,
|
|
109
|
+
created_on: '2023-01-01',
|
|
110
|
+
user: { id: 1, first_name: 'A', last_name: 'B', username: 'ab' }
|
|
111
|
+
}
|
|
112
|
+
];
|
|
113
|
+
(element as any).revisions = mockRevisions;
|
|
114
|
+
(element as any).viewingRevision = mockRevisions[0]; // Select the first one
|
|
115
|
+
|
|
116
|
+
await element.requestUpdate();
|
|
117
|
+
|
|
118
|
+
// Check tab color
|
|
119
|
+
const tab = element.querySelector('#revisions-tab');
|
|
120
|
+
expect(tab).to.exist;
|
|
121
|
+
expect(tab.getAttribute('color')).to.equal('rgb(142, 94, 167)');
|
|
122
|
+
|
|
123
|
+
// Check selected item styles
|
|
124
|
+
const selectedItem = element.querySelector(
|
|
125
|
+
'.revision-item.selected'
|
|
126
|
+
) as HTMLElement;
|
|
127
|
+
expect(selectedItem).to.exist;
|
|
128
|
+
|
|
129
|
+
// We need to check inline styles because they are set in the template
|
|
130
|
+
const style = selectedItem.getAttribute('style');
|
|
131
|
+
expect(style).to.contain('#f0f6ff'); // Blue background
|
|
132
|
+
expect(style).to.contain('#a4cafe'); // Blue border
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -653,11 +653,14 @@ describe('Editor', () => {
|
|
|
653
653
|
|
|
654
654
|
await editor.updateComplete;
|
|
655
655
|
|
|
656
|
-
// node-2
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
expect(
|
|
656
|
+
// node-2 is at top (top: 100 < top: 200) so it should be flow-start
|
|
657
|
+
const node1 = editor.querySelector('temba-flow-node[uuid="node-1"]');
|
|
658
|
+
const node2 = editor.querySelector('temba-flow-node[uuid="node-2"]');
|
|
659
|
+
|
|
660
|
+
expect(node2).to.exist;
|
|
661
|
+
expect(node1).to.exist;
|
|
662
|
+
expect(node2.classList.contains('flow-start')).to.be.true;
|
|
663
|
+
expect(node1.classList.contains('flow-start')).to.be.false;
|
|
661
664
|
|
|
662
665
|
// move node-1 to the top
|
|
663
666
|
zustand.getState().updateCanvasPositions({
|
|
@@ -720,7 +723,7 @@ describe('Editor', () => {
|
|
|
720
723
|
|
|
721
724
|
await editor.updateComplete;
|
|
722
725
|
|
|
723
|
-
|
|
726
|
+
const flowNodes = editor.querySelectorAll('temba-flow-node');
|
|
724
727
|
expect(flowNodes[0].classList.contains('flow-start')).to.be.true;
|
|
725
728
|
|
|
726
729
|
// add a new node at the top
|
|
@@ -741,10 +744,13 @@ describe('Editor', () => {
|
|
|
741
744
|
await editor.updateComplete;
|
|
742
745
|
|
|
743
746
|
// new node should now be the flow-start
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
expect(
|
|
747
|
+
const node1 = editor.querySelector('temba-flow-node[uuid="node-1"]');
|
|
748
|
+
const node2 = editor.querySelector('temba-flow-node[uuid="node-2"]');
|
|
749
|
+
|
|
750
|
+
expect(node2).to.exist;
|
|
751
|
+
expect(node1).to.exist;
|
|
752
|
+
expect(node2.classList.contains('flow-start')).to.be.true;
|
|
753
|
+
expect(node1.classList.contains('flow-start')).to.be.false;
|
|
748
754
|
});
|
|
749
755
|
|
|
750
756
|
it('should handle flow-start when first node is removed', async () => {
|
|
@@ -29,7 +29,14 @@ describe('Plumber - Connection Management', () => {
|
|
|
29
29
|
removeClass: stub(),
|
|
30
30
|
batch: stub().callsFake((fn: any) => fn()),
|
|
31
31
|
addEndpoint: stub().returns({}),
|
|
32
|
+
revalidate: stub(),
|
|
32
33
|
connect: stub(),
|
|
34
|
+
getEndpoints: stub().returns([
|
|
35
|
+
{ elementId: 'test-from', addClass: stub() }
|
|
36
|
+
]),
|
|
37
|
+
select: stub().returns({
|
|
38
|
+
deleteAll: stub()
|
|
39
|
+
}),
|
|
33
40
|
selectEndpoints: stub().returns({
|
|
34
41
|
deleteAll: stub()
|
|
35
42
|
}),
|
|
@@ -131,7 +138,6 @@ describe('Plumber - Connection Management', () => {
|
|
|
131
138
|
expect(result).to.be.true;
|
|
132
139
|
expect((plumber as any).jsPlumb.deleteConnection).to.have.been
|
|
133
140
|
.calledTwice;
|
|
134
|
-
expect((plumber as any).jsPlumb.removeAllEndpoints).to.have.been.called;
|
|
135
141
|
});
|
|
136
142
|
|
|
137
143
|
it('returns false when no connections exist', () => {
|
|
@@ -31,6 +31,12 @@ describe('Plumber', () => {
|
|
|
31
31
|
batch: stub().callsFake((fn) => fn()),
|
|
32
32
|
addEndpoint: stub().returns({}),
|
|
33
33
|
connect: stub(),
|
|
34
|
+
getEndpoints: stub().returns([
|
|
35
|
+
{ elementId: 'test-from', addClass: stub() }
|
|
36
|
+
]),
|
|
37
|
+
select: stub().returns({
|
|
38
|
+
deleteAll: stub()
|
|
39
|
+
}),
|
|
34
40
|
selectEndpoints: stub().returns({
|
|
35
41
|
deleteAll: stub()
|
|
36
42
|
}),
|