From ac368511b8c8cb1a24d1a4f2338b28e70459c532 Mon Sep 17 00:00:00 2001 From: Syed Ali Ul Hasan Date: Fri, 27 Dec 2024 05:02:02 +0530 Subject: [PATCH] refactored src/components/Advertisements/core/ from jest to vitest (#2950) --- .../AdvertisementEntry.test.tsx | 637 ------------------ ...est.tsx => AdvertisementRegister.spec.tsx} | 127 ++-- 2 files changed, 89 insertions(+), 675 deletions(-) delete mode 100644 src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.test.tsx rename src/components/Advertisements/core/AdvertisementRegister/{AdvertisementRegister.test.tsx => AdvertisementRegister.spec.tsx} (87%) diff --git a/src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.test.tsx b/src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.test.tsx deleted file mode 100644 index dbd6f88cc3..0000000000 --- a/src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.test.tsx +++ /dev/null @@ -1,637 +0,0 @@ -import React from 'react'; -import { render, fireEvent, waitFor, screen } from '@testing-library/react'; -import { - ApolloClient, - ApolloProvider, - InMemoryCache, - ApolloLink, - HttpLink, -} from '@apollo/client'; -import type { NormalizedCacheObject } from '@apollo/client'; -import { BrowserRouter } from 'react-router-dom'; -import AdvertisementEntry from './AdvertisementEntry'; -import AdvertisementRegister from '../AdvertisementRegister/AdvertisementRegister'; -import { Provider } from 'react-redux'; -import { store } from 'state/store'; -import { BACKEND_URL } from 'Constant/constant'; -import i18nForTest from 'utils/i18nForTest'; -import { I18nextProvider } from 'react-i18next'; -import dayjs from 'dayjs'; -import useLocalStorage from 'utils/useLocalstorage'; -import { MockedProvider } from '@apollo/client/testing'; -import { ORGANIZATION_ADVERTISEMENT_LIST } from 'GraphQl/Queries/OrganizationQueries'; -import { DELETE_ADVERTISEMENT_BY_ID } from 'GraphQl/Mutations/mutations'; - -const { getItem } = useLocalStorage(); - -const httpLink = new HttpLink({ - uri: BACKEND_URL, - headers: { - authorization: 'Bearer ' + getItem('token') || '', - }, -}); - -const translations = JSON.parse( - JSON.stringify( - i18nForTest.getDataByLanguage('en')?.translation?.advertisement ?? null, - ), -); - -const client: ApolloClient = new ApolloClient({ - cache: new InMemoryCache(), - link: ApolloLink.from([httpLink]), -}); - -const mockUseMutation = jest.fn(); -jest.mock('@apollo/client', () => { - const originalModule = jest.requireActual('@apollo/client'); - return { - ...originalModule, - useMutation: () => mockUseMutation(), - }; -}); -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: '1' }), -})); - -describe('Testing Advertisement Entry Component', () => { - test('Testing rendering and deleting of advertisement', async () => { - const deleteAdByIdMock = jest.fn(); - mockUseMutation.mockReturnValue([deleteAdByIdMock]); - const { getByTestId, getAllByText } = render( - - - - - - - - - , - ); - - //Testing rendering - expect(getByTestId('AdEntry')).toBeInTheDocument(); - expect(getAllByText('POPUP')[0]).toBeInTheDocument(); - expect(getAllByText('Advert1')[0]).toBeInTheDocument(); - expect(screen.getByTestId('media')).toBeInTheDocument(); - - //Testing successful deletion - fireEvent.click(getByTestId('moreiconbtn')); - fireEvent.click(getByTestId('deletebtn')); - - await waitFor(() => { - expect(screen.getByTestId('delete_title')).toBeInTheDocument(); - expect(screen.getByTestId('delete_body')).toBeInTheDocument(); - }); - - fireEvent.click(getByTestId('delete_yes')); - - await waitFor(() => { - expect(deleteAdByIdMock).toHaveBeenCalledWith({ - variables: { - id: '1', - }, - }); - const deletedMessage = screen.queryByText('Advertisement Deleted'); - expect(deletedMessage).toBeNull(); - }); - - //Testing unsuccessful deletion - deleteAdByIdMock.mockRejectedValueOnce(new Error('Deletion Failed')); - - fireEvent.click(getByTestId('moreiconbtn')); - - fireEvent.click(getByTestId('delete_yes')); - - await waitFor(() => { - expect(deleteAdByIdMock).toHaveBeenCalledWith({ - variables: { - id: '1', - }, - }); - const deletionFailedText = screen.queryByText((content, element) => { - return ( - element?.textContent === 'Deletion Failed' && - element.tagName.toLowerCase() === 'div' - ); - }); - expect(deletionFailedText).toBeNull(); - }); - }); - it('should use default props when none are provided', () => { - render( - , - ): void { - throw new Error('Function not implemented.'); - }} - />, - ); - - //Check if component renders with default ''(empty string) - const elements = screen.getAllByText(''); // This will return an array of matching elements - elements.forEach((element) => expect(element).toBeInTheDocument()); - - // Check that the component renders with default `mediaUrl` (empty string) - const mediaElement = screen.getByTestId('media'); - expect(mediaElement).toHaveAttribute('src', ''); - - // Check that the component renders with default `endDate` - const defaultEndDate = new Date().toDateString(); - expect(screen.getByText(`Ends on ${defaultEndDate}`)).toBeInTheDocument(); - - // Check that the component renders with default `startDate` - const defaultStartDate = new Date().toDateString(); - console.log(screen.getByText); - expect(screen.getByText(`Ends on ${defaultStartDate}`)).toBeInTheDocument(); //fix text "Ends on"? - }); - it('should correctly override default props when values are provided', () => { - const mockName = 'Test Ad'; - const mockType = 'Banner'; - const mockMediaUrl = 'https://example.com/media.png'; - const mockEndDate = new Date(2025, 11, 31); - const mockStartDate = new Date(2024, 0, 1); - const mockOrganizationId = 'org123'; - - const { getByText } = render( - , - ): void { - throw new Error('Function not implemented.'); - }} - />, - ); - - // Check that the component renders with provided values - expect(getByText(mockName)).toBeInTheDocument(); - // Add more checks based on how each prop affects rendering - }); - it('should open and close the dropdown when options button is clicked', () => { - const { getByTestId, queryByText, getAllByText } = render( - - - - - - - - - , - ); - - // Test initial rendering - expect(getByTestId('AdEntry')).toBeInTheDocument(); - expect(getAllByText('POPUP')[0]).toBeInTheDocument(); - expect(getAllByText('Advert1')[0]).toBeInTheDocument(); - - // Test dropdown functionality - const optionsButton = getByTestId('moreiconbtn'); - - // Initially, the dropdown should not be visible - expect(queryByText('Edit')).toBeNull(); - - // Click to open the dropdown - fireEvent.click(optionsButton); - - // After clicking the button, the dropdown should be visible - expect(queryByText('Edit')).toBeInTheDocument(); - - // Click again to close the dropdown - fireEvent.click(optionsButton); - - // After the second click, the dropdown should be hidden again - expect(queryByText('Edit')).toBeNull(); - }); - - test('Updates the advertisement and shows success toast on successful update', async () => { - const updateAdByIdMock = jest.fn().mockResolvedValue({ - data: { - updateAdvertisement: { - advertisement: { - _id: '1', - name: 'Updated Advertisement', - mediaUrl: '', - startDate: dayjs(new Date()).add(1, 'day').format('YYYY-MM-DD'), - endDate: dayjs(new Date()).add(2, 'days').format('YYYY-MM-DD'), - type: 'BANNER', - }, - }, - }, - }); - - mockUseMutation.mockReturnValue([updateAdByIdMock]); - - render( - - - - - - - - - , - ); - - const optionsButton = screen.getByTestId('moreiconbtn'); - fireEvent.click(optionsButton); - fireEvent.click(screen.getByTestId('editBtn')); - - fireEvent.change(screen.getByLabelText('Enter name of Advertisement'), { - target: { value: 'Updated Advertisement' }, - }); - - expect(screen.getByLabelText('Enter name of Advertisement')).toHaveValue( - 'Updated Advertisement', - ); - - fireEvent.change(screen.getByLabelText(translations.Rtype), { - target: { value: 'BANNER' }, - }); - expect(screen.getByLabelText(translations.Rtype)).toHaveValue('BANNER'); - - fireEvent.change(screen.getByLabelText(translations.RstartDate), { - target: { value: dayjs().add(1, 'day').format('YYYY-MM-DD') }, - }); - - fireEvent.change(screen.getByLabelText(translations.RendDate), { - target: { value: dayjs().add(2, 'days').format('YYYY-MM-DD') }, - }); - - fireEvent.click(screen.getByTestId('addonupdate')); - - expect(updateAdByIdMock).toHaveBeenCalledWith({ - variables: { - id: '1', - name: 'Updated Advertisement', - type: 'BANNER', - startDate: dayjs().add(1, 'day').format('YYYY-MM-DD'), - endDate: dayjs().add(2, 'days').format('YYYY-MM-DD'), - }, - }); - }); - - test('Simulating if the mutation doesnt have data variable while updating', async () => { - const updateAdByIdMock = jest.fn().mockResolvedValue({ - updateAdvertisement: { - _id: '1', - name: 'Updated Advertisement', - type: 'BANNER', - }, - }); - - mockUseMutation.mockReturnValue([updateAdByIdMock]); - - render( - - - - - - - - - , - ); - - const optionsButton = screen.getByTestId('moreiconbtn'); - fireEvent.click(optionsButton); - fireEvent.click(screen.getByTestId('editBtn')); - - fireEvent.change(screen.getByLabelText('Enter name of Advertisement'), { - target: { value: 'Updated Advertisement' }, - }); - - expect(screen.getByLabelText('Enter name of Advertisement')).toHaveValue( - 'Updated Advertisement', - ); - - fireEvent.change(screen.getByLabelText(translations.Rtype), { - target: { value: 'BANNER' }, - }); - expect(screen.getByLabelText(translations.Rtype)).toHaveValue('BANNER'); - - fireEvent.click(screen.getByTestId('addonupdate')); - - expect(updateAdByIdMock).toHaveBeenCalledWith({ - variables: { - id: '1', - name: 'Updated Advertisement', - type: 'BANNER', - }, - }); - }); - - test('Simulating if the mutation does not have data variable while registering', async () => { - Object.defineProperty(window, 'location', { - configurable: true, - value: { - reload: jest.fn(), - href: 'https://example.com/page/id=1', - }, - }); - const createAdByIdMock = jest.fn().mockResolvedValue({ - data1: { - createAdvertisement: { - _id: '1', - }, - }, - }); - - mockUseMutation.mockReturnValue([createAdByIdMock]); - - render( - - - - - { - - } - - - - , - ); - - fireEvent.click(screen.getByTestId('createAdvertisement')); - - fireEvent.change(screen.getByLabelText('Enter name of Advertisement'), { - target: { value: 'Updated Advertisement' }, - }); - - expect(screen.getByLabelText('Enter name of Advertisement')).toHaveValue( - 'Updated Advertisement', - ); - - fireEvent.change(screen.getByLabelText(translations.Rtype), { - target: { value: 'BANNER' }, - }); - expect(screen.getByLabelText(translations.Rtype)).toHaveValue('BANNER'); - - fireEvent.change(screen.getByLabelText(translations.RstartDate), { - target: { value: '2023-01-01' }, - }); - expect(screen.getByLabelText(translations.RstartDate)).toHaveValue( - '2023-01-01', - ); - - fireEvent.change(screen.getByLabelText(translations.RendDate), { - target: { value: '2023-02-01' }, - }); - expect(screen.getByLabelText(translations.RendDate)).toHaveValue( - '2023-02-01', - ); - - fireEvent.click(screen.getByTestId('addonregister')); - - expect(createAdByIdMock).toHaveBeenCalledWith({ - variables: { - organizationId: '1', - name: 'Updated Advertisement', - file: '', - type: 'BANNER', - startDate: dayjs(new Date('2023-01-01')).format('YYYY-MM-DD'), - endDate: dayjs(new Date('2023-02-01')).format('YYYY-MM-DD'), - }, - }); - }); - test('delet advertisement', async () => { - const deleteAdByIdMock = jest.fn(); - const mocks = [ - { - request: { - query: ORGANIZATION_ADVERTISEMENT_LIST, - variables: { - id: '1', - first: 2, - after: null, - last: null, - before: null, - }, - }, - result: { - data: { - organizations: [ - { - _id: '1', - advertisements: { - edges: [ - { - node: { - _id: '1', - name: 'Advertisement1', - startDate: '2022-01-01', - endDate: '2023-01-01', - mediaUrl: 'http://example1.com', - }, - cursor: 'cursor1', - }, - { - node: { - _id: '2', - name: 'Advertisement2', - startDate: '2024-02-01', - endDate: '2025-02-01', - mediaUrl: 'http://example2.com', - }, - cursor: 'cursor2', - }, - { - node: { - _id: '3', - name: 'Advertisement1', - startDate: '2022-01-01', - endDate: '2023-01-01', - mediaUrl: 'http://example1.com', - }, - cursor: 'cursor3', - }, - { - node: { - _id: '4', - name: 'Advertisement2', - startDate: '2024-02-01', - endDate: '2025-02-01', - mediaUrl: 'http://example2.com', - }, - cursor: 'cursor4', - }, - { - node: { - _id: '5', - name: 'Advertisement1', - startDate: '2022-01-01', - endDate: '2023-01-01', - mediaUrl: 'http://example1.com', - }, - cursor: 'cursor5', - }, - { - node: { - _id: '6', - name: 'Advertisement2', - startDate: '2024-02-01', - endDate: '2025-02-01', - mediaUrl: 'http://example2.com', - }, - cursor: 'cursor6', - }, - ], - pageInfo: { - startCursor: 'cursor1', - endCursor: 'cursor6', - hasNextPage: true, - hasPreviousPage: false, - }, - totalCount: 8, - }, - }, - ], - }, - }, - }, - { - request: { - query: DELETE_ADVERTISEMENT_BY_ID, - variables: { - id: '1', - }, - }, - result: { - data: { - advertisements: { - _id: null, - }, - }, - }, - }, - ]; - mockUseMutation.mockReturnValue([deleteAdByIdMock]); - const { getByTestId, getAllByText } = render( - - - - - - - - - - - , - ); - - //Testing rendering - expect(getByTestId('AdEntry')).toBeInTheDocument(); - expect(getAllByText('POPUP')[0]).toBeInTheDocument(); - expect(getAllByText('Advert1')[0]).toBeInTheDocument(); - expect(screen.getByTestId('media')).toBeInTheDocument(); - - //Testing successful deletion - fireEvent.click(getByTestId('moreiconbtn')); - fireEvent.click(getByTestId('deletebtn')); - - await waitFor(() => { - expect(screen.getByTestId('delete_title')).toBeInTheDocument(); - expect(screen.getByTestId('delete_body')).toBeInTheDocument(); - }); - - fireEvent.click(getByTestId('delete_yes')); - - await waitFor(() => { - expect(deleteAdByIdMock).toHaveBeenCalledWith({ - variables: { - id: '1', - }, - }); - const deletedMessage = screen.queryByText('Advertisement Deleted'); - expect(deletedMessage).toBeNull(); - }); - - //Testing unsuccessful deletion - deleteAdByIdMock.mockRejectedValueOnce(new Error('Deletion Failed')); - - fireEvent.click(getByTestId('moreiconbtn')); - - fireEvent.click(getByTestId('delete_yes')); - - await waitFor(() => { - expect(deleteAdByIdMock).toHaveBeenCalledWith({ - variables: { - id: '1', - }, - }); - const deletionFailedText = screen.queryByText((content, element) => { - return ( - element?.textContent === 'Deletion Failed' && - element.tagName.toLowerCase() === 'div' - ); - }); - expect(deletionFailedText).toBeNull(); - }); - }); -}); diff --git a/src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.test.tsx b/src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.spec.tsx similarity index 87% rename from src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.test.tsx rename to src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.spec.tsx index 0646a94819..80ef45226f 100644 --- a/src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.test.tsx +++ b/src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.spec.tsx @@ -25,14 +25,24 @@ import { StaticMockLink } from 'utils/StaticMockLink'; import userEvent from '@testing-library/user-event'; import useLocalStorage from 'utils/useLocalstorage'; import { ORGANIZATION_ADVERTISEMENT_LIST } from 'GraphQl/Queries/Queries'; +import { vi } from 'vitest'; const { getItem } = useLocalStorage(); -jest.mock('react-toastify', () => ({ +vi.mock('react-router-dom', async () => { + const actual = await vi.importActual('react-router-dom'); + return { + ...actual, + useParams: () => ({ orgId: '1' }), + useNavigate: vi.fn(), + }; +}); + +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - warn: jest.fn(), - error: jest.fn(), + success: vi.fn(), + warn: vi.fn(), + error: vi.fn(), }, })); @@ -120,11 +130,22 @@ const httpLink = new HttpLink({ }, }); +vi.mock('utils/useLocalstorage', () => ({ + default: () => ({ + getItem: vi.fn().mockReturnValue('token'), + }), +})); + const client: ApolloClient = new ApolloClient({ cache: new InMemoryCache(), link: ApolloLink.from([httpLink]), }); +// const client: ApolloClient = new ApolloClient({ +// cache: new InMemoryCache(), +// link, +// }); + const translations = { ...JSON.parse( JSON.stringify( @@ -135,10 +156,6 @@ const translations = { ...JSON.parse(JSON.stringify(i18n.getDataByLanguage('en')?.errors ?? {})), }; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: '1' }), -})); describe('Testing Advertisement Register Component', () => { test('AdvertismentRegister component loads correctly in register mode', async () => { const { getByText } = render( @@ -153,7 +170,7 @@ describe('Testing Advertisement Register Component', () => { nameEdit="Advert1" orgIdEdit="1" advertisementMediaEdit="" - setAfter={jest.fn()} + setAfter={vi.fn()} /> @@ -166,8 +183,7 @@ describe('Testing Advertisement Register Component', () => { }); test('create advertisement', async () => { - jest.useFakeTimers(); - const setTimeoutSpy = jest.spyOn(global, 'setTimeout'); + const setTimeoutSpy = vi.spyOn(global, 'setTimeout'); await act(async () => { render( @@ -182,7 +198,7 @@ describe('Testing Advertisement Register Component', () => { nameEdit="Ad1" orgIdEdit="1" advertisementMediaEdit="" - setAfter={jest.fn()} + setAfter={vi.fn()} /> @@ -254,12 +270,11 @@ describe('Testing Advertisement Register Component', () => { ); expect(setTimeoutSpy).toHaveBeenCalled(); }); - jest.useRealTimers(); + vi.useRealTimers(); }); test('update advertisement', async () => { - jest.useFakeTimers(); - const setTimeoutSpy = jest.spyOn(global, 'setTimeout'); + const setTimeoutSpy = vi.spyOn(global, 'setTimeout'); await act(async () => { render( @@ -274,7 +289,7 @@ describe('Testing Advertisement Register Component', () => { nameEdit="Ad1" orgIdEdit="1" advertisementMediaEdit="" - setAfter={jest.fn()} + setAfter={vi.fn()} formStatus="edit" /> @@ -346,13 +361,12 @@ describe('Testing Advertisement Register Component', () => { expect(setTimeoutSpy).toHaveBeenCalled(); }); - jest.useRealTimers(); + vi.useRealTimers(); }); test('Logs error to the console and shows error toast when advertisement creation fails', async () => { - jest.useFakeTimers(); - const setTimeoutSpy = jest.spyOn(global, 'setTimeout'); - const toastErrorSpy = jest.spyOn(toast, 'error'); + const setTimeoutSpy = vi.spyOn(global, 'setTimeout'); + const toastErrorSpy = vi.spyOn(toast, 'error'); await act(async () => { render( @@ -367,7 +381,7 @@ describe('Testing Advertisement Register Component', () => { nameEdit="Ad1" orgIdEdit="1" advertisementMediaEdit="" - setAfter={jest.fn()} + setAfter={vi.fn()} /> @@ -397,12 +411,11 @@ describe('Testing Advertisement Register Component', () => { }); expect(setTimeoutSpy).toHaveBeenCalled(); - jest.useRealTimers(); + vi.useRealTimers(); }); test('Throws error when the end date is less than the start date', async () => { - jest.useFakeTimers(); - const setTimeoutSpy = jest.spyOn(global, 'setTimeout'); + const setTimeoutSpy = vi.spyOn(global, 'setTimeout'); const { getByText, queryByText, getByLabelText } = render( @@ -415,7 +428,7 @@ describe('Testing Advertisement Register Component', () => { nameEdit="Ad1" orgIdEdit="1" advertisementMediaEdit="" - setAfter={jest.fn()} + setAfter={vi.fn()} /> @@ -469,11 +482,10 @@ describe('Testing Advertisement Register Component', () => { 'End Date should be greater than or equal to Start Date', ); expect(setTimeoutSpy).toHaveBeenCalled(); - jest.useRealTimers(); + vi.useRealTimers(); }); test('AdvertismentRegister component loads correctly in edit mode', async () => { - jest.useFakeTimers(); render( @@ -487,7 +499,7 @@ describe('Testing Advertisement Register Component', () => { orgIdEdit="1" advertisementMediaEdit="google.com" formStatus="edit" - setAfter={jest.fn()} + setAfter={vi.fn()} /> @@ -497,11 +509,10 @@ describe('Testing Advertisement Register Component', () => { await waitFor(() => { expect(screen.getByTestId('editBtn')).toBeInTheDocument(); }); - jest.useRealTimers(); + vi.useRealTimers(); }); test('Opens and closes modals on button click', async () => { - jest.useFakeTimers(); const { getByText, queryByText } = render( @@ -514,7 +525,7 @@ describe('Testing Advertisement Register Component', () => { nameEdit="Advert1" orgIdEdit="1" advertisementMediaEdit="" - setAfter={jest.fn()} + setAfter={vi.fn()} /> @@ -529,11 +540,10 @@ describe('Testing Advertisement Register Component', () => { await waitFor(() => { expect(queryByText(translations.close)).not.toBeInTheDocument(); }); - jest.useRealTimers(); + vi.useRealTimers(); }); test('Throws error when the end date is less than the start date while editing the advertisement', async () => { - jest.useFakeTimers(); const { getByText, getByLabelText, queryByText } = render( @@ -548,7 +558,7 @@ describe('Testing Advertisement Register Component', () => { nameEdit="Advert1" orgIdEdit="1" advertisementMediaEdit="google.com" - setAfter={jest.fn()} + setAfter={vi.fn()} /> } @@ -596,11 +606,10 @@ describe('Testing Advertisement Register Component', () => { 'End Date should be greater than or equal to Start Date', ); }); - jest.useRealTimers(); + vi.useRealTimers(); }); test('Media preview renders correctly', async () => { - jest.useFakeTimers(); render( @@ -613,7 +622,7 @@ describe('Testing Advertisement Register Component', () => { nameEdit="Advert1" orgIdEdit="1" advertisementMediaEdit="test.mp4" - setAfter={jest.fn()} + setAfter={vi.fn()} /> @@ -637,5 +646,47 @@ describe('Testing Advertisement Register Component', () => { fireEvent.click(closeButton); expect(mediaPreview).not.toBeInTheDocument(); }); - jest.useRealTimers(); + vi.useRealTimers(); + + test('shows success toast on successful update', async () => { + const setAfterMock = vi.fn(); + + render( + + + + + + + + + , + ); + + const editButton = screen.getByTestId('editBtn'); + fireEvent.click(editButton); + + const nameInput = screen.getByLabelText('Enter name of Advertisement'); + fireEvent.change(nameInput, { target: { value: 'Updated Ad' } }); + + const saveButton = screen.getByTestId('addonupdate'); + fireEvent.click(saveButton); + + await waitFor(() => { + // Verify success toast was shown + expect(toast.success).toHaveBeenCalledWith( + 'Advertisement created successfully.', + ); + }); + }); });