3 * Copyright (C) 2009-2024 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 import { screen } from '@testing-library/react';
21 import userEvent from '@testing-library/user-event';
22 import { addGlobalSuccessMessage } from 'design-system/lib';
23 import React from 'react';
24 import { byLabelText, byRole, byTestId, byText } from '~sonar-aligned/helpers/testSelector';
25 import SystemServiceMock from '../../../../../api/mocks/SystemServiceMock';
26 import * as settingsApi from '../../../../../api/settings';
27 import * as api from '../../../../../api/system';
28 import { CurrentUserContext } from '../../../../../app/components/current-user/CurrentUserContext';
29 import { mockEmailConfiguration } from '../../../../../helpers/mocks/system';
30 import { mockCurrentUser } from '../../../../../helpers/testMocks';
31 import { renderComponent } from '../../../../../helpers/testReactTestingUtils';
32 import { Permissions } from '../../../../../types/permissions';
33 import { AuthMethod } from '../../../../../types/system';
34 import EmailNotification from '../EmailNotification';
36 jest.mock('../../../../../api/system');
37 jest.mock('../../../../../api/settings');
39 jest.mock('design-system', () => ({
40 ...jest.requireActual('design-system'),
41 addGlobalSuccessMessage: jest.fn(),
44 const systemHandler = new SystemServiceMock();
48 systemHandler.reset();
52 editSubheading1: byText('email_notification.subheading.1'),
55 selectorBasicAuth: byRole('radio', {
56 name: 'email_notification.form.basic_auth.title email_notification.form.basic_auth.description',
58 selectorOAuthAuth: byRole('radio', {
59 name: 'email_notification.form.oauth_auth.title email_notification.form.oauth_auth.description email_notification.form.oauth_auth.supported recommended email_notification.form.oauth_auth.recommended_reason',
61 host: byRole('textbox', {
62 name: 'email_notification.form.host field_required',
64 port: byRole('spinbutton', {
65 name: 'email_notification.form.port field_required',
67 securityProtocol: byRole('searchbox', {
68 name: 'email_notification.form.security_protocol field_required',
70 fromAddress: byRole('textbox', {
71 name: 'email_notification.form.from_address field_required',
73 fromName: byRole('textbox', {
74 name: 'email_notification.form.from_name field_required',
76 subjectPrefix: byRole('textbox', {
77 name: 'email_notification.form.subject_prefix field_required',
79 username: byRole('textbox', {
80 name: 'email_notification.form.username field_required',
83 // basic authentication
84 basic_password: byLabelText('email_notification.form.basic_password*'),
87 oauth_auth_host: byRole('textbox', {
88 name: 'email_notification.form.oauth_authentication_host field_required',
90 oauth_client_id: byLabelText('email_notification.form.oauth_client_id*'),
91 oauth_client_id_edit: byTestId('email_notification.form.oauth_client_id-edit'),
92 oauth_client_id_reset: byTestId('email_notification.form.oauth_client_id-reset'),
93 oauth_client_secret: byLabelText('email_notification.form.oauth_client_secret*'),
94 oauth_tenant: byRole('textbox', { name: 'email_notification.form.oauth_tenant field_required' }),
96 save: byRole('button', {
97 name: 'email_notification.form.save_configuration',
101 overviewHeading: byText('email_notification.overview.heading'),
102 overview_auth_mode: byTestId('email_notification.overview.authentication_type.value'),
103 overview_username: byTestId('email_notification.form.username.value'),
104 overview_basic_password: byTestId('email_notification.form.basic_password.value'),
105 overview_oauth_auth_host: byTestId('email_notification.form.oauth_authentication_host.value'),
106 overview_oauth_client_id: byTestId('email_notification.form.oauth_client_id.value'),
107 overview_oauth_client_secret: byTestId('email_notification.form.oauth_client_secret.value'),
108 overview_oauth_tenant: byTestId('email_notification.form.oauth_tenant.value'),
109 overview_host: byTestId('email_notification.form.host.value'),
110 overview_port: byTestId('email_notification.form.port.value'),
111 overview_security_protocol: byTestId('email_notification.form.security_protocol.value'),
112 overview_from_address: byTestId('email_notification.form.from_address.value'),
113 overview_from_name: byTestId('email_notification.form.from_name.value'),
114 overview_subject_prefix: byTestId('email_notification.form.subject_prefix.value'),
116 edit: byRole('button', {
121 test_email: byRole('button', { name: 'email_notification.test.create_test_email' }),
122 test_email_title: byRole('heading', { name: 'email_notification.test.modal_title' }),
123 test_email_to_address: byRole('textbox', {
124 name: 'email_notification.test.to_address required',
126 test_email_subject: byRole('textbox', { name: 'email_notification.test.subject' }),
127 test_email_message: byRole('textbox', { name: 'email_notification.test.message required' }),
128 test_email_submit: byRole('button', { name: 'email_notification.test.submit' }),
131 describe('Email Basic Configuration', () => {
132 it('can save the basic configuration', async () => {
133 jest.spyOn(api, 'postEmailConfiguration');
134 const user = userEvent.setup();
135 renderEmailNotifications();
136 expect(await ui.editSubheading1.find()).toBeInTheDocument();
138 expect(ui.save.get()).toBeDisabled();
140 expect(ui.selectorBasicAuth.get()).toBeChecked();
141 expect(ui.username.get()).toHaveValue('');
142 expect(ui.basic_password.get()).toHaveValue('');
143 expect(ui.host.get()).toHaveValue('');
144 expect(ui.port.get()).toHaveValue(587);
145 expect(ui.securityProtocol.get()).toHaveValue('');
146 expect(ui.fromAddress.get()).toHaveValue('');
147 expect(ui.fromName.get()).toHaveValue('SonarQube');
148 expect(ui.subjectPrefix.get()).toHaveValue('[SonarQube]');
150 await user.type(ui.basic_password.get(), 'password');
151 await user.type(ui.host.get(), 'host');
152 await user.clear(ui.port.get());
153 await user.type(ui.port.get(), '1234');
154 await user.click(ui.securityProtocol.get());
155 await user.click(screen.getByText('SSLTLS'));
156 await user.type(ui.fromAddress.get(), 'admin@localhost.com');
157 await user.clear(ui.fromName.get());
158 await user.type(ui.fromName.get(), 'fromName');
159 await user.clear(ui.subjectPrefix.get());
160 await user.type(ui.subjectPrefix.get(), 'prefix');
161 await user.type(ui.username.get(), 'username');
163 expect(ui.selectorBasicAuth.get()).toBeChecked();
164 expect(ui.username.get()).toHaveValue('username');
165 expect(ui.basic_password.get()).toHaveValue('password');
166 expect(ui.host.get()).toHaveValue('host');
167 expect(ui.port.get()).toHaveValue(1234);
168 expect(ui.securityProtocol.get()).toHaveValue('SSLTLS');
169 expect(ui.fromAddress.get()).toHaveValue('admin@localhost.com');
170 expect(ui.fromName.get()).toHaveValue('fromName');
171 expect(ui.subjectPrefix.get()).toHaveValue('prefix');
173 expect(await ui.save.find()).toBeEnabled();
174 await user.click(ui.save.get());
176 expect(api.postEmailConfiguration).toHaveBeenCalledTimes(1);
177 expect(api.postEmailConfiguration).toHaveBeenCalledWith({
179 basicPassword: 'password',
180 fromAddress: 'admin@localhost.com',
181 fromName: 'fromName',
184 securityProtocol: 'SSLTLS',
185 subjectPrefix: 'prefix',
186 username: 'username',
189 expect(addGlobalSuccessMessage).toHaveBeenCalledWith(
190 'email_notification.form.save_configuration.create_success',
193 expect(await ui.overviewHeading.find()).toBeInTheDocument();
195 expect(ui.overview_auth_mode.get()).toHaveTextContent(AuthMethod.Basic);
196 expect(ui.overview_username.get()).toHaveTextContent('username');
197 expect(ui.overview_basic_password.get()).toHaveTextContent(
198 'email_notification.overview.private',
200 expect(ui.overview_host.get()).toHaveTextContent('host');
201 expect(ui.overview_port.get()).toHaveTextContent('1234');
202 expect(ui.overview_security_protocol.get()).toHaveTextContent('SSLTLS');
203 expect(ui.overview_from_address.get()).toHaveTextContent('admin@localhost.com');
204 expect(ui.overview_from_name.get()).toHaveTextContent('fromName');
205 expect(ui.overview_subject_prefix.get()).toHaveTextContent('prefix');
208 it('renders the overview after loading configuration', async () => {
209 systemHandler.addEmailConfiguration(
210 mockEmailConfiguration(AuthMethod.Basic, { id: 'email-1' }),
213 renderEmailNotifications();
215 expect(await ui.overviewHeading.find()).toBeInTheDocument();
216 expect(ui.overview_auth_mode.get()).toHaveTextContent(AuthMethod.Basic);
217 expect(ui.overview_username.get()).toHaveTextContent('username');
218 expect(ui.overview_basic_password.get()).toHaveTextContent(
219 'email_notification.overview.private',
221 expect(ui.overview_host.get()).toHaveTextContent('host');
222 expect(ui.overview_port.get()).toHaveTextContent('port');
223 expect(ui.overview_security_protocol.get()).toHaveTextContent('SSLTLS');
224 expect(ui.overview_from_address.get()).toHaveTextContent('from_address');
225 expect(ui.overview_from_name.get()).toHaveTextContent('from_name');
226 expect(ui.overview_subject_prefix.get()).toHaveTextContent('subject_prefix');
229 it('can edit an existing configuration', async () => {
230 systemHandler.addEmailConfiguration(
231 mockEmailConfiguration(AuthMethod.Basic, { id: 'email-1' }),
233 jest.spyOn(api, 'patchEmailConfiguration');
234 const user = userEvent.setup();
235 renderEmailNotifications();
237 expect(await ui.overviewHeading.find()).toBeInTheDocument();
239 await user.click(ui.edit.get());
241 expect(await ui.editSubheading1.find()).toBeInTheDocument();
243 expect(ui.save.get()).toBeDisabled();
244 await user.type(ui.basic_password.get(), 'updated');
245 await user.type(ui.host.get(), '-updated');
246 await user.type(ui.port.get(), '5678');
247 await user.click(ui.securityProtocol.get());
248 await user.click(screen.getByText('STARTTLS'));
249 await user.clear(ui.fromAddress.get());
250 await user.type(ui.fromAddress.get(), 'updated@email.com');
251 await user.type(ui.fromName.get(), '-updated');
252 await user.type(ui.subjectPrefix.get(), '-updated');
253 await user.type(ui.username.get(), '-updated');
255 expect(await ui.save.find()).toBeEnabled();
256 await user.click(ui.save.get());
258 expect(api.patchEmailConfiguration).toHaveBeenCalledTimes(1);
259 expect(api.patchEmailConfiguration).toHaveBeenCalledWith('email-1', {
261 basicPassword: 'updated',
262 fromAddress: 'updated@email.com',
263 fromName: 'from_name-updated',
264 host: 'host-updated',
266 isBasicPasswordSet: true,
268 securityProtocol: 'STARTTLS',
269 subjectPrefix: 'subject_prefix-updated',
270 username: 'username-updated',
273 expect(addGlobalSuccessMessage).toHaveBeenCalledWith(
274 'email_notification.form.save_configuration.update_success',
277 expect(await ui.overviewHeading.find()).toBeInTheDocument();
279 expect(ui.overview_auth_mode.get()).toHaveTextContent(AuthMethod.Basic);
280 expect(ui.overview_username.get()).toHaveTextContent('username-updated');
281 expect(ui.overview_basic_password.get()).toHaveTextContent(
282 'email_notification.overview.private',
284 expect(ui.overview_host.get()).toHaveTextContent('host-updated');
285 expect(ui.overview_port.get()).toHaveTextContent('5678');
286 expect(ui.overview_security_protocol.get()).toHaveTextContent('STARTTLS');
287 expect(ui.overview_from_address.get()).toHaveTextContent('updated@email.com');
288 expect(ui.overview_from_name.get()).toHaveTextContent('from_name-updated');
289 expect(ui.overview_subject_prefix.get()).toHaveTextContent('subject_prefix-updated');
293 describe('Email Oauth Configuration', () => {
294 it('can save the oauth configuration', async () => {
295 jest.spyOn(api, 'postEmailConfiguration');
296 const user = userEvent.setup();
297 renderEmailNotifications();
298 expect(await ui.editSubheading1.find()).toBeInTheDocument();
299 await user.click(ui.selectorOAuthAuth.get());
301 expect(ui.save.get()).toBeDisabled();
303 expect(ui.selectorOAuthAuth.get()).toBeChecked();
304 expect(ui.oauth_auth_host.get()).toHaveValue('');
305 expect(ui.oauth_client_id.get()).toHaveValue('');
306 expect(ui.oauth_client_secret.get()).toHaveValue('');
307 expect(ui.oauth_tenant.get()).toHaveValue('');
308 expect(ui.host.get()).toHaveValue('');
309 expect(ui.port.get()).toHaveValue(587);
310 expect(ui.securityProtocol.get()).toHaveValue('');
311 expect(ui.fromAddress.get()).toHaveValue('');
312 expect(ui.fromName.get()).toHaveValue('SonarQube');
313 expect(ui.subjectPrefix.get()).toHaveValue('[SonarQube]');
315 await user.type(ui.oauth_auth_host.get(), 'oauth_auth_host');
316 await user.type(ui.oauth_client_id.get(), 'oauth_client_id');
317 await user.type(ui.oauth_client_secret.get(), 'oauth_client_secret');
318 await user.type(ui.oauth_tenant.get(), 'oauth_tenant');
319 await user.type(ui.host.get(), 'host');
320 await user.clear(ui.port.get());
321 await user.type(ui.port.get(), '1234');
322 await user.click(ui.securityProtocol.get());
323 await user.click(screen.getByText('SSLTLS'));
324 await user.type(ui.fromAddress.get(), 'admin@localhost.com');
325 await user.clear(ui.fromName.get());
326 await user.type(ui.fromName.get(), 'fromName');
327 await user.clear(ui.subjectPrefix.get());
328 await user.type(ui.subjectPrefix.get(), 'prefix');
329 await user.type(ui.username.get(), 'username');
331 expect(ui.selectorOAuthAuth.get()).toBeChecked();
332 expect(ui.username.get()).toHaveValue('username');
333 expect(ui.oauth_auth_host.get()).toHaveValue('oauth_auth_host');
334 expect(ui.oauth_client_id.get()).toHaveValue('oauth_client_id');
335 expect(ui.oauth_client_secret.get()).toHaveValue('oauth_client_secret');
336 expect(ui.oauth_tenant.get()).toHaveValue('oauth_tenant');
337 expect(ui.host.get()).toHaveValue('host');
338 expect(ui.port.get()).toHaveValue(1234);
339 expect(ui.securityProtocol.get()).toHaveValue('SSLTLS');
340 expect(ui.fromAddress.get()).toHaveValue('admin@localhost.com');
341 expect(ui.fromName.get()).toHaveValue('fromName');
342 expect(ui.subjectPrefix.get()).toHaveValue('prefix');
344 expect(await ui.save.find()).toBeEnabled();
345 await user.click(ui.save.get());
347 expect(api.postEmailConfiguration).toHaveBeenCalledTimes(1);
348 expect(api.postEmailConfiguration).toHaveBeenCalledWith({
351 oauthAuthenticationHost: 'oauth_auth_host',
352 oauthClientId: 'oauth_client_id',
353 oauthClientSecret: 'oauth_client_secret',
354 oauthTenant: 'oauth_tenant',
355 fromAddress: 'admin@localhost.com',
356 fromName: 'fromName',
359 securityProtocol: 'SSLTLS',
360 subjectPrefix: 'prefix',
361 username: 'username',
364 expect(addGlobalSuccessMessage).toHaveBeenCalledWith(
365 'email_notification.form.save_configuration.create_success',
368 expect(await ui.overviewHeading.find()).toBeInTheDocument();
370 expect(ui.overview_auth_mode.get()).toHaveTextContent(AuthMethod.OAuth);
371 expect(ui.overview_oauth_auth_host.get()).toHaveTextContent('oauth_auth_host');
372 expect(ui.overview_oauth_client_id.get()).toHaveTextContent(
373 'email_notification.overview.private',
375 expect(ui.overview_oauth_client_secret.get()).toHaveTextContent(
376 'email_notification.overview.private',
378 expect(ui.overview_oauth_tenant.get()).toHaveTextContent('oauth_tenant');
379 expect(ui.overview_host.get()).toHaveTextContent('host');
380 expect(ui.overview_port.get()).toHaveTextContent('1234');
381 expect(ui.overview_security_protocol.get()).toHaveTextContent('SSLTLS');
382 expect(ui.overview_from_address.get()).toHaveTextContent('admin@localhost.com');
383 expect(ui.overview_from_name.get()).toHaveTextContent('fromName');
384 expect(ui.overview_subject_prefix.get()).toHaveTextContent('prefix');
385 expect(ui.overview_username.get()).toHaveTextContent('username');
388 it('renders the overview after loading configuration', async () => {
389 systemHandler.addEmailConfiguration(
390 mockEmailConfiguration(AuthMethod.OAuth, { id: 'email-2' }),
393 renderEmailNotifications();
395 expect(await ui.overviewHeading.find()).toBeInTheDocument();
396 expect(ui.overview_auth_mode.get()).toHaveTextContent(AuthMethod.OAuth);
397 expect(ui.overview_oauth_auth_host.get()).toHaveTextContent('oauth_auth_host');
398 expect(ui.overview_oauth_client_id.get()).toHaveTextContent(
399 'email_notification.overview.private',
401 expect(ui.overview_oauth_client_secret.get()).toHaveTextContent(
402 'email_notification.overview.private',
404 expect(ui.overview_oauth_tenant.get()).toHaveTextContent('oauth_tenant');
405 expect(ui.overview_host.get()).toHaveTextContent('host');
406 expect(ui.overview_port.get()).toHaveTextContent('port');
407 expect(ui.overview_security_protocol.get()).toHaveTextContent('SSLTLS');
408 expect(ui.overview_from_address.get()).toHaveTextContent('from_address');
409 expect(ui.overview_from_name.get()).toHaveTextContent('from_name');
410 expect(ui.overview_subject_prefix.get()).toHaveTextContent('subject_prefix');
413 it('can edit the configuration', async () => {
414 systemHandler.addEmailConfiguration(
415 mockEmailConfiguration(AuthMethod.OAuth, { id: 'email-1' }),
417 jest.spyOn(api, 'patchEmailConfiguration');
418 const user = userEvent.setup();
419 renderEmailNotifications();
421 expect(await ui.overviewHeading.find()).toBeInTheDocument();
422 await user.click(ui.edit.get());
424 expect(await ui.editSubheading1.find()).toBeInTheDocument();
425 await user.click(ui.selectorOAuthAuth.get());
427 expect(ui.save.get()).toBeDisabled();
429 await user.type(ui.oauth_auth_host.get(), '-updated');
430 await user.click(ui.oauth_client_id_edit.get());
431 await user.type(ui.oauth_client_id.get(), 'updated_id');
432 await user.type(ui.oauth_client_secret.get(), 'updated_secret');
433 await user.type(ui.oauth_tenant.get(), '-updated');
434 await user.type(ui.host.get(), '-updated');
435 await user.type(ui.port.get(), '5678');
436 await user.click(ui.securityProtocol.get());
437 await user.click(screen.getByText('STARTTLS'));
438 await user.clear(ui.fromAddress.get());
439 await user.type(ui.fromAddress.get(), 'updated@email.com');
440 await user.type(ui.fromName.get(), '-updated');
441 await user.type(ui.subjectPrefix.get(), '-updated');
442 await user.type(ui.username.get(), '-updated');
444 expect(await ui.save.find()).toBeEnabled();
445 await user.click(ui.save.get());
447 expect(api.patchEmailConfiguration).toHaveBeenCalledTimes(1);
448 expect(api.patchEmailConfiguration).toHaveBeenCalledWith('email-1', {
450 oauthAuthenticationHost: 'oauth_auth_host-updated',
451 oauthClientId: 'updated_id',
452 oauthClientSecret: 'updated_secret',
453 oauthTenant: 'oauth_tenant-updated',
454 fromAddress: 'updated@email.com',
455 fromName: 'from_name-updated',
456 host: 'host-updated',
458 isOauthClientIdSet: true,
459 isOauthClientSecretSet: true,
461 securityProtocol: 'STARTTLS',
462 subjectPrefix: 'subject_prefix-updated',
463 username: 'username-updated',
466 expect(addGlobalSuccessMessage).toHaveBeenCalledWith(
467 'email_notification.form.save_configuration.update_success',
470 expect(await ui.overviewHeading.find()).toBeInTheDocument();
472 expect(ui.overview_auth_mode.get()).toHaveTextContent(AuthMethod.OAuth);
473 expect(ui.overview_oauth_auth_host.get()).toHaveTextContent('oauth_auth_host');
474 expect(ui.overview_oauth_client_id.get()).toHaveTextContent(
475 'email_notification.overview.private',
477 expect(ui.overview_oauth_client_secret.get()).toHaveTextContent(
478 'email_notification.overview.private',
480 expect(ui.overview_oauth_tenant.get()).toHaveTextContent('oauth_tenant');
481 expect(ui.overview_host.get()).toHaveTextContent('host');
482 expect(ui.overview_port.get()).toHaveTextContent('5678');
483 expect(ui.overview_security_protocol.get()).toHaveTextContent('STARTTLS');
484 expect(ui.overview_from_address.get()).toHaveTextContent('updated@email.com');
485 expect(ui.overview_from_name.get()).toHaveTextContent('from_name-updated');
486 expect(ui.overview_subject_prefix.get()).toHaveTextContent('subject_prefix-updated');
487 expect(ui.overview_username.get()).toHaveTextContent('username-updated');
491 describe('EmailNotification send test email', () => {
492 it('should render the EmailTestModal', async () => {
493 const user = userEvent.setup();
494 systemHandler.addEmailConfiguration(
495 mockEmailConfiguration(AuthMethod.Basic, { id: 'email-1' }),
498 renderEmailNotifications();
499 expect(await ui.overviewHeading.find()).toBeInTheDocument();
501 await user.click(ui.test_email.get());
503 expect(ui.test_email_title.get()).toBeVisible();
504 expect(ui.test_email_to_address.get()).toBeVisible();
505 expect(ui.test_email_to_address.get()).toHaveValue('');
506 expect(ui.test_email_subject.get()).toBeVisible();
507 expect(ui.test_email_subject.get()).toHaveValue('email_notification.test.subject');
508 expect(ui.test_email_message.get()).toBeVisible();
509 expect(ui.test_email_message.get()).toHaveValue('email_notification.test.message_text');
512 it('should be possible to send a test email', async () => {
513 jest.spyOn(settingsApi, 'sendTestEmail');
514 const user = userEvent.setup();
515 systemHandler.addEmailConfiguration(
516 mockEmailConfiguration(AuthMethod.Basic, { id: 'email-1' }),
519 renderEmailNotifications();
520 expect(await ui.overviewHeading.find()).toBeInTheDocument();
522 await user.click(ui.test_email.get());
524 expect(ui.test_email_submit.get()).toBeDisabled();
525 await user.type(ui.test_email_to_address.get(), 'test@test.com');
526 expect(ui.test_email_submit.get()).toBeEnabled();
528 await user.clear(ui.test_email_subject.get());
529 await user.type(ui.test_email_subject.get(), 'Test subject');
530 await user.clear(ui.test_email_message.get());
531 await user.type(ui.test_email_message.get(), 'This is a test message');
533 await user.click(ui.test_email_submit.get());
534 expect(settingsApi.sendTestEmail).toHaveBeenCalledTimes(1);
535 expect(settingsApi.sendTestEmail).toHaveBeenCalledWith(
538 'This is a test message',
543 function renderEmailNotifications() {
544 return renderComponent(
545 <CurrentUserContext.Provider
547 currentUser: mockCurrentUser({
549 permissions: { global: [Permissions.Admin] },
551 updateCurrentUserHomepage: () => {},
552 updateDismissedNotices: () => {},
555 <EmailNotification />
556 </CurrentUserContext.Provider>,