@nyaruka/temba-components 0.130.4 → 0.130.5
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/CHANGELOG.md +14 -0
- package/demo/sortable-rules-demo.html +155 -0
- package/dist/temba-components.js +132 -142
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/events.js.map +1 -1
- package/out-tsc/src/flow/CanvasNode.js +13 -7
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/actions/send_msg.js +1 -0
- package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_groups.js +149 -1
- package/out-tsc/src/flow/nodes/split_by_groups.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +1 -0
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_response.js +81 -75
- package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
- package/out-tsc/src/form/ArrayEditor.js +106 -28
- package/out-tsc/src/form/ArrayEditor.js.map +1 -1
- package/out-tsc/src/form/select/Select.js +21 -25
- package/out-tsc/src/form/select/Select.js.map +1 -1
- package/out-tsc/src/list/SortableList.js +214 -140
- package/out-tsc/src/list/SortableList.js.map +1 -1
- package/out-tsc/src/live/ContactChat.js +9 -5
- package/out-tsc/src/live/ContactChat.js.map +1 -1
- package/out-tsc/test/nodes/split_by_groups.test.js +130 -0
- package/out-tsc/test/nodes/split_by_groups.test.js.map +1 -0
- package/out-tsc/test/nodes/wait_for_response.test.js +149 -0
- package/out-tsc/test/nodes/wait_for_response.test.js.map +1 -1
- package/out-tsc/test/temba-field-config.test.js +56 -0
- package/out-tsc/test/temba-field-config.test.js.map +1 -1
- package/package.json +1 -1
- package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
- package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
- package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
- package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
- package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
- package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
- package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
- package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
- package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
- package/screenshots/truth/editor/wait.png +0 -0
- package/screenshots/truth/field-renderer/select-with-label.png +0 -0
- package/screenshots/truth/list/fields-dragging.png +0 -0
- package/screenshots/truth/list/sortable-dragging.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/editor/information-extraction.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/editor/sentiment-analysis.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/editor/summarization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/editor/translation-task.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
- package/screenshots/truth/select/search-enabled.png +0 -0
- package/screenshots/truth/select/search-selected-focus.png +0 -0
- package/screenshots/truth/select/search-selected.png +0 -0
- package/screenshots/truth/templates/default.png +0 -0
- package/screenshots/truth/templates/unapproved.png +0 -0
- package/src/events.ts +6 -6
- package/src/flow/CanvasNode.ts +15 -13
- package/src/flow/actions/send_msg.ts +1 -0
- package/src/flow/nodes/split_by_groups.ts +190 -1
- package/src/flow/nodes/split_by_llm_categorize.ts +1 -0
- package/src/flow/nodes/wait_for_response.ts +98 -74
- package/src/form/ArrayEditor.ts +112 -28
- package/src/form/select/Select.ts +24 -25
- package/src/list/SortableList.ts +250 -149
- package/src/live/ContactChat.ts +11 -5
- package/test/nodes/split_by_groups.test.ts +165 -0
- package/test/nodes/wait_for_response.test.ts +182 -0
- package/test/temba-field-config.test.ts +69 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { expect } from '@open-wc/testing';
|
|
2
|
+
import { split_by_groups } from '../../src/flow/nodes/split_by_groups';
|
|
3
|
+
import { Node } from '../../src/store/flow-definition.d';
|
|
4
|
+
|
|
5
|
+
describe('temba-split-by-groups', () => {
|
|
6
|
+
it('should transform from flow definition to form data correctly', () => {
|
|
7
|
+
const node: Node = {
|
|
8
|
+
uuid: 'test-node-uuid',
|
|
9
|
+
actions: [],
|
|
10
|
+
router: {
|
|
11
|
+
type: 'switch',
|
|
12
|
+
cases: [
|
|
13
|
+
{
|
|
14
|
+
uuid: 'case-1',
|
|
15
|
+
type: 'has_group',
|
|
16
|
+
arguments: ['group-uuid-1', 'Group 1'],
|
|
17
|
+
category_uuid: 'cat-1'
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
uuid: 'case-2',
|
|
21
|
+
type: 'has_group',
|
|
22
|
+
arguments: ['group-uuid-2', 'Group 2'],
|
|
23
|
+
category_uuid: 'cat-2'
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
categories: [
|
|
27
|
+
{ uuid: 'cat-1', name: 'Group 1', exit_uuid: 'exit-1' },
|
|
28
|
+
{ uuid: 'cat-2', name: 'Group 2', exit_uuid: 'exit-2' },
|
|
29
|
+
{ uuid: 'cat-other', name: 'Other', exit_uuid: 'exit-other' }
|
|
30
|
+
],
|
|
31
|
+
default_category_uuid: 'cat-other',
|
|
32
|
+
operand: '@contact.groups',
|
|
33
|
+
result_name: ''
|
|
34
|
+
},
|
|
35
|
+
exits: [
|
|
36
|
+
{ uuid: 'exit-1', destination_uuid: null },
|
|
37
|
+
{ uuid: 'exit-2', destination_uuid: null },
|
|
38
|
+
{ uuid: 'exit-other', destination_uuid: null }
|
|
39
|
+
]
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const formData = split_by_groups.toFormData!(node);
|
|
43
|
+
|
|
44
|
+
expect(formData.uuid).to.equal('test-node-uuid');
|
|
45
|
+
expect(formData.groups).to.have.lengthOf(2);
|
|
46
|
+
expect(formData.groups[0]).to.deep.equal({
|
|
47
|
+
uuid: 'group-uuid-1',
|
|
48
|
+
name: 'Group 1'
|
|
49
|
+
});
|
|
50
|
+
expect(formData.groups[1]).to.deep.equal({
|
|
51
|
+
uuid: 'group-uuid-2',
|
|
52
|
+
name: 'Group 2'
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should transform from form data to flow definition correctly', () => {
|
|
57
|
+
const originalNode: Node = {
|
|
58
|
+
uuid: 'test-node-uuid',
|
|
59
|
+
actions: [],
|
|
60
|
+
exits: []
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const formData = {
|
|
64
|
+
uuid: 'test-node-uuid',
|
|
65
|
+
groups: [
|
|
66
|
+
{ uuid: 'group-uuid-1', name: 'Group 1' },
|
|
67
|
+
{ uuid: 'group-uuid-2', name: 'Group 2' }
|
|
68
|
+
]
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const resultNode = split_by_groups.fromFormData!(formData, originalNode);
|
|
72
|
+
|
|
73
|
+
expect(resultNode.uuid).to.equal('test-node-uuid');
|
|
74
|
+
expect(resultNode.router!.type).to.equal('switch');
|
|
75
|
+
expect(resultNode.router!.operand).to.equal('@contact.groups');
|
|
76
|
+
expect(resultNode.router!.cases).to.have.lengthOf(2);
|
|
77
|
+
expect(resultNode.router!.categories).to.have.lengthOf(3); // 2 groups + Other
|
|
78
|
+
expect(resultNode.exits).to.have.lengthOf(3); // 2 groups + Other
|
|
79
|
+
|
|
80
|
+
// Check first group case
|
|
81
|
+
const case1 = resultNode.router!.cases![0];
|
|
82
|
+
expect(case1.type).to.equal('has_group');
|
|
83
|
+
expect(case1.arguments).to.deep.equal(['group-uuid-1', 'Group 1']);
|
|
84
|
+
|
|
85
|
+
// Check that "Other" category exists
|
|
86
|
+
const otherCategory = resultNode.router!.categories!.find(
|
|
87
|
+
(cat) => cat.name === 'Other'
|
|
88
|
+
);
|
|
89
|
+
expect(otherCategory).to.exist;
|
|
90
|
+
expect(resultNode.router!.default_category_uuid).to.equal(
|
|
91
|
+
otherCategory!.uuid
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should validate form data correctly', () => {
|
|
96
|
+
// Valid form data
|
|
97
|
+
const validData = {
|
|
98
|
+
groups: [{ uuid: 'group-uuid-1', name: 'Group 1' }]
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const validResult = split_by_groups.validate!(validData);
|
|
102
|
+
expect(validResult.valid).to.be.true;
|
|
103
|
+
expect(Object.keys(validResult.errors)).to.have.lengthOf(0);
|
|
104
|
+
|
|
105
|
+
// Invalid form data - no groups
|
|
106
|
+
const invalidData = {
|
|
107
|
+
groups: []
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const invalidResult = split_by_groups.validate!(invalidData);
|
|
111
|
+
expect(invalidResult.valid).to.be.false;
|
|
112
|
+
expect(invalidResult.errors.groups).to.equal(
|
|
113
|
+
'At least one group is required'
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
// Invalid form data - missing groups
|
|
117
|
+
const missingGroupsData = {};
|
|
118
|
+
|
|
119
|
+
const missingResult = split_by_groups.validate!(missingGroupsData);
|
|
120
|
+
expect(missingResult.valid).to.be.false;
|
|
121
|
+
expect(missingResult.errors.groups).to.equal(
|
|
122
|
+
'At least one group is required'
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should handle arbitrary groups correctly', () => {
|
|
127
|
+
const originalNode: Node = {
|
|
128
|
+
uuid: 'test-node-uuid',
|
|
129
|
+
actions: [],
|
|
130
|
+
exits: []
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const formData = {
|
|
134
|
+
uuid: 'test-node-uuid',
|
|
135
|
+
groups: [
|
|
136
|
+
{ uuid: 'group-uuid-1', name: 'Existing Group' },
|
|
137
|
+
{ name: 'New Group', arbitrary: true }
|
|
138
|
+
]
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const resultNode = split_by_groups.fromFormData!(formData, originalNode);
|
|
142
|
+
|
|
143
|
+
expect(resultNode.router!.cases).to.have.lengthOf(2);
|
|
144
|
+
|
|
145
|
+
// Check existing group
|
|
146
|
+
const existingGroupCase = resultNode.router!.cases!.find(
|
|
147
|
+
(c) => c.arguments![0] === 'group-uuid-1'
|
|
148
|
+
);
|
|
149
|
+
expect(existingGroupCase).to.exist;
|
|
150
|
+
expect(existingGroupCase!.arguments).to.deep.equal([
|
|
151
|
+
'group-uuid-1',
|
|
152
|
+
'Existing Group'
|
|
153
|
+
]);
|
|
154
|
+
|
|
155
|
+
// Check arbitrary group (should have generated UUID)
|
|
156
|
+
const arbitraryGroupCase = resultNode.router!.cases!.find(
|
|
157
|
+
(c) => c.arguments![1] === 'New Group'
|
|
158
|
+
);
|
|
159
|
+
expect(arbitraryGroupCase).to.exist;
|
|
160
|
+
expect(arbitraryGroupCase!.arguments![0]).to.match(
|
|
161
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
|
|
162
|
+
);
|
|
163
|
+
expect(arbitraryGroupCase!.arguments![1]).to.equal('New Group');
|
|
164
|
+
});
|
|
165
|
+
});
|
|
@@ -755,5 +755,187 @@ describe('wait_for_response node config', () => {
|
|
|
755
755
|
);
|
|
756
756
|
expect(secondCases).to.have.length(1);
|
|
757
757
|
});
|
|
758
|
+
|
|
759
|
+
it('preserves category UUIDs when rules are reordered', () => {
|
|
760
|
+
const originalNode = {
|
|
761
|
+
uuid: 'test-node',
|
|
762
|
+
actions: [],
|
|
763
|
+
router: {
|
|
764
|
+
type: 'switch' as const,
|
|
765
|
+
operand: '@input.text',
|
|
766
|
+
categories: [
|
|
767
|
+
{
|
|
768
|
+
uuid: 'yes-category-uuid',
|
|
769
|
+
name: 'Yes',
|
|
770
|
+
exit_uuid: 'yes-exit-uuid'
|
|
771
|
+
},
|
|
772
|
+
{
|
|
773
|
+
uuid: 'no-category-uuid',
|
|
774
|
+
name: 'No',
|
|
775
|
+
exit_uuid: 'no-exit-uuid'
|
|
776
|
+
},
|
|
777
|
+
{
|
|
778
|
+
uuid: 'other-category-uuid',
|
|
779
|
+
name: 'Other',
|
|
780
|
+
exit_uuid: 'other-exit-uuid'
|
|
781
|
+
}
|
|
782
|
+
],
|
|
783
|
+
cases: [
|
|
784
|
+
{
|
|
785
|
+
uuid: 'yes-case-uuid',
|
|
786
|
+
type: 'has_phrase',
|
|
787
|
+
arguments: ['yes'],
|
|
788
|
+
category_uuid: 'yes-category-uuid'
|
|
789
|
+
},
|
|
790
|
+
{
|
|
791
|
+
uuid: 'no-case-uuid',
|
|
792
|
+
type: 'has_phrase',
|
|
793
|
+
arguments: ['no'],
|
|
794
|
+
category_uuid: 'no-category-uuid'
|
|
795
|
+
}
|
|
796
|
+
]
|
|
797
|
+
},
|
|
798
|
+
exits: [
|
|
799
|
+
{ uuid: 'yes-exit-uuid', destination_uuid: null },
|
|
800
|
+
{ uuid: 'no-exit-uuid', destination_uuid: null },
|
|
801
|
+
{ uuid: 'other-exit-uuid', destination_uuid: null }
|
|
802
|
+
]
|
|
803
|
+
};
|
|
804
|
+
|
|
805
|
+
// Reorder the rules - put "No" first, "Yes" second
|
|
806
|
+
const formData = {
|
|
807
|
+
uuid: 'test-node',
|
|
808
|
+
result_name: 'response',
|
|
809
|
+
rules: [
|
|
810
|
+
{
|
|
811
|
+
operator: { value: 'has_phrase', name: 'contains phrase' },
|
|
812
|
+
value1: 'no',
|
|
813
|
+
category: 'No' // This rule was originally second
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
operator: { value: 'has_phrase', name: 'contains phrase' },
|
|
817
|
+
value1: 'yes',
|
|
818
|
+
category: 'Yes' // This rule was originally first
|
|
819
|
+
}
|
|
820
|
+
]
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
const result = wait_for_response.fromFormData!(formData, originalNode);
|
|
824
|
+
|
|
825
|
+
// Verify that categories keep their original UUIDs despite reordering
|
|
826
|
+
expect(result.router?.categories).to.have.length(3); // Two rules + Other
|
|
827
|
+
|
|
828
|
+
const yesCategory = result.router!.categories.find(
|
|
829
|
+
(cat) => cat.name === 'Yes'
|
|
830
|
+
);
|
|
831
|
+
const noCategory = result.router!.categories.find(
|
|
832
|
+
(cat) => cat.name === 'No'
|
|
833
|
+
);
|
|
834
|
+
const otherCategory = result.router!.categories.find(
|
|
835
|
+
(cat) => cat.name === 'Other'
|
|
836
|
+
);
|
|
837
|
+
|
|
838
|
+
// Categories should preserve their original UUIDs
|
|
839
|
+
expect(yesCategory?.uuid).to.equal('yes-category-uuid');
|
|
840
|
+
expect(yesCategory?.exit_uuid).to.equal('yes-exit-uuid');
|
|
841
|
+
|
|
842
|
+
expect(noCategory?.uuid).to.equal('no-category-uuid');
|
|
843
|
+
expect(noCategory?.exit_uuid).to.equal('no-exit-uuid');
|
|
844
|
+
|
|
845
|
+
expect(otherCategory?.uuid).to.equal('other-category-uuid');
|
|
846
|
+
expect(otherCategory?.exit_uuid).to.equal('other-exit-uuid');
|
|
847
|
+
|
|
848
|
+
// Verify the cases are created in the new order but reference correct categories
|
|
849
|
+
expect(result.router?.cases).to.have.length(2);
|
|
850
|
+
|
|
851
|
+
const firstCase = result.router!.cases[0];
|
|
852
|
+
const secondCase = result.router!.cases[1];
|
|
853
|
+
|
|
854
|
+
// First case should be "no" and reference the No category
|
|
855
|
+
expect(firstCase.arguments).to.deep.equal(['no']);
|
|
856
|
+
expect(firstCase.category_uuid).to.equal('no-category-uuid');
|
|
857
|
+
|
|
858
|
+
// Second case should be "yes" and reference the Yes category
|
|
859
|
+
expect(secondCase.arguments).to.deep.equal(['yes']);
|
|
860
|
+
expect(secondCase.category_uuid).to.equal('yes-category-uuid');
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
it('preserves rule order when rules have duplicate categories', () => {
|
|
864
|
+
const formData = {
|
|
865
|
+
uuid: 'test-node',
|
|
866
|
+
result_name: 'response',
|
|
867
|
+
rules: [
|
|
868
|
+
{
|
|
869
|
+
operator: { value: 'has_phrase', name: 'contains phrase' },
|
|
870
|
+
value1: 'first',
|
|
871
|
+
category: 'Shared'
|
|
872
|
+
},
|
|
873
|
+
{
|
|
874
|
+
operator: { value: 'has_phrase', name: 'contains phrase' },
|
|
875
|
+
value1: 'middle',
|
|
876
|
+
category: 'Different'
|
|
877
|
+
},
|
|
878
|
+
{
|
|
879
|
+
operator: { value: 'has_phrase', name: 'contains phrase' },
|
|
880
|
+
value1: 'last',
|
|
881
|
+
category: 'Shared' // Same as first rule
|
|
882
|
+
}
|
|
883
|
+
]
|
|
884
|
+
};
|
|
885
|
+
|
|
886
|
+
const originalNode: Node = {
|
|
887
|
+
uuid: 'test-node',
|
|
888
|
+
actions: [],
|
|
889
|
+
router: {
|
|
890
|
+
type: 'switch',
|
|
891
|
+
result_name: 'response',
|
|
892
|
+
categories: [],
|
|
893
|
+
cases: []
|
|
894
|
+
},
|
|
895
|
+
exits: []
|
|
896
|
+
};
|
|
897
|
+
|
|
898
|
+
const result = wait_for_response.fromFormData!(formData, originalNode);
|
|
899
|
+
|
|
900
|
+
// Should have 3 cases in the same order as the input rules
|
|
901
|
+
expect(result.router?.cases).to.have.length(3);
|
|
902
|
+
|
|
903
|
+
const cases = result.router!.cases;
|
|
904
|
+
|
|
905
|
+
// First case should be "first"
|
|
906
|
+
expect(cases[0].arguments).to.deep.equal(['first']);
|
|
907
|
+
expect(cases[0].type).to.equal('has_phrase');
|
|
908
|
+
|
|
909
|
+
// Second case should be "middle"
|
|
910
|
+
expect(cases[1].arguments).to.deep.equal(['middle']);
|
|
911
|
+
expect(cases[1].type).to.equal('has_phrase');
|
|
912
|
+
|
|
913
|
+
// Third case should be "last"
|
|
914
|
+
expect(cases[2].arguments).to.deep.equal(['last']);
|
|
915
|
+
expect(cases[2].type).to.equal('has_phrase');
|
|
916
|
+
|
|
917
|
+
// Find the shared category
|
|
918
|
+
const sharedCategory = result.router!.categories.find(
|
|
919
|
+
(cat) => cat.name === 'Shared'
|
|
920
|
+
);
|
|
921
|
+
const differentCategory = result.router!.categories.find(
|
|
922
|
+
(cat) => cat.name === 'Different'
|
|
923
|
+
);
|
|
924
|
+
|
|
925
|
+
expect(sharedCategory).to.exist;
|
|
926
|
+
expect(differentCategory).to.exist;
|
|
927
|
+
|
|
928
|
+
// First and third cases should reference the same "Shared" category
|
|
929
|
+
expect(cases[0].category_uuid).to.equal(sharedCategory!.uuid);
|
|
930
|
+
expect(cases[2].category_uuid).to.equal(sharedCategory!.uuid);
|
|
931
|
+
|
|
932
|
+
// Second case should reference the "Different" category
|
|
933
|
+
expect(cases[1].category_uuid).to.equal(differentCategory!.uuid);
|
|
934
|
+
|
|
935
|
+
// Should have 3 categories: Shared, Different, Other
|
|
936
|
+
expect(result.router?.categories).to.have.length(3);
|
|
937
|
+
const categoryNames = result.router!.categories.map((cat) => cat.name);
|
|
938
|
+
expect(categoryNames).to.deep.equal(['Shared', 'Different', 'Other']);
|
|
939
|
+
});
|
|
758
940
|
});
|
|
759
941
|
});
|
|
@@ -150,5 +150,74 @@ describe('Field Configuration System', () => {
|
|
|
150
150
|
// Expects 3 items: 2 initial items + 1 auto-generated empty item
|
|
151
151
|
expect(items?.length).to.equal(3);
|
|
152
152
|
});
|
|
153
|
+
|
|
154
|
+
it('should render with sortable list when sortable=true', async () => {
|
|
155
|
+
const itemConfig = {
|
|
156
|
+
operator: { type: 'text', label: 'Operator' },
|
|
157
|
+
value: { type: 'text', label: 'Value' }
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const initialValue = [
|
|
161
|
+
{ operator: 'equals', value: 'test' },
|
|
162
|
+
{ operator: 'contains', value: 'example' }
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
const el = await fixture(html`
|
|
166
|
+
<temba-array-editor
|
|
167
|
+
.value=${initialValue}
|
|
168
|
+
.itemConfig=${itemConfig}
|
|
169
|
+
.sortable=${true}
|
|
170
|
+
itemLabel="Rule"
|
|
171
|
+
></temba-array-editor>
|
|
172
|
+
`);
|
|
173
|
+
|
|
174
|
+
await (el as any).updateComplete;
|
|
175
|
+
|
|
176
|
+
expect(el).to.exist;
|
|
177
|
+
|
|
178
|
+
// Should have a sortable list component
|
|
179
|
+
const sortableList = el.shadowRoot?.querySelector('temba-sortable-list');
|
|
180
|
+
expect(sortableList).to.exist;
|
|
181
|
+
|
|
182
|
+
// Should have sortable items with proper classes and IDs
|
|
183
|
+
const sortableItems = el.shadowRoot?.querySelectorAll('.sortable');
|
|
184
|
+
expect(sortableItems?.length).to.equal(2); // Only non-empty items should be sortable
|
|
185
|
+
|
|
186
|
+
// Each sortable item should have a unique ID
|
|
187
|
+
const firstItem = sortableItems?.[0] as HTMLElement;
|
|
188
|
+
const secondItem = sortableItems?.[1] as HTMLElement;
|
|
189
|
+
expect(firstItem?.id).to.equal('array-item-0');
|
|
190
|
+
expect(secondItem?.id).to.equal('array-item-1');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should not render sortable list when sortable=false', async () => {
|
|
194
|
+
const itemConfig = {
|
|
195
|
+
operator: { type: 'text', label: 'Operator' },
|
|
196
|
+
value: { type: 'text', label: 'Value' }
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const initialValue = [{ operator: 'equals', value: 'test' }];
|
|
200
|
+
|
|
201
|
+
const el = await fixture(html`
|
|
202
|
+
<temba-array-editor
|
|
203
|
+
.value=${initialValue}
|
|
204
|
+
.itemConfig=${itemConfig}
|
|
205
|
+
.sortable=${false}
|
|
206
|
+
itemLabel="Rule"
|
|
207
|
+
></temba-array-editor>
|
|
208
|
+
`);
|
|
209
|
+
|
|
210
|
+
await (el as any).updateComplete;
|
|
211
|
+
|
|
212
|
+
expect(el).to.exist;
|
|
213
|
+
|
|
214
|
+
// Should not have a sortable list component
|
|
215
|
+
const sortableList = el.shadowRoot?.querySelector('temba-sortable-list');
|
|
216
|
+
expect(sortableList).to.not.exist;
|
|
217
|
+
|
|
218
|
+
// Should have regular list container instead
|
|
219
|
+
const listContainer = el.shadowRoot?.querySelector('.list-items');
|
|
220
|
+
expect(listContainer).to.exist;
|
|
221
|
+
});
|
|
153
222
|
});
|
|
154
223
|
});
|