@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.
Files changed (193) hide show
  1. package/.github/workflows/docker-community-push-latest.yml +13 -23
  2. package/.github/workflows/docker-image-tag-community-tag-push.yml +12 -22
  3. package/CHANGELOG.md +8 -129
  4. package/Dockerfile +5 -4
  5. package/angular.json +3 -21
  6. package/docs/changelog/this-branch.md +0 -36
  7. package/env.sample +2 -3
  8. package/nginx.conf +2 -22
  9. package/package.json +3 -10
  10. package/src/app/app.component.html +2 -2
  11. package/src/app/app.component.scss +14 -25
  12. package/src/app/app.component.spec.ts +6 -21
  13. package/src/app/app.component.ts +9 -10
  14. package/src/app/app.module.ts +0 -13
  15. package/src/app/component/conversation-detail/conversation/conversation.component.html +11 -25
  16. package/src/app/component/conversation-detail/conversation/conversation.component.scss +2 -40
  17. package/src/app/component/conversation-detail/conversation/conversation.component.spec.ts +75 -644
  18. package/src/app/component/conversation-detail/conversation/conversation.component.ts +14 -100
  19. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.html +13 -25
  20. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.spec.ts +5 -123
  21. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.ts +0 -1
  22. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.html +10 -23
  23. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.scss +1 -19
  24. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.spec.ts +149 -242
  25. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.ts +5 -8
  26. package/src/app/component/conversation-detail/conversation-emojii/conversation-emojii.component.spec.ts +3 -53
  27. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.html +96 -200
  28. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.scss +6 -211
  29. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.spec.ts +78 -452
  30. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts +76 -291
  31. package/src/app/component/conversation-detail/conversation-header/conversation-header.component.html +53 -113
  32. package/src/app/component/conversation-detail/conversation-header/conversation-header.component.scss +4 -12
  33. package/src/app/component/conversation-detail/conversation-header/conversation-header.component.spec.ts +29 -274
  34. package/src/app/component/conversation-detail/conversation-internal-frame/conversation-internal-frame.component.html +9 -23
  35. package/src/app/component/conversation-detail/conversation-internal-frame/conversation-internal-frame.component.spec.ts +8 -80
  36. package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.html +23 -29
  37. package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.spec.ts +16 -185
  38. package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.ts +14 -34
  39. package/src/app/component/error-alert/error-alert.component.spec.ts +5 -65
  40. package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.html +7 -16
  41. package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.scss +0 -21
  42. package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.spec.ts +7 -89
  43. package/src/app/component/form/form-builder/form-builder.component.html +1 -1
  44. package/src/app/component/form/form-builder/form-builder.component.spec.ts +21 -163
  45. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.html +4 -8
  46. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.scss +5 -10
  47. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.spec.ts +16 -90
  48. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.ts +0 -26
  49. package/src/app/component/form/inputs/form-label/form-label.component.spec.ts +11 -45
  50. package/src/app/component/form/inputs/form-radio-button/form-radio-button.component.spec.ts +6 -24
  51. package/src/app/component/form/inputs/form-select/form-select.component.spec.ts +5 -14
  52. package/src/app/component/form/inputs/form-text/form-text.component.html +12 -14
  53. package/src/app/component/form/inputs/form-text/form-text.component.scss +1 -11
  54. package/src/app/component/form/inputs/form-text/form-text.component.spec.ts +17 -113
  55. package/src/app/component/form/inputs/form-text/form-text.component.ts +3 -35
  56. package/src/app/component/form/inputs/form-textarea/form-textarea.component.html +11 -13
  57. package/src/app/component/form/inputs/form-textarea/form-textarea.component.scss +5 -6
  58. package/src/app/component/form/inputs/form-textarea/form-textarea.component.spec.ts +13 -149
  59. package/src/app/component/form/inputs/form-textarea/form-textarea.component.ts +0 -26
  60. package/src/app/component/form/prechat-form/prechat-form.component.html +11 -14
  61. package/src/app/component/form/prechat-form/prechat-form.component.spec.ts +10 -102
  62. package/src/app/component/form/prechat-form/prechat-form.component.ts +1 -8
  63. package/src/app/component/home/home.component.html +31 -38
  64. package/src/app/component/home/home.component.scss +2 -4
  65. package/src/app/component/home/home.component.spec.ts +11 -226
  66. package/src/app/component/home-conversations/home-conversations.component.html +26 -30
  67. package/src/app/component/home-conversations/home-conversations.component.scss +0 -3
  68. package/src/app/component/home-conversations/home-conversations.component.spec.ts +36 -212
  69. package/src/app/component/last-message/last-message.component.html +9 -15
  70. package/src/app/component/last-message/last-message.component.scss +2 -16
  71. package/src/app/component/last-message/last-message.component.spec.ts +23 -204
  72. package/src/app/component/last-message/last-message.component.ts +1 -4
  73. package/src/app/component/launcher-button/launcher-button.component.html +13 -8
  74. package/src/app/component/launcher-button/launcher-button.component.spec.ts +8 -104
  75. package/src/app/component/list-all-conversations/list-all-conversations.component.html +17 -12
  76. package/src/app/component/list-all-conversations/list-all-conversations.component.scss +0 -2
  77. package/src/app/component/list-conversations/list-conversations.component.html +22 -22
  78. package/src/app/component/menu-options/menu-options.component.html +20 -30
  79. package/src/app/component/menu-options/menu-options.component.spec.ts +9 -125
  80. package/src/app/component/message/audio/audio.component.html +15 -13
  81. package/src/app/component/message/audio/audio.component.spec.ts +5 -140
  82. package/src/app/component/message/audio/audio.component.ts +5 -1
  83. package/src/app/component/message/avatar/avatar.component.html +2 -2
  84. package/src/app/component/message/avatar/avatar.component.spec.ts +7 -99
  85. package/src/app/component/message/bubble-message/bubble-message.component.html +51 -38
  86. package/src/app/component/message/bubble-message/bubble-message.component.scss +1 -54
  87. package/src/app/component/message/bubble-message/bubble-message.component.spec.ts +57 -154
  88. package/src/app/component/message/bubble-message/bubble-message.component.ts +11 -89
  89. package/src/app/component/message/buttons/action-button/action-button.component.html +4 -3
  90. package/src/app/component/message/buttons/action-button/action-button.component.spec.ts +5 -49
  91. package/src/app/component/message/buttons/link-button/link-button.component.scss +8 -5
  92. package/src/app/component/message/buttons/link-button/link-button.component.spec.ts +5 -50
  93. package/src/app/component/message/buttons/text-button/text-button.component.spec.ts +5 -44
  94. package/src/app/component/message/carousel/carousel.component.html +16 -29
  95. package/src/app/component/message/carousel/carousel.component.scss +8 -20
  96. package/src/app/component/message/carousel/carousel.component.spec.ts +3 -80
  97. package/src/app/component/message/carousel/carousel.component.ts +0 -16
  98. package/src/app/component/message/frame/frame.component.html +4 -9
  99. package/src/app/component/message/frame/frame.component.spec.ts +15 -34
  100. package/src/app/component/message/frame/frame.component.ts +2 -7
  101. package/src/app/component/message/html/html.component.html +1 -1
  102. package/src/app/component/message/html/html.component.scss +1 -1
  103. package/src/app/component/message/html/html.component.spec.ts +7 -24
  104. package/src/app/component/message/image/image.component.html +10 -12
  105. package/src/app/component/message/image/image.component.scss +0 -16
  106. package/src/app/component/message/image/image.component.spec.ts +15 -101
  107. package/src/app/component/message/image/image.component.ts +51 -90
  108. package/src/app/component/message/info-message/info-message.component.spec.ts +14 -26
  109. package/src/app/component/message/like-unlike/like-unlike.component.html +9 -7
  110. package/src/app/component/message/like-unlike/like-unlike.component.spec.ts +3 -31
  111. package/src/app/component/message/return-receipt/return-receipt.component.spec.ts +17 -38
  112. package/src/app/component/message/text/text.component.html +3 -3
  113. package/src/app/component/message/text/text.component.scss +86 -80
  114. package/src/app/component/message/text/text.component.spec.ts +13 -106
  115. package/src/app/component/message-attachment/message-attachment.component.spec.ts +13 -134
  116. package/src/app/component/selection-department/selection-department.component.html +23 -21
  117. package/src/app/component/selection-department/selection-department.component.spec.ts +14 -159
  118. package/src/app/component/selection-department/selection-department.component.ts +1 -8
  119. package/src/app/component/send-button/send-button.component.html +13 -5
  120. package/src/app/component/send-button/send-button.component.spec.ts +2 -2
  121. package/src/app/component/star-rating-widget/star-rating-widget.component.html +81 -51
  122. package/src/app/directives/tooltip.directive.spec.ts +4 -8
  123. package/src/app/modals/confirm-close/confirm-close.component.html +8 -20
  124. package/src/app/modals/confirm-close/confirm-close.component.scss +0 -3
  125. package/src/app/modals/confirm-close/confirm-close.component.spec.ts +4 -13
  126. package/src/app/modals/confirm-close/confirm-close.component.ts +1 -8
  127. package/src/app/pipe/html-entites-encode.pipe.spec.ts +2 -35
  128. package/src/app/pipe/marked.pipe.spec.ts +2 -38
  129. package/src/app/pipe/marked.pipe.ts +41 -51
  130. package/src/app/providers/app-config.service.ts +2 -4
  131. package/src/app/providers/brand.service.spec.ts +2 -23
  132. package/src/app/providers/brand.service.ts +1 -1
  133. package/src/app/providers/global-settings.service.spec.ts +14 -1009
  134. package/src/app/providers/global-settings.service.ts +2 -82
  135. package/src/app/providers/translator.service.ts +6 -26
  136. package/src/app/sass/_variables.scss +0 -3
  137. package/src/app/sass/animations.scss +1 -19
  138. package/src/app/utils/globals.ts +1 -21
  139. package/src/app/utils/utils-resources.ts +1 -1
  140. package/src/assets/i18n/en.json +99 -106
  141. package/src/assets/i18n/es.json +100 -107
  142. package/src/assets/i18n/fr.json +100 -107
  143. package/src/assets/i18n/it.json +98 -107
  144. package/src/assets/twp/index-dev.html +0 -18
  145. package/src/chat21-core/models/message.ts +1 -2
  146. package/src/chat21-core/providers/firebase/firebase-conversation-handler.ts +2 -3
  147. package/src/chat21-core/providers/mqtt/mqtt-conversation-handler.ts +0 -12
  148. package/src/chat21-core/providers/scripts/script.service.spec.ts +2 -12
  149. package/src/chat21-core/providers/tiledesk/tiledesk-requests.service.ts +1 -1
  150. package/src/chat21-core/utils/utils-message.ts +0 -7
  151. package/src/chat21-core/utils/utils.ts +2 -5
  152. package/src/widget-config-template.json +1 -4
  153. package/src/widget-config.json +1 -4
  154. package/tsconfig.json +0 -5
  155. package/.angular-mcp-cache/package.json +0 -1
  156. package/.cursor/angular18-accessibility-auditor-skill.md +0 -442
  157. package/.cursor/mcp.json +0 -15
  158. package/.github/workflows/build.yml +0 -22
  159. package/.github/workflows/playwright.yml +0 -27
  160. package/mocks/voice-websocket-mock/server.cjs +0 -245
  161. package/playwright.config.ts +0 -41
  162. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.html +0 -46
  163. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.scss +0 -83
  164. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.ts +0 -192
  165. package/src/app/component/form/prechat-form-test-mock.ts +0 -35
  166. package/src/app/component/message/audio-sync/audio-sync.component.html +0 -18
  167. package/src/app/component/message/audio-sync/audio-sync.component.scss +0 -65
  168. package/src/app/component/message/audio-sync/audio-sync.component.spec.ts +0 -103
  169. package/src/app/component/message/audio-sync/audio-sync.component.ts +0 -643
  170. package/src/app/providers/tts-audio-playback-coordinator.service.spec.ts +0 -117
  171. package/src/app/providers/tts-audio-playback-coordinator.service.ts +0 -109
  172. package/src/app/providers/voice/STT&TTS/openai-voice.config.ts +0 -12
  173. package/src/app/providers/voice/STT&TTS/openai-voice.provider.ts +0 -171
  174. package/src/app/providers/voice/STT&TTS/speech-provider.abstract.ts +0 -39
  175. package/src/app/providers/voice/audio.types.ts +0 -40
  176. package/src/app/providers/voice/vad.service.spec.ts +0 -28
  177. package/src/app/providers/voice/vad.service.ts +0 -70
  178. package/src/app/providers/voice/voice-streaming.service.spec.ts +0 -23
  179. package/src/app/providers/voice/voice-streaming.service.ts +0 -702
  180. package/src/app/providers/voice/voice-streaming.types.ts +0 -112
  181. package/src/app/providers/voice/voice.service.spec.ts +0 -227
  182. package/src/app/providers/voice/voice.service.ts +0 -973
  183. package/src/app/shims/onnxruntime-web-wasm.ts +0 -4
  184. package/src/assets/onnx/ort-wasm-simd-threaded.mjs +0 -59
  185. package/src/assets/onnx/ort-wasm-simd-threaded.wasm +0 -0
  186. package/src/assets/sounds/keyboard.mp3 +0 -0
  187. package/src/assets/twp/tiledesk_widget_files/widget-css-override-example.css +0 -14
  188. package/src/assets/vad/silero_vad_legacy.onnx +0 -0
  189. package/src/assets/vad/vad.worklet.bundle.min.js +0 -1
  190. package/src/chat21-core/providers/chat-manager.spec.ts +0 -72
  191. package/tests/widget-form-rich.spec.ts +0 -67
  192. package/tests/widget-index-dev-settings.spec.ts +0 -52
  193. package/tests/widget-twp-iframe.spec.ts +0 -39
@@ -1,185 +1,43 @@
1
- import { CommonModule } from '@angular/common';
2
- import { SimpleChange } from '@angular/core';
1
+ import { TranslateModule, TranslateService } from '@ngx-translate/core';
2
+ import { CustomTranslateService } from './../../../../chat21-core/providers/custom-translate.service';
3
3
  import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
4
- import { ReactiveFormsModule, FormBuilder } from '@angular/forms';
5
- import { TranslateModule } from '@ngx-translate/core';
6
-
7
- import { FormCheckboxComponent } from '../inputs/form-checkbox/form-checkbox.component';
8
- import { FormLabelComponent } from '../inputs/form-label/form-label.component';
9
- import { FormTextComponent } from '../inputs/form-text/form-text.component';
10
- import { FormTextareaComponent } from '../inputs/form-textarea/form-textarea.component';
11
- import { clonePrechatFormJsonMock } from '../prechat-form-test-mock';
12
- import { CustomTranslateService } from 'src/chat21-core/providers/custom-translate.service';
13
- import { CustomLogger } from 'src/chat21-core/providers/logger/customLogger';
14
- import { LoggerInstance } from 'src/chat21-core/providers/logger/loggerInstance';
4
+ import { ReactiveFormsModule } from '@angular/forms';
15
5
 
16
6
  import { FormBuilderComponent } from './form-builder.component';
7
+ import { LoggerInstance } from 'src/chat21-core/providers/logger/loggerInstance';
8
+ import { NGXLogger } from 'ngx-logger';
9
+ import { CustomLogger } from 'src/chat21-core/providers/logger/customLogger';
17
10
 
18
11
  describe('FormBuilderComponent', () => {
19
12
  let component: FormBuilderComponent;
20
13
  let fixture: ComponentFixture<FormBuilderComponent>;
21
- const ngxlogger = jasmine.createSpyObj('NGXLogger', ['log', 'trace', 'debug', 'warn', 'error']);
22
- const customLogger = new CustomLogger(ngxlogger);
23
- let origLanguages: PropertyDescriptor | undefined;
24
-
25
- beforeEach(() => {
26
- origLanguages = Object.getOwnPropertyDescriptor(navigator, 'languages');
27
- Object.defineProperty(navigator, 'languages', {
28
- configurable: true,
29
- get: () => ['en-US', 'en'],
30
- });
31
- });
32
-
33
- afterEach(() => {
34
- if (origLanguages) {
35
- Object.defineProperty(navigator, 'languages', origLanguages);
36
- } else {
37
- delete (navigator as any).languages;
38
- }
39
- });
40
-
14
+ let ngxlogger: NGXLogger;
15
+ let customLogger = new CustomLogger(ngxlogger)
16
+
41
17
  beforeEach(waitForAsync(() => {
42
18
  TestBed.configureTestingModule({
43
- declarations: [
44
- FormBuilderComponent,
45
- FormTextComponent,
46
- FormTextareaComponent,
47
- FormCheckboxComponent,
48
- FormLabelComponent,
19
+ declarations: [ FormBuilderComponent ],
20
+ imports: [
21
+ ReactiveFormsModule,
22
+ TranslateModule.forRoot(),
49
23
  ],
50
- imports: [CommonModule, ReactiveFormsModule, TranslateModule.forRoot()],
51
24
  providers: [
52
- {
53
- provide: CustomTranslateService,
54
- useValue: {
55
- translateLanguage: (keys: string[]) => {
56
- const m = new Map<string, string>();
57
- keys.forEach((k: string) => m.set(k, k));
58
- return m;
59
- },
60
- },
61
- },
62
- ],
63
- }).compileComponents();
25
+ CustomTranslateService,
26
+ ]
27
+ })
28
+ .compileComponents();
64
29
  }));
65
30
 
66
31
  beforeEach(() => {
67
32
  fixture = TestBed.createComponent(FormBuilderComponent);
68
33
  component = fixture.componentInstance;
69
- LoggerInstance.setInstance(customLogger);
70
- component['logger'] = LoggerInstance.getInstance();
71
- component.stylesMap = new Map([
72
- ['themeColor', '#2a6ac1'],
73
- ['foregroundColor', '#ffffff'],
74
- ]);
75
- component.isOpenPrechatForm = true;
76
- });
77
-
78
- function applyFormArray(arr: any[]) {
79
- component.formArray = arr;
80
- component.ngOnChanges({ formArray: new SimpleChange(null, arr, true) } as any);
34
+ LoggerInstance.setInstance(customLogger)
35
+ let logger = LoggerInstance.getInstance()
36
+ component['logger']= logger
81
37
  fixture.detectChanges();
82
- }
38
+ });
83
39
 
84
40
  it('should create', () => {
85
- applyFormArray([]);
86
41
  expect(component).toBeTruthy();
87
42
  });
88
-
89
- it('with pre-chat mock should translate labels to English and build validators', () => {
90
- const mock = clonePrechatFormJsonMock();
91
- applyFormArray(mock);
92
- expect(mock[0].text).toBe('User fullname');
93
- expect(mock[1].text).toBe('Email');
94
- expect(mock[1].errorLabel).toBe('Invalid email address');
95
- expect(component.preChatFormGroupCustom.get('userFullname')?.hasError('required')).toBe(true);
96
- expect(component.preChatFormGroupCustom.valid).toBe(false);
97
- component.preChatFormGroupCustom.patchValue({
98
- userFullname: 'John',
99
- userEmail: 'john@example.com',
100
- });
101
- expect(component.preChatFormGroupCustom.valid).toBe(true);
102
- });
103
-
104
- it('onSubmitPreChatForm should emit value when form is valid', () => {
105
- spyOn(component.onSubmitForm, 'emit');
106
- applyFormArray(clonePrechatFormJsonMock());
107
- component.preChatFormGroupCustom.patchValue({
108
- userFullname: 'John',
109
- userEmail: 'john@example.com',
110
- });
111
- component.onSubmitPreChatForm();
112
- expect(component.onSubmitForm.emit).toHaveBeenCalledWith({
113
- userFullname: 'John',
114
- userEmail: 'john@example.com',
115
- });
116
- });
117
-
118
- it('onSubmitPreChatForm should not emit when invalid but set submitted', () => {
119
- spyOn(component.onSubmitForm, 'emit');
120
- applyFormArray(clonePrechatFormJsonMock());
121
- component.onSubmitPreChatForm();
122
- expect(component.onSubmitForm.emit).not.toHaveBeenCalled();
123
- expect(component.submitted).toBe(true);
124
- });
125
-
126
- it('onEnterButtonPressed should delegate to submit', () => {
127
- spyOn(component, 'onSubmitPreChatForm');
128
- component.onEnterButtonPressed({});
129
- expect(component.onSubmitPreChatForm).toHaveBeenCalled();
130
- });
131
-
132
- it('unsupported field type should emit onErrorRenderForm and yield empty group', () => {
133
- spyOn(component.onErrorRenderForm, 'emit');
134
- applyFormArray([{ name: 'x', type: 'select', label: { en: 'X' } }]);
135
- expect(component.onErrorRenderForm.emit).toHaveBeenCalled();
136
- expect(Object.keys(component.preChatFormGroupCustom.controls)).toEqual([]);
137
- });
138
-
139
- it('getAcceptLanguage should pick browser language when keys match navigator.languages', () => {
140
- const lang = component.getAcceptLanguage({ default: 'fallback', en: 'English', it: 'Italian' } as any);
141
- expect(lang).toBe('en');
142
- });
143
-
144
- describe('getAcceptLanguage with non-matching browser languages', () => {
145
- beforeEach(() => {
146
- Object.defineProperty(navigator, 'languages', {
147
- configurable: true,
148
- get: () => ['xx-XX'],
149
- });
150
- });
151
- afterEach(() => {
152
- Object.defineProperty(navigator, 'languages', {
153
- configurable: true,
154
- get: () => ['en-US', 'en'],
155
- });
156
- });
157
-
158
- it('should return default when present and no other key matches', () => {
159
- const lang = component.getAcceptLanguage({ default: 'fallback', fr: 'Français' } as any);
160
- expect(lang).toBe('default');
161
- });
162
- });
163
-
164
- it('onResetForm should clear submitted flag and reset controls', () => {
165
- applyFormArray(clonePrechatFormJsonMock());
166
- component.preChatFormGroupCustom.patchValue({
167
- userFullname: 'John',
168
- userEmail: 'john@example.com',
169
- });
170
- component.onSubmitPreChatForm();
171
- expect(component.submitted).toBe(true);
172
- component.onResetForm();
173
- expect(component.submitted).toBe(false);
174
- expect(component.preChatFormGroupCustom.value).toEqual({
175
- userFullname: null,
176
- userEmail: null,
177
- });
178
- });
179
-
180
- it('should render text inputs from mock in the template', () => {
181
- applyFormArray(clonePrechatFormJsonMock());
182
- const inputs = fixture.nativeElement.querySelectorAll('input[type="text"], input[type="email"]');
183
- expect(inputs.length).toBeGreaterThanOrEqual(2);
184
- });
185
43
  });
@@ -1,18 +1,14 @@
1
1
  <form [formGroup]="form">
2
2
  <div class="c21-body-header-checkbox">
3
- <input class="form-check-input errors"
4
- type="checkbox"
5
- [id]="fieldBaseId"
3
+ <input class="form-check-input errors"
4
+ type="checkbox"
6
5
  [formControlName]="element?.name"
7
6
  [checked]="element?.value"
8
7
  [tabIndex]="element?.tabIndex"
9
8
  [ngClass]="{'errors': hasSubmitted && form.controls[element?.name].errors}"
10
- [attr.aria-required]="element?.mandatory ? 'true' : null"
11
- [attr.aria-invalid]="ariaInvalid"
12
- [attr.aria-describedby]="ariaDescribedByErrors"
13
9
  (keydown.enter)="onEnterPressed($event)">
14
- <label class="c21-form-message-field" [attr.for]="fieldBaseId">{{element?.text}}</label>
15
- <div [id]="errorsId" *ngIf="hasSubmitted && form.get(element?.name)?.errors" class="text-danger" role="alert">
10
+ <label class="c21-form-message-field">{{element?.text}}</label>
11
+ <div *ngIf="hasSubmitted && form.get(element?.name).errors" class="text-danger">
16
12
  {{translationErrorLabelMap.get('LABEL_ERROR_FIELD_REQUIRED')}}
17
13
  </div>
18
14
  </div>
@@ -40,7 +40,7 @@ input[type="checkbox"] {
40
40
 
41
41
  // /* hover state */
42
42
  input[type="checkbox"]:not(:checked):hover,
43
- input[type="checkbox"]:not(:checked):focus:not(:focus-visible) {
43
+ input[type="checkbox"]:not(:checked):focus {
44
44
  border-width: 2px;
45
45
 
46
46
  //box-shadow
@@ -59,15 +59,10 @@ input[type="checkbox"]:active:not(:disabled) {
59
59
  }
60
60
 
61
61
  // /* focus state */
62
- input[type="radio"]:focus:not(:focus-visible),
63
- input[type="checkbox"]:focus:not(:focus-visible) {
64
- outline: none;
65
- }
66
-
67
- input[type="radio"]:focus-visible,
68
- input[type="checkbox"]:focus-visible {
69
- outline: 2px solid var(--themeColor, #1a73e8);
70
- outline-offset: 2px;
62
+ input[type="radio"]:focus,
63
+ input[type="checkbox"]:focus {
64
+ outline:none;
65
+
71
66
  }
72
67
 
73
68
  // /* input checked border color */
@@ -1,104 +1,30 @@
1
- import { Component } from '@angular/core';
2
- import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
3
- import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
4
- import { By } from '@angular/platform-browser';
1
+ import { style } from '@angular/animations';
2
+ import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
3
+ import { FormGroupDirective } from '@angular/forms';
5
4
 
6
- import { FormArray } from 'src/chat21-core/models/formArray';
7
5
  import { FormCheckboxComponent } from './form-checkbox.component';
8
6
 
9
- @Component({
10
- template: `
11
- <form [formGroup]="form">
12
- <chat-form-checkbox
13
- [element]="element"
14
- controlName="acceptTerms"
15
- [translationErrorLabelMap]="translationErrorLabelMap"
16
- [stylesMap]="stylesMap"
17
- [hasSubmitted]="hasSubmitted"
18
- ></chat-form-checkbox>
19
- </form>`,
20
- })
21
- class FormCheckboxHostComponent {
22
- form = new FormBuilder().group({
23
- acceptTerms: [false, [Validators.requiredTrue]],
24
- });
25
- element = {
26
- name: 'acceptTerms',
27
- type: 'checkbox',
28
- text: 'I accept the terms',
29
- mandatory: true,
30
- tabIndex: 1500,
31
- } as FormArray;
32
- stylesMap = new Map<string, string>([
33
- ['themeColor', '#2a6ac1'],
34
- ['foregroundColor', '#ffffff'],
35
- ]);
36
- translationErrorLabelMap = new Map<string, string>([
37
- ['LABEL_ERROR_FIELD_REQUIRED', 'You must accept'],
38
- ]);
39
- hasSubmitted = false;
40
- }
41
-
42
7
  describe('FormCheckboxComponent', () => {
43
- let fixture: ComponentFixture<FormCheckboxHostComponent>;
44
- let host: FormCheckboxHostComponent;
45
-
8
+ let component: FormCheckboxComponent;
9
+ let fixture: ComponentFixture<FormCheckboxComponent>;
10
+ let stylesMap = new Map<string, string>();
46
11
  beforeEach(waitForAsync(() => {
47
12
  TestBed.configureTestingModule({
48
- declarations: [FormCheckboxHostComponent, FormCheckboxComponent],
49
- imports: [ReactiveFormsModule],
50
- }).compileComponents();
13
+ declarations: [ FormCheckboxComponent ],
14
+ providers: [ FormGroupDirective ]
15
+ })
16
+ .compileComponents();
51
17
  }));
52
18
 
53
19
  beforeEach(() => {
54
- fixture = TestBed.createComponent(FormCheckboxHostComponent);
55
- host = fixture.componentInstance;
56
- fixture.detectChanges();
57
- });
58
-
59
- it('should create with checkbox wired to label for=id', () => {
60
- const box = fixture.nativeElement.querySelector(
61
- 'input#c21-prechat-acceptTerms',
62
- ) as HTMLInputElement;
63
- expect(box.type).toBe('checkbox');
64
- const label = fixture.nativeElement.querySelector(
65
- 'label[for="c21-prechat-acceptTerms"]',
66
- ) as HTMLLabelElement;
67
- expect(label.textContent?.trim()).toBe('I accept the terms');
68
- });
69
-
70
- it('should expose aria-invalid after submit when still unchecked', () => {
71
- host.hasSubmitted = true;
20
+ fixture = TestBed.createComponent(FormCheckboxComponent);
21
+ component = fixture.componentInstance;
22
+ component.stylesMap = stylesMap.set('themeColor', "#2a6ac1")
23
+ .set('foregroundColor', "#ffffff")
72
24
  fixture.detectChanges();
73
- const box = fixture.nativeElement.querySelector(
74
- 'input#c21-prechat-acceptTerms',
75
- ) as HTMLInputElement;
76
- expect(box.getAttribute('aria-invalid')).toBe('true');
77
- expect(box.getAttribute('aria-describedby')).toBe('c21-prechat-acceptTerms-errors');
78
- const alert = fixture.nativeElement.querySelector(
79
- '#c21-prechat-acceptTerms-errors',
80
- ) as HTMLElement;
81
- expect(alert.textContent).toContain('You must accept');
82
- });
83
-
84
- it('onEnterPressed should bubble intent to parent form-builder via output', () => {
85
- const inner = fixture.debugElement.query(By.directive(FormCheckboxComponent))
86
- .componentInstance as FormCheckboxComponent;
87
- spyOn(inner.onKeyEnterPressed, 'emit');
88
- inner.onEnterPressed(new KeyboardEvent('keydown', { key: 'Enter' }));
89
- expect(inner.onKeyEnterPressed.emit).toHaveBeenCalled();
90
- });
91
-
92
- it('ariaInvalid should be false before first submit', () => {
93
- const inner = fixture.debugElement.query(By.directive(FormCheckboxComponent))
94
- .componentInstance as FormCheckboxComponent;
95
- expect(inner.ariaInvalid).toBe('false');
96
25
  });
97
26
 
98
- it('fieldBaseId should sanitize special characters in name', () => {
99
- const inner = fixture.debugElement.query(By.directive(FormCheckboxComponent))
100
- .componentInstance as FormCheckboxComponent;
101
- inner.element = { name: 'a/b', text: 'x' } as FormArray;
102
- expect(inner.fieldBaseId).toBe('c21-prechat-a_b');
27
+ it('should create', () => {
28
+ expect(component).toBeTruthy();
103
29
  });
104
30
  });
@@ -17,32 +17,6 @@ export class FormCheckboxComponent implements OnInit {
17
17
  @Output() onKeyEnterPressed = new EventEmitter<any>();
18
18
 
19
19
  form: FormGroup<any>;
20
-
21
- get fieldBaseId(): string {
22
- const raw = this.element?.name || this.controlName || 'field';
23
- return 'c21-prechat-' + String(raw).replace(/[^a-zA-Z0-9_-]/g, '_');
24
- }
25
-
26
- get errorsId(): string {
27
- return this.fieldBaseId + '-errors';
28
- }
29
-
30
- get ariaDescribedByErrors(): string | null {
31
- const name = this.element?.name;
32
- if (!this.hasSubmitted || !this.form?.controls?.[name]?.errors) {
33
- return null;
34
- }
35
- return this.errorsId;
36
- }
37
-
38
- get ariaInvalid(): 'true' | 'false' {
39
- const name = this.element?.name;
40
- if (!this.hasSubmitted || !this.form?.controls?.[name]) {
41
- return 'false';
42
- }
43
- return this.form.controls[name].invalid ? 'true' : 'false';
44
- }
45
-
46
20
  constructor(private rootFormGroup: FormGroupDirective,
47
21
  private elementRef: ElementRef) { }
48
22
 
@@ -1,61 +1,27 @@
1
- import { Component } from '@angular/core';
2
1
  import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
3
- import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
4
- import { By } from '@angular/platform-browser';
2
+ import { FormGroupDirective } from '@angular/forms';
5
3
 
6
- import { FormArray } from 'src/chat21-core/models/formArray';
7
4
  import { FormLabelComponent } from './form-label.component';
8
5
 
9
- @Component({
10
- template: `
11
- <form [formGroup]="form">
12
- <chat-form-label
13
- [element]="element"
14
- controlName="intro"
15
- [hasSubmitted]="hasSubmitted"
16
- ></chat-form-label>
17
- </form>`,
18
- })
19
- class FormLabelHostComponent {
20
- form = new FormBuilder().group({});
21
- hasSubmitted = false;
22
- element = {
23
- name: 'intro',
24
- type: 'static',
25
- text: '<strong>Welcome</strong> to support',
26
- tabIndex: 0,
27
- } as FormArray;
28
- }
29
-
30
6
  describe('FormLabelComponent', () => {
31
- let fixture: ComponentFixture<FormLabelHostComponent>;
7
+ let component: FormLabelComponent;
8
+ let fixture: ComponentFixture<FormLabelComponent>;
32
9
 
33
10
  beforeEach(waitForAsync(() => {
34
11
  TestBed.configureTestingModule({
35
- declarations: [FormLabelHostComponent, FormLabelComponent],
36
- imports: [ReactiveFormsModule],
37
- }).compileComponents();
12
+ declarations: [ FormLabelComponent ],
13
+ providers: [ FormGroupDirective ]
14
+ })
15
+ .compileComponents();
38
16
  }));
39
17
 
40
18
  beforeEach(() => {
41
- fixture = TestBed.createComponent(FormLabelHostComponent);
19
+ fixture = TestBed.createComponent(FormLabelComponent);
20
+ component = fixture.componentInstance;
42
21
  fixture.detectChanges();
43
22
  });
44
23
 
45
- it('should create and bind innerHTML from static field text', () => {
46
- const host = fixture.nativeElement.querySelector('.c21-header-label label') as HTMLElement;
47
- expect(host.innerHTML).toContain('<strong>Welcome</strong>');
48
- });
49
-
50
- it('should assign tabindex from element', () => {
51
- const host = fixture.nativeElement.querySelector('.c21-header-label label') as HTMLElement;
52
- expect(host.getAttribute('tabindex')).toBe('0');
53
- });
54
-
55
- it('should wire FormGroup from parent via FormGroupDirective', () => {
56
- const inner = fixture.componentInstance;
57
- const labelComp = fixture.debugElement.query(By.directive(FormLabelComponent))
58
- .componentInstance as FormLabelComponent;
59
- expect(labelComp.form).toBe(inner.form);
24
+ it('should create', () => {
25
+ expect(component).toBeTruthy();
60
26
  });
61
27
  });
@@ -1,15 +1,16 @@
1
- import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
1
+ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2
2
 
3
3
  import { FormRadioButtonComponent } from './form-radio-button.component';
4
4
 
5
- describe('FormRadioButtonComponent', () => {
5
+ describe('RadioButtonComponent', () => {
6
6
  let component: FormRadioButtonComponent;
7
7
  let fixture: ComponentFixture<FormRadioButtonComponent>;
8
8
 
9
- beforeEach(waitForAsync(() => {
9
+ beforeEach(async(() => {
10
10
  TestBed.configureTestingModule({
11
- declarations: [FormRadioButtonComponent],
12
- }).compileComponents();
11
+ declarations: [ FormRadioButtonComponent ]
12
+ })
13
+ .compileComponents();
13
14
  }));
14
15
 
15
16
  beforeEach(() => {
@@ -21,23 +22,4 @@ describe('FormRadioButtonComponent', () => {
21
22
  it('should create', () => {
22
23
  expect(component).toBeTruthy();
23
24
  });
24
-
25
- it('should render two radio inputs sharing a name for single-choice semantics', () => {
26
- const radios = fixture.nativeElement.querySelectorAll(
27
- 'input[type="radio"]',
28
- ) as NodeListOf<HTMLInputElement>;
29
- expect(radios.length).toBe(2);
30
- expect(radios[0].name).toBe(radios[1].name);
31
- });
32
-
33
- it('should associate each radio with a label via for/id', () => {
34
- const r1 = fixture.nativeElement.querySelector('#flexRadioDefault1') as HTMLInputElement;
35
- const r2 = fixture.nativeElement.querySelector('#flexRadioDefault2') as HTMLInputElement;
36
- const l1 = fixture.nativeElement.querySelector('label[for="flexRadioDefault1"]') as HTMLLabelElement;
37
- const l2 = fixture.nativeElement.querySelector('label[for="flexRadioDefault2"]') as HTMLLabelElement;
38
- expect(l1).toBeTruthy();
39
- expect(l2).toBeTruthy();
40
- expect(r2.checked).toBe(true);
41
- expect(r1.checked).toBe(false);
42
- });
43
25
  });
@@ -1,15 +1,15 @@
1
- import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2
-
1
+ import {ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
3
2
  import { FormSelectComponent } from './form-select.component';
4
3
 
5
- describe('FormSelectComponent', () => {
4
+ describe('SelectComponent', () => {
6
5
  let component: FormSelectComponent;
7
6
  let fixture: ComponentFixture<FormSelectComponent>;
8
7
 
9
8
  beforeEach(waitForAsync(() => {
10
9
  TestBed.configureTestingModule({
11
- declarations: [FormSelectComponent],
12
- }).compileComponents();
10
+ declarations: [ FormSelectComponent ]
11
+ })
12
+ .compileComponents();
13
13
  }));
14
14
 
15
15
  beforeEach(() => {
@@ -21,13 +21,4 @@ describe('FormSelectComponent', () => {
21
21
  it('should create', () => {
22
22
  expect(component).toBeTruthy();
23
23
  });
24
-
25
- it('should render placeholder copy until a real select is implemented', () => {
26
- const p = fixture.nativeElement.querySelector('p') as HTMLParagraphElement;
27
- expect(p.textContent?.trim()).toBe('select works!');
28
- });
29
-
30
- it('ngOnInit should complete without throwing', () => {
31
- expect(() => component.ngOnInit()).not.toThrow();
32
- });
33
24
  });
@@ -1,25 +1,23 @@
1
1
  <form [formGroup]="form">
2
- <div class="form-group label-floating"
2
+ <div class="form-group label-floating"
3
3
  [ngClass]="{'is-empty': !form?.controls[element.name].value}"
4
- [id]="'wrap-' + fieldBaseId" #div_input>
5
- <label class="control-label" [attr.for]="fieldBaseId">
4
+ id="div_input" #div_input>
5
+ <label class="control-label">
6
6
  {{element?.text}}
7
7
  </label>
8
- <input
9
- [id]="fieldBaseId"
10
- [formControlName]="element?.name"
8
+ <input
9
+ [formControlName]="element?.name"
11
10
  [type]="inputType"
12
11
  [tabIndex]="element?.tabIndex"
13
- class="form-control"
12
+ class="form-control"
14
13
  [class.errors]="form?.controls[element?.name].errors && hasSubmitted"
15
- [attr.aria-required]="element?.mandatory ? 'true' : null"
16
- [attr.aria-invalid]="ariaInvalid"
17
- [attr.aria-describedby]="ariaDescribedByErrors"
18
- (keydown.enter)="onEnterPressed($event)"
14
+ (keydown.enter)="onEnterPressed($event)"
19
15
  (focus)="onFocus()" (focusout)="onFocusOut()">
20
16
  </div>
21
- <div [id]="errorsId" *ngIf="hasSubmitted && form?.controls[element?.name]?.errors" class="text-danger" role="alert">
22
- <span *ngIf="form?.controls[element?.name].hasError('required')">{{translationErrorLabelMap?.get('LABEL_ERROR_FIELD_REQUIRED')}}</span>
23
- <span *ngIf="form?.controls[element?.name].hasError('pattern') && element?.errorLabel">{{element?.errorLabel}}</span>
17
+ <div *ngIf="hasSubmitted && form?.controls[element?.name].errors && form?.controls[element?.name].hasError('required')" class="text-danger">
18
+ {{translationErrorLabelMap?.get('LABEL_ERROR_FIELD_REQUIRED')}}
19
+ </div>
20
+ <div *ngIf="hasSubmitted && form?.controls[element?.name].errors && form?.controls[element?.name].hasError('pattern') && element?.errorLabel" class="text-danger">
21
+ {{element?.errorLabel}}
24
22
  </div>
25
23
  </form>
@@ -41,7 +41,7 @@
41
41
  }
42
42
 
43
43
  /* Remove input border colour */
44
- input:focus:not(:focus-visible),
44
+ input:focus,
45
45
  input:active,
46
46
  input:hover,
47
47
  input:visited {
@@ -49,11 +49,6 @@
49
49
  background-color: #ffffff;
50
50
  outline: none;
51
51
  }
52
-
53
- input:focus-visible {
54
- outline: 2px solid var(--themeColor, #1a73e8);
55
- outline-offset: 2px;
56
- }
57
52
 
58
53
  /* Remove input background colour for Chrome autocomplete */
59
54
  input:-webkit-autofill,
@@ -184,11 +179,6 @@ input {
184
179
  transition-duration: 0.3s;
185
180
  }
186
181
 
187
- .form-group.is-focused .form-control:focus-visible {
188
- outline: 2px solid var(--themeColor, #1a73e8) !important;
189
- outline-offset: 2px;
190
- }
191
-
192
182
  .form-group.is-focused.form-info .form-control {
193
183
  background-image: linear-gradient(#00bcd4, #00bcd4), linear-gradient(#D2D2D2, #D2D2D2);
194
184
  }