@nyaruka/temba-components 0.139.0 → 0.140.0
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/.github/workflows/cla.yml +1 -1
- package/.github/workflows/copilot-setup-steps.yml +6 -1
- package/CHANGELOG.md +17 -0
- package/demo/data/flows/sample-flow.json +24 -0
- package/dist/temba-components.js +562 -296
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/display/Chat.js +10 -7
- package/out-tsc/src/display/Chat.js.map +1 -1
- package/out-tsc/src/display/Dropdown.js +3 -1
- package/out-tsc/src/display/Dropdown.js.map +1 -1
- package/out-tsc/src/display/FloatingTab.js +3 -3
- package/out-tsc/src/display/FloatingTab.js.map +1 -1
- package/out-tsc/src/display/Thumbnail.js +163 -5
- package/out-tsc/src/display/Thumbnail.js.map +1 -1
- package/out-tsc/src/flow/CanvasNode.js +64 -22
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +142 -8
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/NodeEditor.js +118 -10
- package/out-tsc/src/flow/NodeEditor.js.map +1 -1
- package/out-tsc/src/flow/StickyNote.js +13 -4
- package/out-tsc/src/flow/StickyNote.js.map +1 -1
- package/out-tsc/src/flow/actions/audio-player.js +112 -0
- package/out-tsc/src/flow/actions/audio-player.js.map +1 -0
- package/out-tsc/src/flow/actions/enter_flow.js +43 -0
- package/out-tsc/src/flow/actions/enter_flow.js.map +1 -0
- package/out-tsc/src/flow/actions/play_audio.js +57 -4
- package/out-tsc/src/flow/actions/play_audio.js.map +1 -1
- package/out-tsc/src/flow/actions/say_msg.js +86 -3
- package/out-tsc/src/flow/actions/say_msg.js.map +1 -1
- package/out-tsc/src/flow/config.js +11 -3
- package/out-tsc/src/flow/config.js.map +1 -1
- package/out-tsc/src/flow/nodes/shared-rules.js +1 -1
- package/out-tsc/src/flow/nodes/shared-rules.js.map +1 -1
- package/out-tsc/src/flow/nodes/terminal.js +7 -0
- package/out-tsc/src/flow/nodes/terminal.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_audio.js +77 -0
- package/out-tsc/src/flow/nodes/wait_for_audio.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_dial.js +151 -0
- package/out-tsc/src/flow/nodes/wait_for_dial.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_digits.js +61 -1
- package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_menu.js +173 -2
- package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -1
- package/out-tsc/src/flow/operators.js +21 -5
- package/out-tsc/src/flow/operators.js.map +1 -1
- package/out-tsc/src/flow/types.js.map +1 -1
- package/out-tsc/src/flow/utils.js +79 -3
- package/out-tsc/src/flow/utils.js.map +1 -1
- package/out-tsc/src/form/ArrayEditor.js +4 -2
- package/out-tsc/src/form/ArrayEditor.js.map +1 -1
- package/out-tsc/src/form/FieldRenderer.js +49 -0
- package/out-tsc/src/form/FieldRenderer.js.map +1 -1
- package/out-tsc/src/interfaces.js +1 -0
- package/out-tsc/src/interfaces.js.map +1 -1
- package/out-tsc/src/layout/Dialog.js +52 -7
- package/out-tsc/src/layout/Dialog.js.map +1 -1
- package/out-tsc/src/live/TembaChart.js.map +1 -1
- package/out-tsc/src/simulator/Simulator.js +10 -4
- package/out-tsc/src/simulator/Simulator.js.map +1 -1
- package/out-tsc/src/store/AppState.js +89 -3
- package/out-tsc/src/store/AppState.js.map +1 -1
- package/out-tsc/test/actions/play_audio.test.js +118 -0
- package/out-tsc/test/actions/play_audio.test.js.map +1 -0
- package/out-tsc/test/actions/say_msg.test.js +158 -0
- package/out-tsc/test/actions/say_msg.test.js.map +1 -0
- package/out-tsc/test/nodes/wait_for_audio.test.js +156 -0
- package/out-tsc/test/nodes/wait_for_audio.test.js.map +1 -0
- package/out-tsc/test/nodes/wait_for_dial.test.js +336 -0
- package/out-tsc/test/nodes/wait_for_dial.test.js.map +1 -0
- package/out-tsc/test/nodes/wait_for_digits.test.js +198 -84
- package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -1
- package/out-tsc/test/nodes/wait_for_menu.test.js +340 -0
- package/out-tsc/test/nodes/wait_for_menu.test.js.map +1 -0
- package/out-tsc/test/temba-flow-collision.test.js +261 -6
- package/out-tsc/test/temba-flow-collision.test.js.map +1 -1
- package/out-tsc/test/temba-node-type-selector.test.js +6 -6
- package/out-tsc/test/temba-node-type-selector.test.js.map +1 -1
- package/package.json +1 -1
- package/screenshots/truth/actions/play_audio/editor/expression-url.png +0 -0
- package/screenshots/truth/actions/play_audio/editor/static-url.png +0 -0
- package/screenshots/truth/actions/play_audio/render/expression-url.png +0 -0
- package/screenshots/truth/actions/play_audio/render/static-url.png +0 -0
- package/screenshots/truth/actions/say_msg/editor/multiline-text.png +0 -0
- package/screenshots/truth/actions/say_msg/editor/simple-text.png +0 -0
- package/screenshots/truth/actions/say_msg/editor/text-with-audio-url.png +0 -0
- package/screenshots/truth/actions/say_msg/render/multiline-text.png +0 -0
- package/screenshots/truth/actions/say_msg/render/simple-text.png +0 -0
- package/screenshots/truth/actions/say_msg/render/text-with-audio-url.png +0 -0
- package/screenshots/truth/editor/router.png +0 -0
- package/screenshots/truth/editor/wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_audio/editor/basic-audio-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_audio/render/basic-audio-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_dial/editor/basic-dial.png +0 -0
- package/screenshots/truth/nodes/wait_for_dial/editor/dial-with-limits.png +0 -0
- package/screenshots/truth/nodes/wait_for_dial/render/basic-dial.png +0 -0
- package/screenshots/truth/nodes/wait_for_dial/render/dial-with-limits.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/digits-with-rules.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/digits-with-rules.png +0 -0
- package/screenshots/truth/nodes/wait_for_menu/editor/menu-with-digits.png +0 -0
- package/screenshots/truth/nodes/wait_for_menu/render/menu-with-digits.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/nodes/wait_for_response/render/basic-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
- package/src/display/Chat.ts +13 -7
- package/src/display/Dropdown.ts +3 -1
- package/src/display/FloatingTab.ts +3 -3
- package/src/display/Thumbnail.ts +162 -2
- package/src/flow/CanvasNode.ts +69 -23
- package/src/flow/Editor.ts +156 -13
- package/src/flow/NodeEditor.ts +137 -9
- package/src/flow/StickyNote.ts +14 -4
- package/src/flow/actions/audio-player.ts +127 -0
- package/src/flow/actions/enter_flow.ts +44 -0
- package/src/flow/actions/play_audio.ts +64 -5
- package/src/flow/actions/say_msg.ts +94 -4
- package/src/flow/config.ts +11 -3
- package/src/flow/nodes/shared-rules.ts +1 -1
- package/src/flow/nodes/terminal.ts +9 -0
- package/src/flow/nodes/wait_for_audio.ts +88 -0
- package/src/flow/nodes/wait_for_dial.ts +176 -0
- package/src/flow/nodes/wait_for_digits.ts +86 -2
- package/src/flow/nodes/wait_for_menu.ts +209 -3
- package/src/flow/operators.ts +23 -5
- package/src/flow/types.ts +23 -1
- package/src/flow/utils.ts +82 -3
- package/src/form/ArrayEditor.ts +4 -2
- package/src/form/FieldRenderer.ts +64 -1
- package/src/interfaces.ts +2 -1
- package/src/layout/Dialog.ts +53 -7
- package/src/live/TembaChart.ts +1 -1
- package/src/simulator/Simulator.ts +13 -4
- package/src/store/AppState.ts +105 -1
- package/src/store/flow-definition.d.ts +2 -0
- package/test/actions/play_audio.test.ts +155 -0
- package/test/actions/say_msg.test.ts +196 -0
- package/test/nodes/wait_for_audio.test.ts +182 -0
- package/test/nodes/wait_for_dial.test.ts +382 -0
- package/test/nodes/wait_for_digits.test.ts +233 -109
- package/test/nodes/wait_for_menu.test.ts +383 -0
- package/test/temba-flow-collision.test.ts +286 -6
- package/test/temba-node-type-selector.test.ts +6 -6
- package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { expect } from '@open-wc/testing';
|
|
2
|
+
import { play_audio } from '../../src/flow/actions/play_audio';
|
|
3
|
+
import { PlayAudio } from '../../src/store/flow-definition';
|
|
4
|
+
import { ActionTest } from '../ActionHelper';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Test suite for the play_audio action configuration.
|
|
8
|
+
*/
|
|
9
|
+
describe('play_audio action config', () => {
|
|
10
|
+
const helper = new ActionTest(play_audio, 'play_audio');
|
|
11
|
+
|
|
12
|
+
describe('basic properties', () => {
|
|
13
|
+
helper.testBasicProperties();
|
|
14
|
+
|
|
15
|
+
it('has correct name', () => {
|
|
16
|
+
expect(play_audio.name).to.equal('Play Recording');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('is voice-only', () => {
|
|
20
|
+
expect(play_audio.flowTypes).to.deep.equal(['voice']);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('action scenarios', () => {
|
|
25
|
+
helper.testAction(
|
|
26
|
+
{
|
|
27
|
+
uuid: 'test-play-1',
|
|
28
|
+
type: 'play_audio',
|
|
29
|
+
audio_url: '@results.voicemail'
|
|
30
|
+
} as PlayAudio,
|
|
31
|
+
'expression-url'
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
helper.testAction(
|
|
35
|
+
{
|
|
36
|
+
uuid: 'test-play-2',
|
|
37
|
+
type: 'play_audio',
|
|
38
|
+
audio_url: 'https://example.com/greeting.mp3'
|
|
39
|
+
} as PlayAudio,
|
|
40
|
+
'static-url'
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('data transformation', () => {
|
|
45
|
+
it('converts action to form data', () => {
|
|
46
|
+
const action: PlayAudio = {
|
|
47
|
+
uuid: 'test-action',
|
|
48
|
+
type: 'play_audio',
|
|
49
|
+
audio_url: '@results.voicemail'
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const formData = play_audio.toFormData!(action);
|
|
53
|
+
expect(formData.uuid).to.equal('test-action');
|
|
54
|
+
expect(formData.audio_url).to.equal('@results.voicemail');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('handles missing audio_url', () => {
|
|
58
|
+
const action = {
|
|
59
|
+
uuid: 'test-action',
|
|
60
|
+
type: 'play_audio'
|
|
61
|
+
} as PlayAudio;
|
|
62
|
+
|
|
63
|
+
const formData = play_audio.toFormData!(action);
|
|
64
|
+
expect(formData.audio_url).to.equal('');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('converts form data to action', () => {
|
|
68
|
+
const formData = {
|
|
69
|
+
uuid: 'test-action',
|
|
70
|
+
audio_url: '@results.voicemail'
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const action = play_audio.fromFormData!(formData) as PlayAudio;
|
|
74
|
+
expect(action.uuid).to.equal('test-action');
|
|
75
|
+
expect(action.type).to.equal('play_audio');
|
|
76
|
+
expect(action.audio_url).to.equal('@results.voicemail');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('trims whitespace from audio_url', () => {
|
|
80
|
+
const formData = {
|
|
81
|
+
uuid: 'test-action',
|
|
82
|
+
audio_url: ' @results.voicemail '
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const action = play_audio.fromFormData!(formData) as PlayAudio;
|
|
86
|
+
expect(action.audio_url).to.equal('@results.voicemail');
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('localization', () => {
|
|
91
|
+
it('converts localization to form data', () => {
|
|
92
|
+
const action: PlayAudio = {
|
|
93
|
+
uuid: 'test-action',
|
|
94
|
+
type: 'play_audio',
|
|
95
|
+
audio_url: '@results.voicemail'
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const localization = {
|
|
99
|
+
audio_url: ['@results.voicemail_es']
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const formData = play_audio.toLocalizationFormData!(action, localization);
|
|
103
|
+
expect(formData.audio_url).to.equal('@results.voicemail_es');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('handles missing localization', () => {
|
|
107
|
+
const action: PlayAudio = {
|
|
108
|
+
uuid: 'test-action',
|
|
109
|
+
type: 'play_audio',
|
|
110
|
+
audio_url: '@results.voicemail'
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const formData = play_audio.toLocalizationFormData!(action, {});
|
|
114
|
+
expect(formData.audio_url).to.equal('');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('converts form data to localization', () => {
|
|
118
|
+
const action: PlayAudio = {
|
|
119
|
+
uuid: 'test-action',
|
|
120
|
+
type: 'play_audio',
|
|
121
|
+
audio_url: '@results.voicemail'
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const formData = {
|
|
125
|
+
uuid: 'test-action',
|
|
126
|
+
audio_url: '@results.voicemail_es'
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const localization = play_audio.fromLocalizationFormData!(
|
|
130
|
+
formData,
|
|
131
|
+
action
|
|
132
|
+
);
|
|
133
|
+
expect(localization.audio_url).to.deep.equal(['@results.voicemail_es']);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('omits unchanged localization', () => {
|
|
137
|
+
const action: PlayAudio = {
|
|
138
|
+
uuid: 'test-action',
|
|
139
|
+
type: 'play_audio',
|
|
140
|
+
audio_url: '@results.voicemail'
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const formData = {
|
|
144
|
+
uuid: 'test-action',
|
|
145
|
+
audio_url: '@results.voicemail' // same as original
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const localization = play_audio.fromLocalizationFormData!(
|
|
149
|
+
formData,
|
|
150
|
+
action
|
|
151
|
+
);
|
|
152
|
+
expect(localization.audio_url).to.be.undefined;
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
});
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { expect } from '@open-wc/testing';
|
|
2
|
+
import { say_msg } from '../../src/flow/actions/say_msg';
|
|
3
|
+
import { SayMsg } from '../../src/store/flow-definition';
|
|
4
|
+
import { ActionTest } from '../ActionHelper';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Test suite for the say_msg action configuration.
|
|
8
|
+
*/
|
|
9
|
+
describe('say_msg action config', () => {
|
|
10
|
+
const helper = new ActionTest(say_msg, 'say_msg');
|
|
11
|
+
|
|
12
|
+
describe('basic properties', () => {
|
|
13
|
+
helper.testBasicProperties();
|
|
14
|
+
|
|
15
|
+
it('has correct name', () => {
|
|
16
|
+
expect(say_msg.name).to.equal('Say Message');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('is voice-only', () => {
|
|
20
|
+
expect(say_msg.flowTypes).to.deep.equal(['voice']);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('action scenarios', () => {
|
|
25
|
+
helper.testAction(
|
|
26
|
+
{
|
|
27
|
+
uuid: 'test-say-1',
|
|
28
|
+
type: 'say_msg',
|
|
29
|
+
text: 'Hello, welcome to our service.'
|
|
30
|
+
} as SayMsg,
|
|
31
|
+
'simple-text'
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
helper.testAction(
|
|
35
|
+
{
|
|
36
|
+
uuid: 'test-say-2',
|
|
37
|
+
type: 'say_msg',
|
|
38
|
+
text: 'Press 1 for sales.\nPress 2 for support.\nPress 3 to leave a message.'
|
|
39
|
+
} as SayMsg,
|
|
40
|
+
'multiline-text'
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
helper.testAction(
|
|
44
|
+
{
|
|
45
|
+
uuid: 'test-say-3',
|
|
46
|
+
type: 'say_msg',
|
|
47
|
+
text: 'Please listen to the following recording.',
|
|
48
|
+
audio_url: 'https://example.com/greeting.mp3'
|
|
49
|
+
} as SayMsg,
|
|
50
|
+
'text-with-audio-url'
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('data transformation', () => {
|
|
55
|
+
it('converts action to form data', () => {
|
|
56
|
+
const action: SayMsg = {
|
|
57
|
+
uuid: 'test-action',
|
|
58
|
+
type: 'say_msg',
|
|
59
|
+
text: 'Hello world',
|
|
60
|
+
audio_url: 'https://example.com/audio.mp3'
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const formData = say_msg.toFormData!(action);
|
|
64
|
+
expect(formData.uuid).to.equal('test-action');
|
|
65
|
+
expect(formData.text).to.equal('Hello world');
|
|
66
|
+
expect(formData.audio_url).to.equal('https://example.com/audio.mp3');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('handles missing audio_url in toFormData', () => {
|
|
70
|
+
const action: SayMsg = {
|
|
71
|
+
uuid: 'test-action',
|
|
72
|
+
type: 'say_msg',
|
|
73
|
+
text: 'Hello'
|
|
74
|
+
} as SayMsg;
|
|
75
|
+
|
|
76
|
+
const formData = say_msg.toFormData!(action);
|
|
77
|
+
expect(formData.audio_url).to.equal('');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('converts form data to action', () => {
|
|
81
|
+
const formData = {
|
|
82
|
+
uuid: 'test-action',
|
|
83
|
+
text: 'Hello world',
|
|
84
|
+
audio_url: 'https://example.com/audio.mp3'
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const action = say_msg.fromFormData!(formData) as SayMsg;
|
|
88
|
+
expect(action.uuid).to.equal('test-action');
|
|
89
|
+
expect(action.type).to.equal('say_msg');
|
|
90
|
+
expect(action.text).to.equal('Hello world');
|
|
91
|
+
expect(action.audio_url).to.equal('https://example.com/audio.mp3');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('omits empty audio_url in fromFormData', () => {
|
|
95
|
+
const formData = {
|
|
96
|
+
uuid: 'test-action',
|
|
97
|
+
text: 'Hello world',
|
|
98
|
+
audio_url: ''
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const action = say_msg.fromFormData!(formData) as SayMsg;
|
|
102
|
+
expect(action.audio_url).to.be.undefined;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('trims whitespace from audio_url', () => {
|
|
106
|
+
const formData = {
|
|
107
|
+
uuid: 'test-action',
|
|
108
|
+
text: 'Hello',
|
|
109
|
+
audio_url: ' https://example.com/audio.mp3 '
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const action = say_msg.fromFormData!(formData) as SayMsg;
|
|
113
|
+
expect(action.audio_url).to.equal('https://example.com/audio.mp3');
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('sanitize', () => {
|
|
118
|
+
it('trims text whitespace', () => {
|
|
119
|
+
const formData = { text: ' Hello world ' };
|
|
120
|
+
say_msg.sanitize!(formData);
|
|
121
|
+
expect(formData.text).to.equal('Hello world');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('localization', () => {
|
|
126
|
+
it('converts localization to form data', () => {
|
|
127
|
+
const action: SayMsg = {
|
|
128
|
+
uuid: 'test-action',
|
|
129
|
+
type: 'say_msg',
|
|
130
|
+
text: 'Hello',
|
|
131
|
+
audio_url: 'https://example.com/en.mp3'
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const localization = {
|
|
135
|
+
text: ['Hola'],
|
|
136
|
+
audio_url: ['https://example.com/es.mp3']
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const formData = say_msg.toLocalizationFormData!(action, localization);
|
|
140
|
+
expect(formData.text).to.equal('Hola');
|
|
141
|
+
expect(formData.audio_url).to.equal('https://example.com/es.mp3');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('handles missing localization fields', () => {
|
|
145
|
+
const action: SayMsg = {
|
|
146
|
+
uuid: 'test-action',
|
|
147
|
+
type: 'say_msg',
|
|
148
|
+
text: 'Hello'
|
|
149
|
+
} as SayMsg;
|
|
150
|
+
|
|
151
|
+
const formData = say_msg.toLocalizationFormData!(action, {});
|
|
152
|
+
expect(formData.text).to.equal('');
|
|
153
|
+
expect(formData.audio_url).to.equal('');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('converts form data to localization', () => {
|
|
157
|
+
const action: SayMsg = {
|
|
158
|
+
uuid: 'test-action',
|
|
159
|
+
type: 'say_msg',
|
|
160
|
+
text: 'Hello',
|
|
161
|
+
audio_url: 'https://example.com/en.mp3'
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const formData = {
|
|
165
|
+
uuid: 'test-action',
|
|
166
|
+
text: 'Hola',
|
|
167
|
+
audio_url: 'https://example.com/es.mp3'
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const localization = say_msg.fromLocalizationFormData!(formData, action);
|
|
171
|
+
expect(localization.text).to.deep.equal(['Hola']);
|
|
172
|
+
expect(localization.audio_url).to.deep.equal([
|
|
173
|
+
'https://example.com/es.mp3'
|
|
174
|
+
]);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('omits unchanged localization fields', () => {
|
|
178
|
+
const action: SayMsg = {
|
|
179
|
+
uuid: 'test-action',
|
|
180
|
+
type: 'say_msg',
|
|
181
|
+
text: 'Hello',
|
|
182
|
+
audio_url: 'https://example.com/en.mp3'
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const formData = {
|
|
186
|
+
uuid: 'test-action',
|
|
187
|
+
text: 'Hello', // same as original
|
|
188
|
+
audio_url: 'https://example.com/en.mp3' // same as original
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const localization = say_msg.fromLocalizationFormData!(formData, action);
|
|
192
|
+
expect(localization.text).to.be.undefined;
|
|
193
|
+
expect(localization.audio_url).to.be.undefined;
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
});
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { expect } from '@open-wc/testing';
|
|
2
|
+
import { wait_for_audio } from '../../src/flow/nodes/wait_for_audio';
|
|
3
|
+
import { Node } from '../../src/store/flow-definition';
|
|
4
|
+
import { NodeTest } from '../NodeHelper';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Test suite for the wait_for_audio node configuration.
|
|
8
|
+
*/
|
|
9
|
+
describe('wait_for_audio node config', () => {
|
|
10
|
+
const helper = new NodeTest(wait_for_audio, 'wait_for_audio');
|
|
11
|
+
|
|
12
|
+
describe('basic properties', () => {
|
|
13
|
+
helper.testBasicProperties();
|
|
14
|
+
|
|
15
|
+
it('has correct name', () => {
|
|
16
|
+
expect(wait_for_audio.name).to.equal('Make Recording');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('has correct type', () => {
|
|
20
|
+
expect(wait_for_audio.type).to.equal('wait_for_audio');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('is voice-only', () => {
|
|
24
|
+
expect(wait_for_audio.flowTypes).to.deep.equal(['voice']);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('has form with result_name field', () => {
|
|
28
|
+
expect(wait_for_audio.form).to.exist;
|
|
29
|
+
expect(wait_for_audio.form!.result_name).to.exist;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('has layout', () => {
|
|
33
|
+
expect(wait_for_audio.layout).to.deep.equal(['result_name']);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('node scenarios', () => {
|
|
38
|
+
it('renders basic audio wait', async () => {
|
|
39
|
+
await helper.testNode(
|
|
40
|
+
{
|
|
41
|
+
uuid: 'test-audio-node-1',
|
|
42
|
+
actions: [],
|
|
43
|
+
router: {
|
|
44
|
+
type: 'switch',
|
|
45
|
+
operand: '@input',
|
|
46
|
+
wait: {
|
|
47
|
+
type: 'msg',
|
|
48
|
+
hint: { type: 'audio' }
|
|
49
|
+
},
|
|
50
|
+
result_name: 'recording',
|
|
51
|
+
default_category_uuid: 'all-cat-1',
|
|
52
|
+
cases: [],
|
|
53
|
+
categories: [
|
|
54
|
+
{
|
|
55
|
+
uuid: 'all-cat-1',
|
|
56
|
+
name: 'All Responses',
|
|
57
|
+
exit_uuid: 'all-exit-1'
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
exits: [{ uuid: 'all-exit-1', destination_uuid: null }]
|
|
62
|
+
} as Node,
|
|
63
|
+
{ type: 'wait_for_audio' },
|
|
64
|
+
'basic-audio-wait'
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('data transformation', () => {
|
|
70
|
+
it('converts node to form data', () => {
|
|
71
|
+
const node: Node = {
|
|
72
|
+
uuid: 'test-node',
|
|
73
|
+
actions: [],
|
|
74
|
+
router: {
|
|
75
|
+
type: 'switch',
|
|
76
|
+
result_name: 'my_recording',
|
|
77
|
+
categories: []
|
|
78
|
+
},
|
|
79
|
+
exits: []
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const formData = wait_for_audio.toFormData!(node);
|
|
83
|
+
expect(formData.uuid).to.equal('test-node');
|
|
84
|
+
expect(formData.result_name).to.equal('my_recording');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('handles missing result_name', () => {
|
|
88
|
+
const node: Node = {
|
|
89
|
+
uuid: 'test-node',
|
|
90
|
+
actions: [],
|
|
91
|
+
router: {
|
|
92
|
+
type: 'switch',
|
|
93
|
+
categories: []
|
|
94
|
+
},
|
|
95
|
+
exits: []
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const formData = wait_for_audio.toFormData!(node);
|
|
99
|
+
expect(formData.result_name).to.equal('');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('creates node with All Responses category', () => {
|
|
103
|
+
const formData = {
|
|
104
|
+
uuid: 'test-node',
|
|
105
|
+
result_name: 'recording'
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const originalNode: Node = {
|
|
109
|
+
uuid: 'test-node',
|
|
110
|
+
actions: [],
|
|
111
|
+
exits: [],
|
|
112
|
+
router: {
|
|
113
|
+
type: 'switch',
|
|
114
|
+
categories: []
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const result = wait_for_audio.fromFormData!(formData, originalNode);
|
|
119
|
+
|
|
120
|
+
expect(result.router?.categories).to.have.length(1);
|
|
121
|
+
expect(result.router?.categories[0].name).to.equal('All Responses');
|
|
122
|
+
expect(result.router?.wait?.type).to.equal('msg');
|
|
123
|
+
expect(result.router?.wait?.hint?.type).to.equal('audio');
|
|
124
|
+
expect(result.router?.result_name).to.equal('recording');
|
|
125
|
+
expect(result.exits).to.have.length(1);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('preserves existing All Responses category UUID', () => {
|
|
129
|
+
const formData = {
|
|
130
|
+
uuid: 'test-node',
|
|
131
|
+
result_name: 'recording'
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const originalNode: Node = {
|
|
135
|
+
uuid: 'test-node',
|
|
136
|
+
actions: [],
|
|
137
|
+
router: {
|
|
138
|
+
type: 'switch',
|
|
139
|
+
categories: [
|
|
140
|
+
{
|
|
141
|
+
uuid: 'existing-cat-uuid',
|
|
142
|
+
name: 'All Responses',
|
|
143
|
+
exit_uuid: 'existing-exit-uuid'
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
},
|
|
147
|
+
exits: [
|
|
148
|
+
{
|
|
149
|
+
uuid: 'existing-exit-uuid',
|
|
150
|
+
destination_uuid: 'some-destination'
|
|
151
|
+
}
|
|
152
|
+
]
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const result = wait_for_audio.fromFormData!(formData, originalNode);
|
|
156
|
+
|
|
157
|
+
expect(result.router?.categories[0].uuid).to.equal('existing-cat-uuid');
|
|
158
|
+
expect(result.router?.categories[0].exit_uuid).to.equal(
|
|
159
|
+
'existing-exit-uuid'
|
|
160
|
+
);
|
|
161
|
+
expect(result.exits[0].uuid).to.equal('existing-exit-uuid');
|
|
162
|
+
expect(result.exits[0].destination_uuid).to.equal('some-destination');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('omits result_name when empty', () => {
|
|
166
|
+
const formData = {
|
|
167
|
+
uuid: 'test-node',
|
|
168
|
+
result_name: ''
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const originalNode: Node = {
|
|
172
|
+
uuid: 'test-node',
|
|
173
|
+
actions: [],
|
|
174
|
+
exits: [],
|
|
175
|
+
router: { type: 'switch', categories: [] }
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const result = wait_for_audio.fromFormData!(formData, originalNode);
|
|
179
|
+
expect(result.router?.result_name).to.be.undefined;
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
});
|