@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,55 +1,45 @@
|
|
|
1
1
|
<div id='c21-menu-options'>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
[attr.aria-label]="g.OPTIONS"
|
|
5
|
-
[attr.aria-expanded]="g.isOpenMenuOptions ? 'true' : 'false'"
|
|
6
|
-
aria-haspopup="true"
|
|
7
|
-
(click)="f21_toggle_options()"
|
|
8
|
-
(mouseover)="isHover=true"
|
|
9
|
-
(mouseleave)="isHover=false">
|
|
10
|
-
<svg aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" width="24px" viewBox="0 0 24 24" [ngStyle]="{'display': (isHover || g.isOpenMenuOptions)? 'block': 'none'}">
|
|
2
|
+
<div class="button-menu-options" (click)="f21_toggle_options()" (mouseover)="isHover=true" (mouseleave)="isHover=false">
|
|
3
|
+
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" width="24px" viewBox="0 0 24 24" [ngStyle]="{'display': (isHover || g.isOpenMenuOptions)? 'block': 'none'}">
|
|
11
4
|
<g>
|
|
12
5
|
<path d="M0,0h24v24H0V0z" fill="none"/>
|
|
13
6
|
<path d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"/>
|
|
14
7
|
</g>
|
|
15
8
|
</svg>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class="
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
9
|
+
<!-- <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Livello_1" x="0px" y="0px" viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" [ngStyle]="{'display': (isHover)? 'block': 'none'}" xml:space="preserve">
|
|
10
|
+
<circle class="st1" cx="6" cy="12" r="2" [ngStyle]="{'fill': g.themeColor, 'opacity': 1}"/>
|
|
11
|
+
<circle class="st2" cx="18" cy="12" r="2"[ngStyle]="{'fill': g.themeColor, 'opacity': 0.6}"/>
|
|
12
|
+
<circle class="st3" cx="12" cy="12" r="2"[ngStyle]="{'fill': g.themeColor, 'opacity': 0.4}"/>
|
|
13
|
+
</svg> -->
|
|
14
|
+
</div>
|
|
15
|
+
<div *ngIf="g.isOpenMenuOptions" class="modal-menu-options">
|
|
22
16
|
<div class="">
|
|
23
|
-
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
[attr.aria-pressed]="g.soundEnabled ? 'true' : 'false'"
|
|
27
|
-
[attr.aria-label]="g.soundEnabled ? g.SOUND_ON : g.SOUND_OFF"
|
|
28
|
-
(click)="toggleSound()">
|
|
29
|
-
<svg *ngIf="!g.soundEnabled" aria-hidden="true" focusable="false" class="icon-menu" xmlns="http://www.w3.org/2000/svg"
|
|
17
|
+
|
|
18
|
+
<div class="c21-button" (click)="toggleSound()">
|
|
19
|
+
<svg *ngIf="!g.soundEnabled" aria-labelledby="altIconTitle" class="icon-menu" xmlns="http://www.w3.org/2000/svg"
|
|
30
20
|
width="20" height="20" viewBox="0 0 24 24">
|
|
31
21
|
<path fill="none" d="M0 0h24v24H0V0z" />
|
|
32
22
|
<path d="M4.34 2.93L2.93 4.34 7.29 8.7 7 9H3v6h4l5 5v-6.59l4.18 4.18c-.65.49-1.38.88-2.18 1.11v2.06c1.34-.3 2.57-.92 3.61-1.75l2.05 2.05 1.41-1.41L4.34 2.93zM10 15.17L7.83 13H5v-2h2.83l.88-.88L10 11.41v3.76zM19 12c0 .82-.15 1.61-.41 2.34l1.53 1.53c.56-1.17.88-2.48.88-3.87 0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zm-7-8l-1.88 1.88L12 7.76zm4.5 8c0-1.77-1.02-3.29-2.5-4.03v1.79l2.48 2.48c.01-.08.02-.16.02-.24z" />
|
|
23
|
+
<title id="altIconTitle">{{g.SOUND_OFF}}</title>
|
|
33
24
|
</svg>
|
|
34
|
-
<svg *ngIf="g.soundEnabled" aria-
|
|
25
|
+
<svg *ngIf="g.soundEnabled" aria-labelledby="altIconTitle" class="icon-menu" xmlns="http://www.w3.org/2000/svg"
|
|
35
26
|
width="20" height="20" viewBox="0 0 24 24">
|
|
36
27
|
<path fill="none" d="M0 0h24v24H0V0z" />
|
|
37
28
|
<path d="M3 9v6h4l5 5V4L7 9H3zm7-.17v6.34L7.83 13H5v-2h2.83L10 8.83zM16.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77 0-4.28-2.99-7.86-7-8.77z" />
|
|
29
|
+
<title id="altIconTitle">{{g.SOUND_ON}}</title>
|
|
38
30
|
</svg>
|
|
39
31
|
<span *ngIf="!g.soundEnabled" class="label-menu-item"> {{g.SOUND_OFF}}</span>
|
|
40
32
|
<span *ngIf="g.soundEnabled" class="label-menu-item"> {{g.SOUND_ON}}</span>
|
|
41
33
|
|
|
42
|
-
</
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
[attr.aria-label]="g.LOGOUT"
|
|
46
|
-
(click)="signOut()">
|
|
47
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" aria-hidden="true" focusable="false" class="icon-menu">
|
|
34
|
+
</div>
|
|
35
|
+
<div class="c21-button" *ngIf="g.isLogged && g.showLogoutOption" (click)="signOut()">
|
|
36
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" aria-labelledby="altIconTitle" class="icon-menu">
|
|
48
37
|
<path fill="none" d="M0 0h24v24H0V0z"/>
|
|
49
38
|
<path d="M10.09 15.59L11.5 17l5-5-5-5-1.41 1.41L12.67 11H3v2h9.67l-2.58 2.59zM19 3H5c-1.11 0-2 .9-2 2v4h2V5h14v14H5v-4H3v4c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/>
|
|
39
|
+
<title id="altIconTitle">{{g.LOGOUT}}</title>
|
|
50
40
|
</svg>
|
|
51
41
|
<span *ngIf="g.isLogged && g.showLogoutOption" class="label-menu-item"> {{g.LOGOUT}}</span>
|
|
52
|
-
</
|
|
42
|
+
</div>
|
|
53
43
|
<div class="c21-button build_version_menu">
|
|
54
44
|
<span class="label-menu-item"> {{g.BUILD_VERSION}}</span>
|
|
55
45
|
</div>
|
|
@@ -1,143 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
|
3
|
-
import { By } from '@angular/platform-browser';
|
|
4
|
-
import { convertColorToRGBA } from 'src/chat21-core/utils/utils';
|
|
1
|
+
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|
5
2
|
import { Globals } from '../../utils/globals';
|
|
3
|
+
|
|
6
4
|
import { MenuOptionsComponent } from './menu-options.component';
|
|
7
5
|
|
|
8
6
|
describe('MenuOptionsComponent', () => {
|
|
9
|
-
let fixture: ComponentFixture<MenuOptionsComponent>;
|
|
10
7
|
let component: MenuOptionsComponent;
|
|
11
|
-
let
|
|
8
|
+
let fixture: ComponentFixture<MenuOptionsComponent>;
|
|
12
9
|
|
|
13
|
-
beforeEach(
|
|
10
|
+
beforeEach(async(() => {
|
|
14
11
|
TestBed.configureTestingModule({
|
|
15
|
-
declarations: [MenuOptionsComponent],
|
|
16
|
-
providers: [Globals]
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
declarations: [ MenuOptionsComponent ],
|
|
13
|
+
providers: [ Globals ]
|
|
14
|
+
})
|
|
15
|
+
.compileComponents();
|
|
19
16
|
}));
|
|
20
17
|
|
|
21
18
|
beforeEach(() => {
|
|
22
19
|
fixture = TestBed.createComponent(MenuOptionsComponent);
|
|
23
20
|
component = fixture.componentInstance;
|
|
24
|
-
|
|
25
|
-
globals.initDefafultParameters();
|
|
26
|
-
globals.OPTIONS = 'Opzioni';
|
|
27
|
-
globals.SOUND_ON = 'Suono on';
|
|
28
|
-
globals.SOUND_OFF = 'Suono off';
|
|
29
|
-
globals.LOGOUT = 'Esci';
|
|
30
|
-
globals.BUILD_VERSION = 'v9.9.9-test';
|
|
31
|
-
globals.themeColor = '#2a6ac1';
|
|
32
|
-
globals.soundEnabled = true;
|
|
33
|
-
globals.isOpenMenuOptions = false;
|
|
34
|
-
globals.isLogged = false;
|
|
35
|
-
globals.showLogoutOption = true;
|
|
21
|
+
fixture.detectChanges();
|
|
36
22
|
});
|
|
37
23
|
|
|
38
24
|
it('should create', () => {
|
|
39
25
|
expect(component).toBeTruthy();
|
|
40
26
|
});
|
|
41
|
-
|
|
42
|
-
it('ngOnInit calcola themeColor50 da themeColor', () => {
|
|
43
|
-
fixture.detectChanges();
|
|
44
|
-
expect(component.themeColor50).toBe(convertColorToRGBA(globals.themeColor, 50));
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('f21_toggle_options apre e chiude il menu', () => {
|
|
48
|
-
fixture.detectChanges();
|
|
49
|
-
const toggle = fixture.debugElement.query(By.css('.button-menu-options'));
|
|
50
|
-
toggle.triggerEventHandler('click', null);
|
|
51
|
-
expect(globals.isOpenMenuOptions).toBe(true);
|
|
52
|
-
fixture.detectChanges();
|
|
53
|
-
expect(fixture.debugElement.query(By.css('.modal-menu-options'))).toBeTruthy();
|
|
54
|
-
component.f21_toggle_options();
|
|
55
|
-
expect(globals.isOpenMenuOptions).toBe(false);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('toggleSound inverte soundEnabled e chiude il menu', () => {
|
|
59
|
-
globals.soundEnabled = false;
|
|
60
|
-
globals.isOpenMenuOptions = true;
|
|
61
|
-
fixture.detectChanges();
|
|
62
|
-
component.toggleSound();
|
|
63
|
-
expect(globals.soundEnabled).toBe(true);
|
|
64
|
-
expect(globals.isOpenMenuOptions).toBe(false);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('signOut emette onSignOut e chiude il menu', () => {
|
|
68
|
-
spyOn(component.onSignOut, 'emit');
|
|
69
|
-
globals.isOpenMenuOptions = true;
|
|
70
|
-
component.signOut();
|
|
71
|
-
expect(globals.isOpenMenuOptions).toBe(false);
|
|
72
|
-
expect(component.onSignOut.emit).toHaveBeenCalled();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('menu aperto: etichette suono e versione visibili', () => {
|
|
76
|
-
globals.isOpenMenuOptions = true;
|
|
77
|
-
globals.soundEnabled = true;
|
|
78
|
-
fixture.detectChanges();
|
|
79
|
-
const root = fixture.nativeElement as HTMLElement;
|
|
80
|
-
expect(root.textContent).toContain('Suono on');
|
|
81
|
-
expect(root.textContent).toContain('v9.9.9-test');
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('logout visibile solo con isLogged e showLogoutOption', () => {
|
|
85
|
-
globals.isOpenMenuOptions = true;
|
|
86
|
-
globals.isLogged = false;
|
|
87
|
-
globals.showLogoutOption = true;
|
|
88
|
-
fixture.detectChanges();
|
|
89
|
-
let logoutBtn = fixture.debugElement.queryAll(By.css('.modal-menu-options button')).find((d) =>
|
|
90
|
-
(d.nativeElement as HTMLElement).getAttribute('aria-label')?.includes('Esci'),
|
|
91
|
-
);
|
|
92
|
-
expect(logoutBtn).toBeFalsy();
|
|
93
|
-
|
|
94
|
-
globals.isLogged = true;
|
|
95
|
-
fixture.detectChanges();
|
|
96
|
-
logoutBtn = fixture.debugElement.queryAll(By.css('.modal-menu-options button')).find((d) =>
|
|
97
|
-
(d.nativeElement as HTMLElement).getAttribute('aria-label') === 'Esci',
|
|
98
|
-
);
|
|
99
|
-
expect(logoutBtn).toBeTruthy();
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('isHover espone l’icona ingranaggio (mouseover / mouseleave)', () => {
|
|
103
|
-
fixture.detectChanges();
|
|
104
|
-
const toggle = fixture.debugElement.query(By.css('.button-menu-options'));
|
|
105
|
-
expect(component.isHover).toBe(false);
|
|
106
|
-
toggle.triggerEventHandler('mouseover', null);
|
|
107
|
-
expect(component.isHover).toBe(true);
|
|
108
|
-
toggle.triggerEventHandler('mouseleave', null);
|
|
109
|
-
expect(component.isHover).toBe(false);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('aria-expanded del toggle riflette isOpenMenuOptions', () => {
|
|
113
|
-
globals.isOpenMenuOptions = true;
|
|
114
|
-
fixture.detectChanges();
|
|
115
|
-
const btn = fixture.debugElement.query(By.css('.button-menu-options')).nativeElement as HTMLButtonElement;
|
|
116
|
-
expect(btn.getAttribute('aria-expanded')).toBe('true');
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it('keydown escape sul pannello opzioni chiude il menu', () => {
|
|
120
|
-
globals.isOpenMenuOptions = true;
|
|
121
|
-
fixture.detectChanges();
|
|
122
|
-
const modal = fixture.debugElement.query(By.css('.modal-menu-options'));
|
|
123
|
-
const ev = new KeyboardEvent('keydown', { key: 'Escape' });
|
|
124
|
-
spyOn(ev, 'preventDefault');
|
|
125
|
-
spyOn(ev, 'stopPropagation');
|
|
126
|
-
modal.triggerEventHandler('keydown.escape', ev);
|
|
127
|
-
expect(globals.isOpenMenuOptions).toBe(false);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it('pulsante suono espone aria-pressed e aria-label in base a soundEnabled', () => {
|
|
131
|
-
globals.isOpenMenuOptions = true;
|
|
132
|
-
globals.soundEnabled = false;
|
|
133
|
-
fixture.detectChanges();
|
|
134
|
-
const soundBtn = fixture.debugElement.query(By.css('button[aria-label="Suono off"]'))
|
|
135
|
-
.nativeElement as HTMLButtonElement;
|
|
136
|
-
expect(soundBtn.getAttribute('aria-pressed')).toBe('false');
|
|
137
|
-
globals.soundEnabled = true;
|
|
138
|
-
fixture.detectChanges();
|
|
139
|
-
const soundOn = fixture.debugElement.query(By.css('button[aria-label="Suono on"]'))
|
|
140
|
-
.nativeElement as HTMLButtonElement;
|
|
141
|
-
expect(soundOn.getAttribute('aria-pressed')).toBe('true');
|
|
142
|
-
});
|
|
143
27
|
});
|
|
@@ -1,21 +1,23 @@
|
|
|
1
|
-
<div class="audio-container">
|
|
2
1
|
|
|
2
|
+
<!-- <audio *ngIf="metadata" controls>
|
|
3
|
+
<source [src]="metadata.src" type="audio/mpeg">
|
|
4
|
+
</audio> *ngIf="!metadata"-->
|
|
5
|
+
<div class="audio-container">
|
|
6
|
+
|
|
3
7
|
<div class="audio-track">
|
|
4
|
-
<button
|
|
5
|
-
|
|
6
|
-
[attr.aria-label]="isPlaying
|
|
7
|
-
? (translationMap?.get('BUTTON_PAUSE_AUDIO') || 'Pause audio')
|
|
8
|
-
: (translationMap?.get('BUTTON_PLAY_AUDIO') || 'Play audio')"
|
|
9
|
-
[attr.aria-pressed]="isPlaying ? 'true' : 'false'"
|
|
10
|
-
(click)="playPauseAudio()">
|
|
11
|
-
<svg *ngIf="!isPlaying" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" aria-hidden="true" focusable="false">
|
|
8
|
+
<button *ngIf="!isPlaying" class="play-pause" (click)="playPauseAudio()">
|
|
9
|
+
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px">
|
|
12
10
|
<path d="M320-200v-560l440 280-440 280Z"/>
|
|
13
11
|
</svg>
|
|
14
|
-
<
|
|
12
|
+
<!-- <i class="material-icons">play_arrow</i> -->
|
|
13
|
+
</button>
|
|
14
|
+
<button *ngIf="isPlaying" class="play-pause" (click)="playPauseAudio()">
|
|
15
|
+
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" >
|
|
15
16
|
<path d="M560-200v-560h160v560H560Zm-320 0v-560h160v560H240Z"/>
|
|
16
17
|
</svg>
|
|
18
|
+
<!-- <i class="material-icons">pause</i> -->
|
|
17
19
|
</button>
|
|
18
|
-
<div class="duration" [style.color]="color"
|
|
20
|
+
<div class="duration" [style.color]="color">
|
|
19
21
|
<span *ngIf="!isPlaying">{{ audioDuration ? formatTime(audioDuration) : '00:00' }}</span>
|
|
20
22
|
<span *ngIf="isPlaying">{{ formatTime(currentTime) }}</span>
|
|
21
23
|
</div>
|
|
@@ -24,7 +26,7 @@
|
|
|
24
26
|
|
|
25
27
|
<div class="audio-player-custom">
|
|
26
28
|
<audio #audioElement [src]="audioUrl"></audio>
|
|
27
|
-
<canvas #canvasElement class="waveformCanvas"
|
|
29
|
+
<canvas #canvasElement class="waveformCanvas"></canvas>
|
|
28
30
|
</div>
|
|
29
31
|
|
|
30
|
-
</div>
|
|
32
|
+
</div>
|
|
@@ -1,158 +1,23 @@
|
|
|
1
1
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
-
import { LoggerInstance } from 'src/chat21-core/providers/logger/loggerInstance';
|
|
3
|
-
import { CustomLogger } from 'src/chat21-core/providers/logger/customLogger';
|
|
4
|
-
import { NGXLogger } from 'ngx-logger';
|
|
5
2
|
|
|
6
3
|
import { AudioComponent } from './audio.component';
|
|
7
4
|
|
|
8
|
-
describe('
|
|
5
|
+
describe('AudioTrackComponent', () => {
|
|
9
6
|
let component: AudioComponent;
|
|
10
7
|
let fixture: ComponentFixture<AudioComponent>;
|
|
11
|
-
const ngxlogger = jasmine.createSpyObj('NGXLogger', ['log', 'trace', 'debug', 'warn', 'error', 'info']);
|
|
12
|
-
const customLogger = new CustomLogger(ngxlogger);
|
|
13
|
-
const arrayBuf = new ArrayBuffer(64);
|
|
14
|
-
|
|
15
|
-
const fakeBuffer = {
|
|
16
|
-
duration: 90,
|
|
17
|
-
getChannelData: () => new Float32Array(4000),
|
|
18
|
-
} as unknown as AudioBuffer;
|
|
19
8
|
|
|
20
9
|
beforeEach(async () => {
|
|
21
|
-
LoggerInstance.setInstance(customLogger);
|
|
22
|
-
spyOn(window, 'fetch').and.returnValue(
|
|
23
|
-
Promise.resolve({
|
|
24
|
-
arrayBuffer: () => Promise.resolve(arrayBuf),
|
|
25
|
-
} as Response),
|
|
26
|
-
);
|
|
27
|
-
spyOn(AudioContext.prototype, 'decodeAudioData').and.returnValue(Promise.resolve(fakeBuffer));
|
|
28
|
-
|
|
29
10
|
await TestBed.configureTestingModule({
|
|
30
|
-
declarations: [AudioComponent]
|
|
11
|
+
declarations: [ AudioComponent ]
|
|
31
12
|
})
|
|
32
|
-
|
|
33
|
-
set: {
|
|
34
|
-
template: `
|
|
35
|
-
<div class="audio-container">
|
|
36
|
-
<div class="audio-track"></div>
|
|
37
|
-
<div class="audio-player-custom">
|
|
38
|
-
<audio #audioElement></audio>
|
|
39
|
-
<canvas #canvasElement width="120" height="32"></canvas>
|
|
40
|
-
</div>
|
|
41
|
-
</div>`,
|
|
42
|
-
},
|
|
43
|
-
})
|
|
44
|
-
.compileComponents();
|
|
13
|
+
.compileComponents();
|
|
45
14
|
|
|
46
15
|
fixture = TestBed.createComponent(AudioComponent);
|
|
47
16
|
component = fixture.componentInstance;
|
|
48
|
-
component.stylesMap = new Map<string, string>([
|
|
49
|
-
['bubbleSentBackground', 'rgba(10, 20, 30, 1)'],
|
|
50
|
-
['bubbleSentTextColor', '#112233'],
|
|
51
|
-
]);
|
|
52
|
-
component.color = '#000000';
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('should create', async () => {
|
|
56
|
-
const blob = new Blob([new Uint8Array(arrayBuf.byteLength)], { type: 'audio/wav' });
|
|
57
|
-
component.audioBlob = blob;
|
|
58
17
|
fixture.detectChanges();
|
|
59
|
-
await fixture.whenStable();
|
|
60
|
-
fixture.detectChanges();
|
|
61
|
-
expect(component).toBeTruthy();
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('formatTime should pad seconds under 10', () => {
|
|
65
|
-
expect(component.formatTime(0)).toBe('0:00');
|
|
66
|
-
expect(component.formatTime(9)).toBe('0:09');
|
|
67
|
-
expect(component.formatTime(70)).toBe('1:10');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('extractFirstColor should parse first rgba from gradient string', () => {
|
|
71
|
-
expect(component.extractFirstColor('linear-gradient(rgba(1, 2, 3, 0.5), red)')).toBe('rgba(1, 2, 3, 0.5)');
|
|
72
|
-
expect(component.extractFirstColor('no-color')).toBeNull();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('drawWaveform should return early when canvas context missing', () => {
|
|
76
|
-
const canvas = document.createElement('canvas');
|
|
77
|
-
spyOn(canvas, 'getContext').and.returnValue(null);
|
|
78
|
-
(component as any).waveformCanvas = { nativeElement: canvas };
|
|
79
|
-
(component as any).audioBuffer = fakeBuffer;
|
|
80
|
-
(component as any).audioDuration = 10;
|
|
81
|
-
(component as any).audioElement = {
|
|
82
|
-
nativeElement: { currentTime: 0, paused: true },
|
|
83
|
-
};
|
|
84
|
-
expect(() => component.drawWaveform(fakeBuffer)).not.toThrow();
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('drawWaveform should render bars when context exists', () => {
|
|
88
|
-
const fillRect = jasmine.createSpy('fillRect');
|
|
89
|
-
const clearRect = jasmine.createSpy('clearRect');
|
|
90
|
-
const canvas = document.createElement('canvas');
|
|
91
|
-
canvas.width = 200;
|
|
92
|
-
canvas.height = 40;
|
|
93
|
-
spyOn(canvas, 'getContext').and.returnValue({ fillRect, clearRect } as any);
|
|
94
|
-
(component as any).waveformCanvas = { nativeElement: canvas };
|
|
95
|
-
(component as any).audioElement = {
|
|
96
|
-
nativeElement: { currentTime: 0, paused: true },
|
|
97
|
-
};
|
|
98
|
-
(component as any).audioDuration = 10;
|
|
99
|
-
component.drawWaveform(fakeBuffer);
|
|
100
|
-
expect(clearRect).toHaveBeenCalled();
|
|
101
|
-
expect(fillRect).toHaveBeenCalled();
|
|
102
18
|
});
|
|
103
19
|
|
|
104
|
-
it('
|
|
105
|
-
|
|
106
|
-
component.audioBlob = blob;
|
|
107
|
-
spyOn(URL, 'createObjectURL').and.returnValue('blob:mock-audio');
|
|
108
|
-
fixture.detectChanges();
|
|
109
|
-
await fixture.whenStable();
|
|
110
|
-
fixture.detectChanges();
|
|
111
|
-
expect(component.rawAudioUrl).toBe('blob:mock-audio');
|
|
112
|
-
expect(URL.createObjectURL).toHaveBeenCalledWith(blob);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('ngAfterViewInit with metadata.src should fetch and decode', async () => {
|
|
116
|
-
component.audioBlob = null;
|
|
117
|
-
component.metadata = { src: 'blob:from-meta' };
|
|
118
|
-
fixture.detectChanges();
|
|
119
|
-
await fixture.whenStable();
|
|
120
|
-
fixture.detectChanges();
|
|
121
|
-
expect(window.fetch).toHaveBeenCalled();
|
|
122
|
-
expect(component.audioDuration).toBe(90);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it('playPauseAudio should toggle play state when buffer ready', () => {
|
|
126
|
-
spyOn(window, 'requestAnimationFrame').and.stub();
|
|
127
|
-
(component as any).audioBuffer = fakeBuffer;
|
|
128
|
-
(component as any).audioDuration = 10;
|
|
129
|
-
const play = jasmine.createSpy('play').and.returnValue(Promise.resolve());
|
|
130
|
-
const pause = jasmine.createSpy('pause');
|
|
131
|
-
const canvas = document.createElement('canvas');
|
|
132
|
-
canvas.width = 120;
|
|
133
|
-
canvas.height = 32;
|
|
134
|
-
spyOn(canvas, 'getContext').and.returnValue({
|
|
135
|
-
fillRect: jasmine.createSpy(),
|
|
136
|
-
clearRect: jasmine.createSpy(),
|
|
137
|
-
} as any);
|
|
138
|
-
(component as any).waveformCanvas = { nativeElement: canvas };
|
|
139
|
-
(component as any).audioElement = {
|
|
140
|
-
nativeElement: { paused: true, currentTime: 0, play, pause, ontimeupdate: null as any, onended: null as any },
|
|
141
|
-
};
|
|
142
|
-
(component as any).audioContext = { resume: jasmine.createSpy().and.returnValue(Promise.resolve()) };
|
|
143
|
-
|
|
144
|
-
component.playPauseAudio();
|
|
145
|
-
expect(play).toHaveBeenCalled();
|
|
146
|
-
expect(component.isPlaying).toBe(true);
|
|
147
|
-
|
|
148
|
-
(component as any).audioElement.nativeElement.paused = false;
|
|
149
|
-
component.playPauseAudio();
|
|
150
|
-
expect(pause).toHaveBeenCalled();
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it('getAudioDuration should set audioDuration from decoded buffer', async () => {
|
|
154
|
-
component.metadata = { src: 'blob:x' };
|
|
155
|
-
await component.getAudioDuration();
|
|
156
|
-
expect(component.audioDuration).toBe(90);
|
|
20
|
+
it('should create', () => {
|
|
21
|
+
expect(component).toBeTruthy();
|
|
157
22
|
});
|
|
158
23
|
});
|
|
@@ -18,7 +18,6 @@ export class AudioComponent implements AfterViewInit {
|
|
|
18
18
|
@Input() metadata: any | null = null;
|
|
19
19
|
@Input() color: string;
|
|
20
20
|
@Input() stylesMap: Map<string, string>;
|
|
21
|
-
@Input() translationMap: Map<string, string>;
|
|
22
21
|
|
|
23
22
|
audioUrl: SafeUrl | null = null;
|
|
24
23
|
rawAudioUrl: string | null = null;
|
|
@@ -155,10 +154,15 @@ export class AudioComponent implements AfterViewInit {
|
|
|
155
154
|
// });
|
|
156
155
|
|
|
157
156
|
const response = await fetch(this.rawAudioUrl!);
|
|
157
|
+
this.logger.debug('getAudioDuration: response ---> ', response)
|
|
158
158
|
const arrayBuffer = await response.arrayBuffer();
|
|
159
|
+
this.logger.debug('getAudioDuration: arrayBuffer ---> ', arrayBuffer)
|
|
159
160
|
const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
|
|
161
|
+
this.logger.debug('getAudioDuration: audioContext ---> ', audioContext)
|
|
160
162
|
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
|
|
163
|
+
this.logger.debug('getAudioDuration: audioBuffer ---> ', audioBuffer)
|
|
161
164
|
this.audioDuration = audioBuffer.duration;
|
|
165
|
+
this.logger.debug('getAudioDuration: audioDuration ---> ', this.audioDuration)
|
|
162
166
|
|
|
163
167
|
}
|
|
164
168
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<div class="c21-icon-avatar">
|
|
2
2
|
<div class="c21-avatar-image profile_image">
|
|
3
3
|
<!-- is a BOT -->
|
|
4
|
-
<img *ngIf="senderID?.indexOf('bot_') !== -1 || senderFullname.toLowerCase().includes('bot')" [src]="url"
|
|
4
|
+
<img *ngIf="senderID?.indexOf('bot_') !== -1 || senderFullname.toLowerCase().includes('bot')" [src]="url" (error)="onBotImgError($event)" (load)="onLoadedBot($event)"/>
|
|
5
5
|
<!-- is a HUMAN -->
|
|
6
|
-
<img *ngIf="senderID?.indexOf('bot_') === -1 && !senderFullname.toLowerCase().includes('bot')" [src]="url"
|
|
6
|
+
<img *ngIf="senderID?.indexOf('bot_') === -1 && !senderFullname.toLowerCase().includes('bot')" [src]="url" (error)="onHumanImgError($event)" (load)="onLoadedHuman($event)"/>
|
|
7
7
|
</div>
|
|
8
8
|
</div>
|
|
9
9
|
|
|
@@ -1,119 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
|
1
|
+
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|
3
2
|
import { ImageRepoService } from '../../../../chat21-core/providers/abstract/image-repo.service';
|
|
4
3
|
|
|
5
4
|
import { AvatarComponent } from './avatar.component';
|
|
6
5
|
|
|
7
|
-
@Injectable()
|
|
8
|
-
class ImageRepoStub extends ImageRepoService {
|
|
9
|
-
getImagePhotoUrl(uid: string): string {
|
|
10
|
-
return `https://cdn.test/photo/${uid}`;
|
|
11
|
-
}
|
|
12
|
-
checkImageExists(_uid: string, _cb: (exist: boolean) => void): void {}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
6
|
describe('AvatarComponent', () => {
|
|
16
7
|
let component: AvatarComponent;
|
|
17
8
|
let fixture: ComponentFixture<AvatarComponent>;
|
|
18
9
|
|
|
19
|
-
beforeEach(
|
|
10
|
+
beforeEach(async(() => {
|
|
20
11
|
TestBed.configureTestingModule({
|
|
21
|
-
declarations: [AvatarComponent],
|
|
22
|
-
providers: [
|
|
23
|
-
})
|
|
12
|
+
declarations: [ AvatarComponent ],
|
|
13
|
+
providers: [ ImageRepoService ]
|
|
14
|
+
})
|
|
15
|
+
.compileComponents();
|
|
24
16
|
}));
|
|
25
17
|
|
|
26
18
|
beforeEach(() => {
|
|
27
19
|
fixture = TestBed.createComponent(AvatarComponent);
|
|
28
20
|
component = fixture.componentInstance;
|
|
29
|
-
|
|
21
|
+
fixture.detectChanges();
|
|
30
22
|
});
|
|
31
23
|
|
|
32
24
|
it('should create', () => {
|
|
33
|
-
component.senderID = 'user_1';
|
|
34
|
-
component.senderFullname = 'Alice';
|
|
35
|
-
fixture.detectChanges();
|
|
36
25
|
expect(component).toBeTruthy();
|
|
37
26
|
});
|
|
38
|
-
|
|
39
|
-
it('ngOnInit should prefer remote photo when checkImageExists returns true', () => {
|
|
40
|
-
const remote = 'https://cdn.test/photo/bot_1';
|
|
41
|
-
spyOn(AvatarComponent.prototype as any, 'checkImageExists').and.callFake((_url: string, cb: (b: boolean) => void) => {
|
|
42
|
-
cb(true);
|
|
43
|
-
});
|
|
44
|
-
component.senderID = 'bot_1';
|
|
45
|
-
component.senderFullname = 'Support Bot';
|
|
46
|
-
component.ngOnInit();
|
|
47
|
-
expect(component.url).toBe(remote);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('ngOnInit should keep default bot asset when remote image missing', () => {
|
|
51
|
-
spyOn(AvatarComponent.prototype as any, 'checkImageExists').and.callFake((_url: string, cb: (b: boolean) => void) => {
|
|
52
|
-
cb(false);
|
|
53
|
-
});
|
|
54
|
-
component.senderID = 'bot_1';
|
|
55
|
-
component.senderFullname = 'Bot';
|
|
56
|
-
component.ngOnInit();
|
|
57
|
-
expect(component.url).toBe(component.baseLocation + '/assets/images/tommy_bot_tiledesk.svg');
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('ngOnInit should use human default when not bot and photo missing', () => {
|
|
61
|
-
spyOn(AvatarComponent.prototype as any, 'checkImageExists').and.callFake((_url: string, cb: (b: boolean) => void) => {
|
|
62
|
-
cb(false);
|
|
63
|
-
});
|
|
64
|
-
component.senderID = 'user_99';
|
|
65
|
-
component.senderFullname = 'Bob';
|
|
66
|
-
component.ngOnInit();
|
|
67
|
-
expect(component.url).toBe(component.baseLocation + '/assets/images/chat_human_avatar.svg');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('checkImageExists should invoke callback on image load', (done) => {
|
|
71
|
-
const imgCtor = window.Image;
|
|
72
|
-
(window as any).Image = function MockImage(this: any) {
|
|
73
|
-
setTimeout(() => this.onload && this.onload(), 0);
|
|
74
|
-
return this;
|
|
75
|
-
} as any;
|
|
76
|
-
component.checkImageExists('https://x', (ok) => {
|
|
77
|
-
(window as any).Image = imgCtor;
|
|
78
|
-
expect(ok).toBe(true);
|
|
79
|
-
done();
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('checkImageExists should invoke callback false on error', (done) => {
|
|
84
|
-
const imgCtor = window.Image;
|
|
85
|
-
(window as any).Image = function MockImage(this: any) {
|
|
86
|
-
setTimeout(() => this.onerror && this.onerror(), 0);
|
|
87
|
-
return this;
|
|
88
|
-
} as any;
|
|
89
|
-
component.checkImageExists('https://bad', (ok) => {
|
|
90
|
-
(window as any).Image = imgCtor;
|
|
91
|
-
expect(ok).toBe(false);
|
|
92
|
-
done();
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('onBotImgError should swap to local bot svg', () => {
|
|
97
|
-
const target = { src: 'bad' } as any;
|
|
98
|
-
component.baseLocation = 'https://host';
|
|
99
|
-
component.onBotImgError({ target });
|
|
100
|
-
expect(target.src).toContain('tommy_bot_tiledesk.svg');
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('onHumanImgError should swap to local human svg', () => {
|
|
104
|
-
const target = { src: 'bad' } as any;
|
|
105
|
-
component.baseLocation = 'https://host';
|
|
106
|
-
component.onHumanImgError({ target });
|
|
107
|
-
expect(target.src).toContain('chat_human_avatar.svg');
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('template should expose accessible name on avatar image (bot)', () => {
|
|
111
|
-
spyOn(AvatarComponent.prototype as any, 'checkImageExists').and.stub();
|
|
112
|
-
component.senderID = 'bot_x';
|
|
113
|
-
component.senderFullname = 'Helper Bot';
|
|
114
|
-
fixture.detectChanges();
|
|
115
|
-
const img = (fixture.nativeElement as HTMLElement).querySelector('img');
|
|
116
|
-
expect(img?.getAttribute('alt')).toBeTruthy();
|
|
117
|
-
expect(img?.getAttribute('role')).toBe('img');
|
|
118
|
-
});
|
|
119
27
|
});
|