@chat21/chat21-web-widget 5.1.34-rc1 → 5.1.34
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/docker-community-push-latest.yml +13 -23
- package/.github/workflows/docker-image-tag-community-tag-push.yml +12 -22
- package/CHANGELOG.md +8 -129
- package/Dockerfile +5 -4
- package/angular.json +3 -21
- package/docs/changelog/this-branch.md +0 -36
- package/env.sample +2 -3
- package/nginx.conf +2 -22
- package/package.json +3 -10
- package/src/app/app.component.html +2 -2
- package/src/app/app.component.scss +14 -25
- package/src/app/app.component.spec.ts +6 -21
- package/src/app/app.component.ts +9 -10
- package/src/app/app.module.ts +0 -13
- package/src/app/component/conversation-detail/conversation/conversation.component.html +11 -25
- package/src/app/component/conversation-detail/conversation/conversation.component.scss +2 -40
- package/src/app/component/conversation-detail/conversation/conversation.component.spec.ts +75 -644
- package/src/app/component/conversation-detail/conversation/conversation.component.ts +14 -100
- package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.html +13 -25
- package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.spec.ts +5 -123
- package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.ts +0 -1
- package/src/app/component/conversation-detail/conversation-content/conversation-content.component.html +10 -23
- package/src/app/component/conversation-detail/conversation-content/conversation-content.component.scss +1 -19
- package/src/app/component/conversation-detail/conversation-content/conversation-content.component.spec.ts +149 -242
- package/src/app/component/conversation-detail/conversation-content/conversation-content.component.ts +5 -8
- package/src/app/component/conversation-detail/conversation-emojii/conversation-emojii.component.spec.ts +3 -53
- package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.html +96 -200
- package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.scss +6 -211
- package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.spec.ts +78 -452
- package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts +76 -291
- package/src/app/component/conversation-detail/conversation-header/conversation-header.component.html +53 -113
- package/src/app/component/conversation-detail/conversation-header/conversation-header.component.scss +4 -12
- package/src/app/component/conversation-detail/conversation-header/conversation-header.component.spec.ts +29 -274
- package/src/app/component/conversation-detail/conversation-internal-frame/conversation-internal-frame.component.html +9 -23
- package/src/app/component/conversation-detail/conversation-internal-frame/conversation-internal-frame.component.spec.ts +8 -80
- package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.html +23 -29
- package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.spec.ts +16 -185
- package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.ts +14 -34
- package/src/app/component/error-alert/error-alert.component.spec.ts +5 -65
- package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.html +7 -16
- package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.scss +0 -21
- package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.spec.ts +7 -89
- package/src/app/component/form/form-builder/form-builder.component.html +1 -1
- package/src/app/component/form/form-builder/form-builder.component.spec.ts +21 -163
- package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.html +4 -8
- package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.scss +5 -10
- package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.spec.ts +16 -90
- package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.ts +0 -26
- package/src/app/component/form/inputs/form-label/form-label.component.spec.ts +11 -45
- package/src/app/component/form/inputs/form-radio-button/form-radio-button.component.spec.ts +6 -24
- package/src/app/component/form/inputs/form-select/form-select.component.spec.ts +5 -14
- package/src/app/component/form/inputs/form-text/form-text.component.html +12 -14
- package/src/app/component/form/inputs/form-text/form-text.component.scss +1 -11
- package/src/app/component/form/inputs/form-text/form-text.component.spec.ts +17 -113
- package/src/app/component/form/inputs/form-text/form-text.component.ts +3 -35
- package/src/app/component/form/inputs/form-textarea/form-textarea.component.html +11 -13
- package/src/app/component/form/inputs/form-textarea/form-textarea.component.scss +5 -6
- package/src/app/component/form/inputs/form-textarea/form-textarea.component.spec.ts +13 -149
- package/src/app/component/form/inputs/form-textarea/form-textarea.component.ts +0 -26
- package/src/app/component/form/prechat-form/prechat-form.component.html +11 -14
- package/src/app/component/form/prechat-form/prechat-form.component.spec.ts +10 -102
- package/src/app/component/form/prechat-form/prechat-form.component.ts +1 -8
- package/src/app/component/home/home.component.html +31 -38
- package/src/app/component/home/home.component.scss +2 -4
- package/src/app/component/home/home.component.spec.ts +11 -226
- package/src/app/component/home-conversations/home-conversations.component.html +26 -30
- package/src/app/component/home-conversations/home-conversations.component.scss +0 -3
- package/src/app/component/home-conversations/home-conversations.component.spec.ts +36 -212
- package/src/app/component/last-message/last-message.component.html +9 -15
- package/src/app/component/last-message/last-message.component.scss +2 -16
- package/src/app/component/last-message/last-message.component.spec.ts +23 -204
- package/src/app/component/last-message/last-message.component.ts +1 -4
- package/src/app/component/launcher-button/launcher-button.component.html +13 -8
- package/src/app/component/launcher-button/launcher-button.component.spec.ts +8 -104
- package/src/app/component/list-all-conversations/list-all-conversations.component.html +17 -12
- package/src/app/component/list-all-conversations/list-all-conversations.component.scss +0 -2
- package/src/app/component/list-conversations/list-conversations.component.html +22 -22
- package/src/app/component/menu-options/menu-options.component.html +20 -30
- package/src/app/component/menu-options/menu-options.component.spec.ts +9 -125
- package/src/app/component/message/audio/audio.component.html +15 -13
- package/src/app/component/message/audio/audio.component.spec.ts +5 -140
- package/src/app/component/message/audio/audio.component.ts +5 -1
- package/src/app/component/message/avatar/avatar.component.html +2 -2
- package/src/app/component/message/avatar/avatar.component.spec.ts +7 -99
- package/src/app/component/message/bubble-message/bubble-message.component.html +51 -38
- package/src/app/component/message/bubble-message/bubble-message.component.scss +1 -54
- package/src/app/component/message/bubble-message/bubble-message.component.spec.ts +57 -154
- package/src/app/component/message/bubble-message/bubble-message.component.ts +11 -89
- package/src/app/component/message/buttons/action-button/action-button.component.html +4 -3
- package/src/app/component/message/buttons/action-button/action-button.component.spec.ts +5 -49
- package/src/app/component/message/buttons/link-button/link-button.component.scss +8 -5
- package/src/app/component/message/buttons/link-button/link-button.component.spec.ts +5 -50
- package/src/app/component/message/buttons/text-button/text-button.component.spec.ts +5 -44
- package/src/app/component/message/carousel/carousel.component.html +16 -29
- package/src/app/component/message/carousel/carousel.component.scss +8 -20
- package/src/app/component/message/carousel/carousel.component.spec.ts +3 -80
- package/src/app/component/message/carousel/carousel.component.ts +0 -16
- package/src/app/component/message/frame/frame.component.html +4 -9
- package/src/app/component/message/frame/frame.component.spec.ts +15 -34
- package/src/app/component/message/frame/frame.component.ts +2 -7
- package/src/app/component/message/html/html.component.html +1 -1
- package/src/app/component/message/html/html.component.scss +1 -1
- package/src/app/component/message/html/html.component.spec.ts +7 -24
- package/src/app/component/message/image/image.component.html +10 -12
- package/src/app/component/message/image/image.component.scss +0 -16
- package/src/app/component/message/image/image.component.spec.ts +15 -101
- package/src/app/component/message/image/image.component.ts +51 -90
- package/src/app/component/message/info-message/info-message.component.spec.ts +14 -26
- package/src/app/component/message/like-unlike/like-unlike.component.html +9 -7
- package/src/app/component/message/like-unlike/like-unlike.component.spec.ts +3 -31
- package/src/app/component/message/return-receipt/return-receipt.component.spec.ts +17 -38
- package/src/app/component/message/text/text.component.html +3 -3
- package/src/app/component/message/text/text.component.scss +86 -80
- package/src/app/component/message/text/text.component.spec.ts +13 -106
- package/src/app/component/message-attachment/message-attachment.component.spec.ts +13 -134
- package/src/app/component/selection-department/selection-department.component.html +23 -21
- package/src/app/component/selection-department/selection-department.component.spec.ts +14 -159
- package/src/app/component/selection-department/selection-department.component.ts +1 -8
- package/src/app/component/send-button/send-button.component.html +13 -5
- package/src/app/component/send-button/send-button.component.spec.ts +2 -2
- package/src/app/component/star-rating-widget/star-rating-widget.component.html +81 -51
- package/src/app/directives/tooltip.directive.spec.ts +4 -8
- package/src/app/modals/confirm-close/confirm-close.component.html +8 -20
- package/src/app/modals/confirm-close/confirm-close.component.scss +0 -3
- package/src/app/modals/confirm-close/confirm-close.component.spec.ts +4 -13
- package/src/app/modals/confirm-close/confirm-close.component.ts +1 -8
- package/src/app/pipe/html-entites-encode.pipe.spec.ts +2 -35
- package/src/app/pipe/marked.pipe.spec.ts +2 -38
- package/src/app/pipe/marked.pipe.ts +41 -51
- package/src/app/providers/app-config.service.ts +2 -4
- package/src/app/providers/brand.service.spec.ts +2 -23
- package/src/app/providers/brand.service.ts +1 -1
- package/src/app/providers/global-settings.service.spec.ts +14 -1009
- package/src/app/providers/global-settings.service.ts +2 -82
- package/src/app/providers/translator.service.ts +6 -26
- package/src/app/sass/_variables.scss +0 -3
- package/src/app/sass/animations.scss +1 -19
- package/src/app/utils/globals.ts +1 -21
- package/src/app/utils/utils-resources.ts +1 -1
- package/src/assets/i18n/en.json +99 -106
- package/src/assets/i18n/es.json +100 -107
- package/src/assets/i18n/fr.json +100 -107
- package/src/assets/i18n/it.json +98 -107
- package/src/assets/twp/index-dev.html +0 -18
- package/src/chat21-core/models/message.ts +1 -2
- package/src/chat21-core/providers/firebase/firebase-conversation-handler.ts +2 -3
- package/src/chat21-core/providers/mqtt/mqtt-conversation-handler.ts +0 -12
- package/src/chat21-core/providers/scripts/script.service.spec.ts +2 -12
- package/src/chat21-core/providers/tiledesk/tiledesk-requests.service.ts +1 -1
- package/src/chat21-core/utils/utils-message.ts +0 -7
- package/src/chat21-core/utils/utils.ts +2 -5
- package/src/widget-config-template.json +1 -4
- package/src/widget-config.json +1 -4
- package/tsconfig.json +0 -5
- package/.angular-mcp-cache/package.json +0 -1
- package/.cursor/angular18-accessibility-auditor-skill.md +0 -442
- package/.cursor/mcp.json +0 -15
- package/.github/workflows/build.yml +0 -22
- package/.github/workflows/playwright.yml +0 -27
- package/mocks/voice-websocket-mock/server.cjs +0 -245
- package/playwright.config.ts +0 -41
- package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.html +0 -46
- package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.scss +0 -83
- package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.ts +0 -192
- package/src/app/component/form/prechat-form-test-mock.ts +0 -35
- package/src/app/component/message/audio-sync/audio-sync.component.html +0 -18
- package/src/app/component/message/audio-sync/audio-sync.component.scss +0 -65
- package/src/app/component/message/audio-sync/audio-sync.component.spec.ts +0 -103
- package/src/app/component/message/audio-sync/audio-sync.component.ts +0 -643
- package/src/app/providers/tts-audio-playback-coordinator.service.spec.ts +0 -117
- package/src/app/providers/tts-audio-playback-coordinator.service.ts +0 -109
- package/src/app/providers/voice/STT&TTS/openai-voice.config.ts +0 -12
- package/src/app/providers/voice/STT&TTS/openai-voice.provider.ts +0 -171
- package/src/app/providers/voice/STT&TTS/speech-provider.abstract.ts +0 -39
- package/src/app/providers/voice/audio.types.ts +0 -40
- package/src/app/providers/voice/vad.service.spec.ts +0 -28
- package/src/app/providers/voice/vad.service.ts +0 -70
- package/src/app/providers/voice/voice-streaming.service.spec.ts +0 -23
- package/src/app/providers/voice/voice-streaming.service.ts +0 -702
- package/src/app/providers/voice/voice-streaming.types.ts +0 -112
- package/src/app/providers/voice/voice.service.spec.ts +0 -227
- package/src/app/providers/voice/voice.service.ts +0 -973
- package/src/app/shims/onnxruntime-web-wasm.ts +0 -4
- package/src/assets/onnx/ort-wasm-simd-threaded.mjs +0 -59
- package/src/assets/onnx/ort-wasm-simd-threaded.wasm +0 -0
- package/src/assets/sounds/keyboard.mp3 +0 -0
- package/src/assets/twp/tiledesk_widget_files/widget-css-override-example.css +0 -14
- package/src/assets/vad/silero_vad_legacy.onnx +0 -0
- package/src/assets/vad/vad.worklet.bundle.min.js +0 -1
- package/src/chat21-core/providers/chat-manager.spec.ts +0 -72
- package/tests/widget-form-rich.spec.ts +0 -67
- package/tests/widget-index-dev-settings.spec.ts +0 -52
- package/tests/widget-twp-iframe.spec.ts +0 -39
|
@@ -1,4 +1,26 @@
|
|
|
1
|
-
|
|
1
|
+
<!-- [ngClass]="{'button-in-msg' : message.metadata && message.metadata.button}" -->
|
|
2
|
+
<!-- ngStyle]="{'padding': (isImage(message) || isFrame(message))?'1px':'0 8px'}" -->
|
|
3
|
+
<!-- 'width': (isImage(message) || isFrame(message))? sizeImage?.width + 'px': null -->
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
<!-- <div id="bubble-message" *ngIf="isAudio(message)" [ngStyle]="{'padding': '0'}" class="messages primary-color">
|
|
8
|
+
<div>
|
|
9
|
+
<chat-audio-track *ngIf="isAudio(message)"
|
|
10
|
+
[metadata]="message.metadata"
|
|
11
|
+
></chat-audio-track>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
[ngStyle]="{'padding': (isImage(message) || isFrame(message) || isAudio(message))?'0 0px':'0 8px'}"
|
|
17
|
+
-->
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
<div id="bubble-message" *ngIf="hasRenderableContent()" class="messages primary-color">
|
|
2
24
|
<div>
|
|
3
25
|
|
|
4
26
|
<div *ngIf="messageType(MESSAGE_TYPE_OTHERS, message) && !isSameSender"
|
|
@@ -6,7 +28,17 @@
|
|
|
6
28
|
[ngStyle]="{'margin': (isImage(message) || isFrame(message))? '12px 16px 8px 16px': '12px 16px 0px 16px'}" class="message_sender_fullname">
|
|
7
29
|
{{message?.sender_fullname}}
|
|
8
30
|
</div>
|
|
31
|
+
<!-- message type:: image -->
|
|
32
|
+
<!-- <div *ngIf="message.type == 'image' && message.metadata" [ngStyle] = "{ 'max-width': getSizeImg(message).width, 'max-height': getSizeImg(message).height }">
|
|
33
|
+
<img class="message-contentX message-content-imageX" [src]="message.metadata.src" />
|
|
34
|
+
</div> -->
|
|
35
|
+
|
|
36
|
+
<!-- <img *ngIf="message.type == 'image' && message.metadata" class="message-contentX message-content-imageX"
|
|
37
|
+
[src]="message.metadata.src" [width]="getSizeImg(message).width"
|
|
38
|
+
[height]="getSizeImg(message).height" /> -->
|
|
9
39
|
|
|
40
|
+
<!-- [width]="getMetadataSize(message.metadata).width"
|
|
41
|
+
[height]="getMetadataSize(message.metadata).height" -->
|
|
10
42
|
<chat-image *ngIf="isImage(message)"
|
|
11
43
|
[metadata]="message.metadata"
|
|
12
44
|
[width]="sizeImage?.width"
|
|
@@ -21,42 +53,22 @@
|
|
|
21
53
|
(onElementRendered)="onElementRenderedFN($event)">
|
|
22
54
|
</chat-frame>
|
|
23
55
|
|
|
56
|
+
<!-- <chat-audio *ngIf="isAudio(message)"
|
|
57
|
+
[metadata]="message.metadata"
|
|
58
|
+
(onElementRendered)="onElementRenderedFN($event)">
|
|
59
|
+
</chat-audio> -->
|
|
24
60
|
|
|
25
61
|
<chat-audio *ngIf="isAudio(message)"
|
|
26
62
|
[metadata]="message.metadata"
|
|
27
63
|
[color]="fontColor"
|
|
28
|
-
[stylesMap]="stylesMap"
|
|
29
|
-
[translationMap]="translationMap">
|
|
64
|
+
[stylesMap]="stylesMap">
|
|
30
65
|
</chat-audio>
|
|
31
|
-
|
|
32
|
-
<!-- Json sources -->
|
|
66
|
+
|
|
33
67
|
<chat-json-sources *ngIf="jsonSources !== null && jsonSources.length > 0"
|
|
34
68
|
[items]="jsonSources"
|
|
35
69
|
(onElementRendered)="onElementRenderedFN($event)">
|
|
36
70
|
</chat-json-sources>
|
|
37
|
-
|
|
38
|
-
and the message was not already played by the proxy (avoids replay on session end) -->
|
|
39
|
-
<chat-audio-sync *ngIf="isAudioTTS(message) && !(voiceService.isWssVoiceActive$ | async) && !voiceService.wasProxyHandled(message?.uid)"
|
|
40
|
-
[message]="message"
|
|
41
|
-
[color]="fontColor">
|
|
42
|
-
</chat-audio-sync>
|
|
43
|
-
|
|
44
|
-
<!-- Karaoke display for TTS messages while a WSS voice session is active -->
|
|
45
|
-
<p *ngIf="isAudioTTS(message) && (voiceService.isWssVoiceActive$ | async) && _wssKaraokeWords$"
|
|
46
|
-
class="wss-karaoke"
|
|
47
|
-
[style.color]="fontColor">
|
|
48
|
-
<span *ngFor="let w of (_wssKaraokeWords$ | async); trackBy: trackKaraokeWord"
|
|
49
|
-
class="wss-word"
|
|
50
|
-
[class.future]="w.state === 'future'"
|
|
51
|
-
[class.active]="w.state === 'active'"
|
|
52
|
-
[class.past]="w.state === 'past'">{{ w.text }} </span>
|
|
53
|
-
</p>
|
|
54
|
-
|
|
55
|
-
<!-- Text fallback for TTS messages after the voice proxy session ended -->
|
|
56
|
-
<chat-text *ngIf="isAudioTTS(message) && !(voiceService.isWssVoiceActive$ | async) && voiceService.wasProxyHandled(message?.uid) && message?.text"
|
|
57
|
-
[text]="message?.text"
|
|
58
|
-
[color]="fontColor">
|
|
59
|
-
</chat-text>
|
|
71
|
+
|
|
60
72
|
|
|
61
73
|
<!-- <chat-frame *ngIf="message.metadata && message.metadata.type && message.metadata.type.includes('video')"
|
|
62
74
|
[metadata]="message.metadata"
|
|
@@ -68,17 +80,10 @@
|
|
|
68
80
|
<!-- <div *ngIf="message.type == 'text'"> -->
|
|
69
81
|
|
|
70
82
|
<!-- tooltip="{{message.timestamp | dateAgo}} ({{message.timestamp | date:'shortDate'}} {{message.timestamp | date:'HH:mm:ss'}})" placement="bottom" -->
|
|
71
|
-
<div *ngIf="message?.text &&
|
|
72
|
-
|
|
73
|
-
<!-- Word-by-word streaming reveal during an active voice session -->
|
|
74
|
-
<p *ngIf="_isStreaming" class="streaming-text" [style.color]="fontColor">
|
|
75
|
-
<span *ngFor="let w of _streamingWords; trackBy: trackWord"
|
|
76
|
-
class="stream-word"
|
|
77
|
-
[style.animation-delay]="(w.index * 80) + 'ms'">{{w.word}} </span>
|
|
78
|
-
</p>
|
|
83
|
+
<div *ngIf="(message?.text && !isAudio(message)) && !isJsonSources(message)">
|
|
79
84
|
|
|
80
85
|
<!-- [htmlEnabled]="(message?.type==='html')? true : false" -->
|
|
81
|
-
<chat-text *ngIf="message?.type !=='html'"
|
|
86
|
+
<chat-text *ngIf="jsonSources === null && message?.type !=='html'"
|
|
82
87
|
[text]="message?.text"
|
|
83
88
|
[color]="fontColor"
|
|
84
89
|
(onBeforeMessageRender)="onBeforeMessageRenderFN($event)"
|
|
@@ -93,5 +98,13 @@
|
|
|
93
98
|
</chat-html>
|
|
94
99
|
|
|
95
100
|
</div>
|
|
101
|
+
|
|
96
102
|
</div>
|
|
97
|
-
|
|
103
|
+
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
@@ -37,57 +37,4 @@
|
|
|
37
37
|
chat-audio {
|
|
38
38
|
display: flex;
|
|
39
39
|
}
|
|
40
|
-
}
|
|
41
|
-
.streaming-text {
|
|
42
|
-
padding: 0;
|
|
43
|
-
margin: 0;
|
|
44
|
-
line-height: 1.5;
|
|
45
|
-
|
|
46
|
-
.stream-word {
|
|
47
|
-
display: inline;
|
|
48
|
-
opacity: 0;
|
|
49
|
-
animation: stream-word-in 200ms ease-out forwards;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
@keyframes stream-word-in {
|
|
54
|
-
from {
|
|
55
|
-
opacity: 0;
|
|
56
|
-
transform: translateY(3px);
|
|
57
|
-
}
|
|
58
|
-
to {
|
|
59
|
-
opacity: 1;
|
|
60
|
-
transform: translateY(0);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// -- WSS TTS Karaoke ----------------------------------------------------------
|
|
65
|
-
.wss-karaoke {
|
|
66
|
-
// Match chat-text (ShadowDom) visual layout so there's no jump when voice opens.
|
|
67
|
-
// font-size must be set explicitly: the parent has font-size:10px but chat-text
|
|
68
|
-
// overrides it via :host { font-size: var(--font-size-bubble-message, 14px) }.
|
|
69
|
-
display: block;
|
|
70
|
-
margin: 0;
|
|
71
|
-
padding: 12px 16px;
|
|
72
|
-
font-size: var(--font-size-bubble-message, 14px);
|
|
73
|
-
line-height: 1.4em;
|
|
74
|
-
font-weight: 300;
|
|
75
|
-
overflow: hidden;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
.wss-word {
|
|
79
|
-
display: inline;
|
|
80
|
-
transition: opacity 120ms ease;
|
|
81
|
-
|
|
82
|
-
&.future {
|
|
83
|
-
opacity: 0.35;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
&.active {
|
|
87
|
-
opacity: 1;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
&.past {
|
|
91
|
-
opacity: 1;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
40
|
+
}
|
|
@@ -1,62 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
|
1
|
+
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|
3
2
|
import { By } from '@angular/platform-browser';
|
|
4
|
-
import { of } from 'rxjs';
|
|
5
|
-
import { MAX_WIDTH_IMAGES, MIN_WIDTH_IMAGES } from 'src/chat21-core/utils/constants';
|
|
6
|
-
import { VoiceService } from 'src/app/providers/voice/voice.service';
|
|
7
|
-
import { JsonSourcesParserService } from 'src/app/providers/json-sources-parser.service';
|
|
8
3
|
|
|
9
4
|
import { BubbleMessageComponent } from './bubble-message.component';
|
|
5
|
+
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
|
10
6
|
|
|
11
7
|
describe('BubbleMessageComponent', () => {
|
|
12
8
|
let component: BubbleMessageComponent;
|
|
13
9
|
let fixture: ComponentFixture<BubbleMessageComponent>;
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
isWssVoiceActive: false,
|
|
17
|
-
markProxyHandled: jasmine.createSpy('markProxyHandled'),
|
|
18
|
-
voiceTtsKaraoke$: of({ text: '', words: [], activeIndex: -1 }),
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const jsonSourcesParserMock = {
|
|
22
|
-
parseBaseFromMessage: jasmine.createSpy('parseBaseFromMessage').and.returnValue(null),
|
|
23
|
-
enrichSources: jasmine.createSpy('enrichSources').and.resolveTo(null),
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const textMessage: any = {
|
|
27
|
-
attributes: { projectId: 'p1' },
|
|
28
|
-
channel_type: 'group',
|
|
29
|
-
recipient: 'support-group-x',
|
|
30
|
-
recipient_fullname: 'Guest ',
|
|
31
|
-
sender: 'bot_1',
|
|
32
|
-
sender_fullname: 'BOT2',
|
|
33
|
-
status: 150,
|
|
34
|
-
text: 'Hello',
|
|
35
|
-
timestamp: 1629273999970,
|
|
36
|
-
type: 'text',
|
|
37
|
-
uid: 'm1',
|
|
38
|
-
isSender: false,
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
beforeEach(waitForAsync(() => {
|
|
11
|
+
beforeEach(async(() => {
|
|
42
12
|
TestBed.configureTestingModule({
|
|
43
|
-
declarations: [BubbleMessageComponent],
|
|
13
|
+
declarations: [ BubbleMessageComponent ],
|
|
44
14
|
schemas: [NO_ERRORS_SCHEMA],
|
|
45
|
-
|
|
46
|
-
{ provide: VoiceService, useValue: voiceServiceMock },
|
|
47
|
-
{ provide: JsonSourcesParserService, useValue: jsonSourcesParserMock },
|
|
15
|
+
imports: [
|
|
48
16
|
],
|
|
49
|
-
})
|
|
17
|
+
})
|
|
18
|
+
.compileComponents();
|
|
50
19
|
}));
|
|
51
20
|
|
|
52
21
|
beforeEach(() => {
|
|
53
22
|
fixture = TestBed.createComponent(BubbleMessageComponent);
|
|
54
23
|
component = fixture.componentInstance;
|
|
55
|
-
component.stylesMap = new Map([
|
|
56
|
-
['buttonFontSize', '14px'],
|
|
57
|
-
['themeColor', '#000'],
|
|
58
|
-
['foregroundColor', '#fff'],
|
|
59
|
-
]);
|
|
60
24
|
fixture.detectChanges();
|
|
61
25
|
});
|
|
62
26
|
|
|
@@ -64,115 +28,54 @@ describe('BubbleMessageComponent', () => {
|
|
|
64
28
|
expect(component).toBeTruthy();
|
|
65
29
|
});
|
|
66
30
|
|
|
67
|
-
it('should have a chat-text child
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it('should leave width undefined when metadata has no width (calcImageSize)', () => {
|
|
120
|
-
component.message = {
|
|
121
|
-
...textMessage,
|
|
122
|
-
metadata: { width: undefined, height: 10 },
|
|
123
|
-
};
|
|
124
|
-
component.ngOnChanges();
|
|
125
|
-
expect(component.sizeImage.width).toBeUndefined();
|
|
126
|
-
expect(component.sizeImage.height).toBe(10);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('should ignore non-object metadata', () => {
|
|
130
|
-
component.message = { ...textMessage, metadata: 'x' as any };
|
|
131
|
-
component.ngOnChanges();
|
|
132
|
-
expect(component.sizeImage).toEqual({ width: 0, height: 0 });
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('should derive fullnameColor from fontColor', () => {
|
|
136
|
-
component.message = textMessage;
|
|
137
|
-
component.fontColor = '#ff0000';
|
|
138
|
-
component.ngOnChanges();
|
|
139
|
-
expect(component.fullnameColor).toBeTruthy();
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('should prefer sender fullname color when name present', () => {
|
|
143
|
-
component.message = { ...textMessage, sender_fullname: 'Anna' };
|
|
144
|
-
component.fontColor = '#00ff00';
|
|
145
|
-
component.ngOnChanges();
|
|
146
|
-
expect(component.fullnameColor).toBeTruthy();
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
describe('emitters', () => {
|
|
151
|
-
beforeEach(() => {
|
|
152
|
-
component.message = textMessage;
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it('onBeforeMessageRenderFN should emit with sanitizer and message', () => {
|
|
156
|
-
spyOn(component.onBeforeMessageRender, 'emit');
|
|
157
|
-
const ev = { messageEl: {}, component: {} };
|
|
158
|
-
component.onBeforeMessageRenderFN(ev);
|
|
159
|
-
expect(component.onBeforeMessageRender.emit).toHaveBeenCalled();
|
|
160
|
-
const arg = (component.onBeforeMessageRender.emit as jasmine.Spy).calls.mostRecent().args[0];
|
|
161
|
-
expect(arg.message).toBe(component.message);
|
|
162
|
-
expect(arg.sanitizer).toBe(component.sanitizer);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it('onAfterMessageRenderFN should emit', () => {
|
|
166
|
-
spyOn(component.onAfterMessageRender, 'emit');
|
|
167
|
-
const ev = { messageEl: {}, component: {} };
|
|
168
|
-
component.onAfterMessageRenderFN(ev);
|
|
169
|
-
expect(component.onAfterMessageRender.emit).toHaveBeenCalled();
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it('onElementRenderedFN should forward element and status', () => {
|
|
173
|
-
spyOn(component.onElementRendered, 'emit');
|
|
174
|
-
component.onElementRenderedFN({ element: 'image', status: true });
|
|
175
|
-
expect(component.onElementRendered.emit).toHaveBeenCalledWith({ element: 'image', status: true });
|
|
176
|
-
});
|
|
177
|
-
});
|
|
31
|
+
it('should have a "chat-text" child element', () => {
|
|
32
|
+
const messages: any = {
|
|
33
|
+
attributes: {
|
|
34
|
+
projectId: "6013ec749b32000045be650e",
|
|
35
|
+
tiledesk_message_id: "611cbf8ffb379b00346660e7"
|
|
36
|
+
},
|
|
37
|
+
channel_type: "group",
|
|
38
|
+
recipient: "support-group-6013ec749b32000045be650e-4904aee91f8b487aad117bcda860549d",
|
|
39
|
+
recipient_fullname: "Guest ",
|
|
40
|
+
sender: "bot_602256f6c001b800342cb76f",
|
|
41
|
+
sender_fullname: "BOT2",
|
|
42
|
+
status: 150,
|
|
43
|
+
text: "Hello 👋. I'm a bot 🤖.\n\nChoose one of the options below or write a message to reach our staff.",
|
|
44
|
+
timestamp: 1629273999970,
|
|
45
|
+
type: "text",
|
|
46
|
+
uid: "-MhNI3eaIoLTOLoX3TAu",
|
|
47
|
+
isSender: false
|
|
48
|
+
}
|
|
49
|
+
component.message = messages
|
|
50
|
+
// component.textColor = 'black'
|
|
51
|
+
fixture.detectChanges()
|
|
52
|
+
const textChild = fixture.debugElement.query(By.css('chat-text'))
|
|
53
|
+
textChild.properties.text
|
|
54
|
+
expect(textChild).toBeTruthy();
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('should have a text inside "chat-text" child element', () => {
|
|
58
|
+
const messages: any = {
|
|
59
|
+
attributes: {
|
|
60
|
+
projectId: "6013ec749b32000045be650e",
|
|
61
|
+
tiledesk_message_id: "611cbf8ffb379b00346660e7"
|
|
62
|
+
},
|
|
63
|
+
channel_type: "group",
|
|
64
|
+
recipient: "support-group-6013ec749b32000045be650e-4904aee91f8b487aad117bcda860549d",
|
|
65
|
+
recipient_fullname: "Guest ",
|
|
66
|
+
sender: "bot_602256f6c001b800342cb76f",
|
|
67
|
+
sender_fullname: "BOT2",
|
|
68
|
+
status: 150,
|
|
69
|
+
text: "Hello 👋. I'm a bot 🤖.\n\nChoose one of the options below or write a message to reach our staff.",
|
|
70
|
+
timestamp: 1629273999970,
|
|
71
|
+
type: "text",
|
|
72
|
+
uid: "-MhNI3eaIoLTOLoX3TAu",
|
|
73
|
+
isSender: false
|
|
74
|
+
}
|
|
75
|
+
component.message = messages
|
|
76
|
+
// component.textColor = 'black'
|
|
77
|
+
fixture.detectChanges()
|
|
78
|
+
const textChild = fixture.debugElement.query(By.css('chat-text'))
|
|
79
|
+
expect(textChild.properties.text).toEqual(messages.text)
|
|
80
|
+
})
|
|
178
81
|
});
|
|
@@ -1,39 +1,25 @@
|
|
|
1
|
-
import { Component, EventEmitter, HostBinding, Input,
|
|
2
|
-
import { Observable, Subscription } from 'rxjs';
|
|
3
|
-
import { map, startWith } from 'rxjs/operators';
|
|
1
|
+
import { Component, EventEmitter, HostBinding, Input, Output } from '@angular/core';
|
|
4
2
|
import { DomSanitizer } from '@angular/platform-browser';
|
|
5
3
|
import { MessageModel } from 'src/chat21-core/models/message';
|
|
6
4
|
import { MESSAGE_TYPE_MINE, MESSAGE_TYPE_OTHERS, TYPE_MSG_URL_PREVIEW } from 'src/chat21-core/utils/constants';
|
|
7
5
|
import { convertColorToRGBA } from 'src/chat21-core/utils/utils';
|
|
8
|
-
import {
|
|
9
|
-
import { calcImageSize, isAudio, isAudioTTS, isFile, isFrame, isImage, isJsonSources, messageType } from 'src/chat21-core/utils/utils-message';
|
|
6
|
+
import { calcImageSize, isAudio, isFile, isFrame, isImage, isJsonSources, messageType } from 'src/chat21-core/utils/utils-message';
|
|
10
7
|
import { getColorBck } from 'src/chat21-core/utils/utils-user';
|
|
11
|
-
import {
|
|
8
|
+
import { JsonSourcesParserService } from 'src/app/providers/json-sources-parser.service';
|
|
12
9
|
import { JsonSourceItem } from '../json-sources/json-sources.component';
|
|
13
|
-
import { VoiceTtsKaraokeWord } from 'src/app/providers/voice/voice-streaming.types';
|
|
14
10
|
|
|
15
11
|
@Component({
|
|
16
12
|
selector: 'chat-bubble-message',
|
|
17
13
|
templateUrl: './bubble-message.component.html',
|
|
18
14
|
styleUrls: ['./bubble-message.component.scss']
|
|
19
15
|
})
|
|
20
|
-
export class BubbleMessageComponent
|
|
16
|
+
export class BubbleMessageComponent {
|
|
21
17
|
|
|
22
18
|
@Input() message: MessageModel;
|
|
23
19
|
@Input() isSameSender: boolean;
|
|
24
20
|
@Input() fontColor: string;
|
|
25
21
|
@Input() stylesMap: Map<string, string>;
|
|
26
|
-
|
|
27
|
-
/** When true, a newly-arrived bot text message reveals its words one by one. */
|
|
28
|
-
@Input() streamOnArrival = false;
|
|
29
|
-
/** One-shot flag: set once in ngOnChanges, never reverts so animation isn't replayed. */
|
|
30
|
-
_isStreaming = false;
|
|
31
|
-
/** Precomputed word list; rebuilt only when the message text changes. */
|
|
32
|
-
_streamingWords: Array<{ word: string; index: number }> = [];
|
|
33
|
-
/** Live karaoke word states driven by voiceTtsKaraoke$ during an active WSS session. */
|
|
34
|
-
_wssKaraokeWords$?: Observable<VoiceTtsKaraokeWord[]>;
|
|
35
|
-
|
|
36
|
-
private _kSub?: Subscription;
|
|
22
|
+
|
|
37
23
|
@Output() onBeforeMessageRender = new EventEmitter();
|
|
38
24
|
@Output() onAfterMessageRender = new EventEmitter();
|
|
39
25
|
@Output() onElementRendered = new EventEmitter<{ element: string; status: boolean }>();
|
|
@@ -58,56 +44,22 @@ export class BubbleMessageComponent implements OnInit, OnDestroy {
|
|
|
58
44
|
readonly isFrame = isFrame;
|
|
59
45
|
readonly isAudio = isAudio;
|
|
60
46
|
readonly isJsonSources = isJsonSources;
|
|
61
|
-
readonly isAudioTTS = isAudioTTS;
|
|
62
47
|
readonly messageType = messageType;
|
|
63
48
|
readonly convertColorToRGBA = convertColorToRGBA;
|
|
64
49
|
readonly MESSAGE_TYPE_MINE = MESSAGE_TYPE_MINE;
|
|
65
50
|
readonly MESSAGE_TYPE_OTHERS = MESSAGE_TYPE_OTHERS;
|
|
66
51
|
|
|
67
|
-
sizeImage: { width: number; height: number }
|
|
68
|
-
fullnameColor: string
|
|
52
|
+
sizeImage: { width: number; height: number };
|
|
53
|
+
fullnameColor: string;
|
|
69
54
|
jsonSources: JsonSourceItem[] | null = null;
|
|
70
55
|
isUrlPreviewMessage = false;
|
|
71
56
|
|
|
72
57
|
private urlPreviewReqId = 0;
|
|
73
58
|
|
|
74
59
|
constructor(
|
|
75
|
-
public sanitizer: DomSanitizer,
|
|
76
|
-
public voiceService: VoiceService,
|
|
60
|
+
public sanitizer: DomSanitizer,
|
|
77
61
|
private jsonSourcesParser: JsonSourcesParserService
|
|
78
|
-
) {
|
|
79
|
-
|
|
80
|
-
ngOnInit() {
|
|
81
|
-
// If this TTS message arrived while the voice proxy was active, mark it so
|
|
82
|
-
// audio-sync never replays it after the session ends.
|
|
83
|
-
if (isAudioTTS(this.message) && this.voiceService.isWssVoiceActive && this.message?.uid) {
|
|
84
|
-
this.voiceService.markProxyHandled(this.message.uid);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Set up karaoke observable for TTS messages during WSS sessions.
|
|
88
|
-
if (isAudioTTS(this.message) && this.message?.text) {
|
|
89
|
-
const text = this.message.text;
|
|
90
|
-
const rawWords = text.trim().split(/\s+/).filter((w) => w.length > 0);
|
|
91
|
-
// Always start as 'past' (fully visible). The karaoke RAF loop will drive
|
|
92
|
-
// words through future→active→past for the current speaking turn; using
|
|
93
|
-
// 'future' here would dimm old/history messages the moment voice opens.
|
|
94
|
-
const initialWords: VoiceTtsKaraokeWord[] = rawWords.map((w) => ({ text: w, state: 'past' as const }));
|
|
95
|
-
|
|
96
|
-
this._wssKaraokeWords$ = this.voiceService.voiceTtsKaraoke$.pipe(
|
|
97
|
-
startWith({ text, words: initialWords, activeIndex: -1 }),
|
|
98
|
-
map((frame) =>
|
|
99
|
-
frame.text === text
|
|
100
|
-
? (frame.words as VoiceTtsKaraokeWord[])
|
|
101
|
-
: initialWords,
|
|
102
|
-
),
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
ngOnDestroy(): void {
|
|
108
|
-
this._kSub?.unsubscribe();
|
|
109
|
-
this._kSub = undefined;
|
|
110
|
-
}
|
|
62
|
+
) {}
|
|
111
63
|
|
|
112
64
|
ngOnChanges(): void {
|
|
113
65
|
if (this.message?.metadata && typeof this.message.metadata === 'object') {
|
|
@@ -122,30 +74,8 @@ export class BubbleMessageComponent implements OnInit, OnDestroy {
|
|
|
122
74
|
this.fullnameColor = getColorBck(this.message.sender_fullname);
|
|
123
75
|
}
|
|
124
76
|
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
if (
|
|
128
|
-
!this._isStreaming &&
|
|
129
|
-
this.streamOnArrival &&
|
|
130
|
-
this.message?.isJustRecived === true &&
|
|
131
|
-
this.messageType(this.MESSAGE_TYPE_OTHERS, this.message) &&
|
|
132
|
-
!this.isAudio(this.message) &&
|
|
133
|
-
!this.isAudioTTS(this.message) &&
|
|
134
|
-
this.message?.type !== 'html'
|
|
135
|
-
) {
|
|
136
|
-
this._isStreaming = true;
|
|
137
|
-
this._streamingWords = (this.message.text ?? '')
|
|
138
|
-
.trim()
|
|
139
|
-
.split(/\s+/)
|
|
140
|
-
.filter(w => w.length > 0)
|
|
141
|
-
.map((word, index) => ({ word, index }));
|
|
142
|
-
this.message.isJustRecived = false;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (this.message?.type !== TYPE_MSG_URL_PREVIEW) {
|
|
146
|
-
this.jsonSources = null;
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
77
|
+
// Reset on every message change: we must not "leak" sources across different messages.
|
|
78
|
+
this.jsonSources = null;
|
|
149
79
|
|
|
150
80
|
// url_preview payload can live on message root OR inside metadata/attributes depending on the integration.
|
|
151
81
|
const urlPreviewLike =
|
|
@@ -169,14 +99,6 @@ export class BubbleMessageComponent implements OnInit, OnDestroy {
|
|
|
169
99
|
this.jsonSources = enriched;
|
|
170
100
|
}
|
|
171
101
|
|
|
172
|
-
trackWord(_index: number, item: { word: string; index: number }): number {
|
|
173
|
-
return item.index;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
trackKaraokeWord(index: number): number {
|
|
177
|
-
return index;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
102
|
onBeforeMessageRenderFN(event: any): void {
|
|
181
103
|
this.onBeforeMessageRender.emit({ message: this.message, sanitizer: this.sanitizer, messageEl: event.messageEl, component: event.component });
|
|
182
104
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
<div #actionButton class="button-in-msg action
|
|
1
|
+
<div #actionButton id="actionButton" class="button-in-msg action"
|
|
2
2
|
[ngClass]="{'disabled': isConversationArchived}"
|
|
3
|
-
(click)="actionButtonAction()"
|
|
4
|
-
(mouseover)="onMouseOver($event)"
|
|
3
|
+
(click)="actionButtonAction()"
|
|
4
|
+
(mouseover)="onMouseOver($event)"
|
|
5
5
|
(mouseout)="onMouseOut($event)">
|
|
6
6
|
{{button?.value}}
|
|
7
7
|
</div>
|
|
8
|
+
<!-- title="{{button?.value}}" -->
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ComponentFixture, TestBed, fakeAsync, tick, waitForAsync } from '@angular/core/testing';
|
|
3
|
-
import { By } from '@angular/platform-browser';
|
|
1
|
+
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|
4
2
|
|
|
5
3
|
import { ActionButtonComponent } from './action-button.component';
|
|
6
4
|
|
|
@@ -8,62 +6,20 @@ describe('ActionButtonComponent', () => {
|
|
|
8
6
|
let component: ActionButtonComponent;
|
|
9
7
|
let fixture: ComponentFixture<ActionButtonComponent>;
|
|
10
8
|
|
|
11
|
-
beforeEach(
|
|
9
|
+
beforeEach(async(() => {
|
|
12
10
|
TestBed.configureTestingModule({
|
|
13
|
-
declarations: [ActionButtonComponent]
|
|
14
|
-
})
|
|
11
|
+
declarations: [ ActionButtonComponent ]
|
|
12
|
+
})
|
|
13
|
+
.compileComponents();
|
|
15
14
|
}));
|
|
16
15
|
|
|
17
16
|
beforeEach(() => {
|
|
18
17
|
fixture = TestBed.createComponent(ActionButtonComponent);
|
|
19
18
|
component = fixture.componentInstance;
|
|
20
|
-
component.button = { value: 'OK', action: 'go' };
|
|
21
19
|
fixture.detectChanges();
|
|
22
20
|
});
|
|
23
21
|
|
|
24
22
|
it('should create', () => {
|
|
25
23
|
expect(component).toBeTruthy();
|
|
26
24
|
});
|
|
27
|
-
|
|
28
|
-
it('ngOnChanges should map theme CSS variables onto .action', () => {
|
|
29
|
-
component.fontSize = '16px';
|
|
30
|
-
component.backgroundColor = '#111';
|
|
31
|
-
component.textColor = '#222';
|
|
32
|
-
component.hoverBackgroundColor = '#333';
|
|
33
|
-
component.hoverTextColor = '#444';
|
|
34
|
-
component.ngOnChanges({
|
|
35
|
-
fontSize: new SimpleChange(null, '16px', true),
|
|
36
|
-
});
|
|
37
|
-
const el = fixture.nativeElement.querySelector('.action') as HTMLElement;
|
|
38
|
-
expect(el.style.getPropertyValue('--buttonFontSize').trim()).toBe('16px');
|
|
39
|
-
expect(el.style.getPropertyValue('--buttonBackgroundColor').trim()).toBeTruthy();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('actionButtonAction should emit when action present', fakeAsync(() => {
|
|
43
|
-
spyOn(component.onButtonClicked, 'emit');
|
|
44
|
-
const de = fixture.debugElement.query(By.css('.action'));
|
|
45
|
-
de.triggerEventHandler('click', {});
|
|
46
|
-
tick(500);
|
|
47
|
-
expect(component.onButtonClicked.emit).toHaveBeenCalled();
|
|
48
|
-
}));
|
|
49
|
-
|
|
50
|
-
it('actionButtonAction should no-op when button has no action', () => {
|
|
51
|
-
component.button = { value: 'X', action: '' };
|
|
52
|
-
fixture.detectChanges();
|
|
53
|
-
spyOn(component.onButtonClicked, 'emit');
|
|
54
|
-
component.actionButtonAction();
|
|
55
|
-
expect(component.onButtonClicked.emit).not.toHaveBeenCalled();
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('mouseover and mouseout should not throw', () => {
|
|
59
|
-
expect(() => component.onMouseOver({} as any)).not.toThrow();
|
|
60
|
-
expect(() => component.onMouseOut({} as any)).not.toThrow();
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('template should mark archived conversations as disabled', () => {
|
|
64
|
-
component.isConversationArchived = true;
|
|
65
|
-
fixture.detectChanges();
|
|
66
|
-
const el = fixture.nativeElement.querySelector('.action');
|
|
67
|
-
expect(el.classList.contains('disabled')).toBe(true);
|
|
68
|
-
});
|
|
69
25
|
});
|