diff --git a/docs/docs/auto-docs/screens/OrgPost/OrgPost/functions/default.md b/docs/docs/auto-docs/screens/OrgPost/OrgPost/functions/default.md index 62c0eb7190..9b90cc010b 100644 --- a/docs/docs/auto-docs/screens/OrgPost/OrgPost/functions/default.md +++ b/docs/docs/auto-docs/screens/OrgPost/OrgPost/functions/default.md @@ -6,7 +6,7 @@ > **default**(): `JSX.Element` -Defined in: [src/screens/OrgPost/OrgPost.tsx:70](https://github.com/PalisadoesFoundation/talawa-admin/blob/main/src/screens/OrgPost/OrgPost.tsx#L70) +Defined in: [src/screens/OrgPost/OrgPost.tsx:71](https://github.com/PalisadoesFoundation/talawa-admin/blob/main/src/screens/OrgPost/OrgPost.tsx#L71) This function is used to display the posts of the organization. It displays the posts in a card format. It also provides the functionality to create a new post. The user can also sort the posts based on the date of creation. diff --git a/src/screens/OrgPost/OrgPost.spec.tsx b/src/screens/OrgPost/OrgPost.spec.tsx index cfe2d8c9bf..60a2734ac3 100644 --- a/src/screens/OrgPost/OrgPost.spec.tsx +++ b/src/screens/OrgPost/OrgPost.spec.tsx @@ -1,5 +1,5 @@ import { MockedProvider } from '@apollo/react-testing'; -import { fireEvent, render, screen } from '@testing-library/react'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; @@ -12,7 +12,7 @@ import { store } from 'state/store'; import { StaticMockLink } from 'utils/StaticMockLink'; import i18nForTest from 'utils/i18nForTest'; import OrgPost from './OrgPost'; -import { vi } from 'vitest'; +import { describe, it, expect, beforeEach, vi } from 'vitest'; const MOCKS = [ { request: { @@ -47,9 +47,9 @@ const MOCKS = [ }, likeCount: 0, commentCount: 0, - comments: [], pinned: true, likedBy: [], + comments: [], }, cursor: '6411e53835d7ba2344a78e21', }, @@ -178,7 +178,125 @@ async function wait(ms = 500): Promise { }); } +const initialMock = { + request: { + query: ORGANIZATION_POST_LIST, + variables: { + id: undefined, + after: null, + before: null, + first: 6, + last: null, + }, + }, + result: { + data: { + organizations: [ + { + posts: { + edges: [], + pageInfo: { + startCursor: 'startCursor1', + endCursor: 'endCursor1', + hasNextPage: true, + hasPreviousPage: false, + }, + totalCount: 10, + }, + }, + ], + }, + }, +}; + +const nextPageMock = { + request: { + query: ORGANIZATION_POST_LIST, + variables: { + id: undefined, + after: 'endCursor1', + before: null, + first: 6, + last: null, + }, + }, + result: { + data: { + organizations: [ + { + posts: { + edges: [], + pageInfo: { + startCursor: 'startCursor2', + endCursor: 'endCursor2', + hasNextPage: false, + hasPreviousPage: true, + }, + totalCount: 10, + }, + }, + ], + }, + }, +}; + +const prevPageMock = { + request: { + query: ORGANIZATION_POST_LIST, + variables: { + id: undefined, + after: null, + before: 'startCursor2', + first: null, + last: 6, + }, + }, + result: { + data: { + organizations: [ + { + posts: { + edges: [], + pageInfo: { + startCursor: 'startCursor1', + endCursor: 'endCursor1', + hasNextPage: true, + hasPreviousPage: false, + }, + totalCount: 10, + }, + }, + ], + }, + }, +}; + +const successMock = { + request: { + query: CREATE_POST_MUTATION, + variables: { + title: 'Test Post', + text: 'Test Content', + file: '', + pinned: false, + }, + }, + result: { + data: { + createPost: { + _id: '123', + title: 'Test Post', + text: 'Test Content', + }, + }, + }, +}; + describe('Organisation Post Page', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + const formData = { posttitle: 'dummy post', postinfo: 'This is a dummy post', @@ -186,7 +304,121 @@ describe('Organisation Post Page', () => { postVideo: new File(['hello'], 'hello.mp4', { type: 'video/mp4' }), }; - test('correct mock data should be queried', async () => { + it('handleAddMediaChange: uploading a file and verifying the preview', async () => { + render( + + + + + + + + + , + ); + + await wait(); + + userEvent.click(screen.getByTestId('createPostModalBtn')); + + const fileInput = screen.getByTestId('addMediaField'); + userEvent.upload(fileInput, formData.postImage); + + const imagePreview = await screen.findByAltText('Post Image Preview'); + expect(imagePreview).toBeInTheDocument(); + + const closeButton = screen.getByTestId('mediaCloseButton'); + fireEvent.click(closeButton); + expect(imagePreview).not.toBeInTheDocument(); + }); + + it('handleSearch: searching by title and text', async () => { + render( + + + + + + + + + , + ); + + await wait(); + + const searchInput = screen.getByPlaceholderText(/Search By/i); + userEvent.type(searchInput, 'postone{enter}'); + expect(searchInput).toHaveValue('postone'); + + const sortDropdown = screen.getByTestId('sort'); + userEvent.click(sortDropdown); + }); + + it('createPost: creating a post with and without media, and error handling', async () => { + render( + + + + + + + + + , + ); + + await wait(); + + userEvent.click(screen.getByTestId('createPostModalBtn')); + + const postTitleInput = screen.getByTestId('modalTitle'); + fireEvent.change(postTitleInput, { target: { value: 'Test Post' } }); + + const postInfoTextarea = screen.getByTestId('modalinfo'); + fireEvent.change(postInfoTextarea, { + target: { value: 'Test post information' }, + }); + + const createPostBtn = screen.getByTestId('createPostBtn'); + fireEvent.click(createPostBtn); + + await wait(); + + userEvent.click(screen.getByTestId('closeOrganizationModal')); + }); + + it('Modal interactions: opening and closing the modal', async () => { + render( + + + + + + + + + , + ); + + await wait(); + + const createPostModalBtn = screen.getByTestId('createPostModalBtn'); + userEvent.click(createPostModalBtn); + + const modalTitle = screen.getByTestId('modalOrganizationHeader'); + expect(modalTitle).toBeInTheDocument(); + + const closeButton = screen.getByTestId(/modalOrganizationHeader/i); + userEvent.click(closeButton); + + await wait(); + + const closedModalTitle = screen.queryByText(/postDetail/i); + expect(closedModalTitle).not.toBeInTheDocument(); + }); + + it('correct mock data should be queried', async () => { const dataQuery1 = MOCKS[0]?.result?.data?.organizations[0].posts.edges[0]; expect(dataQuery1).toEqual({ @@ -213,7 +445,7 @@ describe('Organisation Post Page', () => { }); }); - test('Testing create post functionality', async () => { + it('Testing create post functionality', async () => { render( @@ -247,7 +479,7 @@ describe('Organisation Post Page', () => { userEvent.click(screen.getByTestId('closeOrganizationModal')); }, 15000); - test('Testing search functionality', async () => { + it('Testing search functionality', async () => { render( @@ -272,9 +504,9 @@ describe('Organisation Post Page', () => { const sortDropdown = screen.getByTestId('sort'); userEvent.click(sortDropdown); }); - test('Testing search text and title toggle', async () => { + + it('Testing search text and title toggle', async () => { await act(async () => { - // Wrap the test code in act render( @@ -315,9 +547,9 @@ describe('Organisation Post Page', () => { expect(searchInput).toHaveAttribute('placeholder', 'Search By Title'); }); - test('Testing search latest and oldest toggle', async () => { + + it('Testing search latest and oldest toggle', async () => { await act(async () => { - // Wrap the test code in act render( @@ -358,11 +590,12 @@ describe('Organisation Post Page', () => { }); expect(searchInput).toBeInTheDocument(); }); - test('After creating a post, the data should be refetched', async () => { + + it('After creating a post, the data should be refetched', async () => { const refetchMock = vi.fn(); render( - + @@ -378,8 +611,6 @@ describe('Organisation Post Page', () => { userEvent.click(screen.getByTestId('createPostModalBtn')); - // Fill in post form fields... - userEvent.click(screen.getByTestId('createPostBtn')); await wait(); @@ -387,7 +618,7 @@ describe('Organisation Post Page', () => { expect(refetchMock).toHaveBeenCalledTimes(0); }); - test('Create post without media', async () => { + it('Create post without media', async () => { render( @@ -416,80 +647,6 @@ describe('Organisation Post Page', () => { fireEvent.click(createPostBtn); }, 15000); - test('Create post and preview', async () => { - render( - - - - - - - - - - , - ); - - await wait(); - userEvent.click(screen.getByTestId('createPostModalBtn')); - - const postTitleInput = screen.getByTestId('modalTitle'); - fireEvent.change(postTitleInput, { target: { value: 'Test Post' } }); - - const postInfoTextarea = screen.getByTestId('modalinfo'); - fireEvent.change(postInfoTextarea, { - target: { value: 'Test post information' }, - }); - - // Simulate uploading an image - const imageFile = new File(['image content'], 'image.png', { - type: 'image/png', - }); - const imageInput = screen.getByTestId('addMediaField'); - userEvent.upload(imageInput, imageFile); - - // Check if the image is displayed - const imagePreview = await screen.findByAltText('Post Image Preview'); - expect(imagePreview).toBeInTheDocument(); - - // Check if the close button for the image works - const closeButton = screen.getByTestId('mediaCloseButton'); - fireEvent.click(closeButton); - - // Check if the image is removed from the preview - expect(imagePreview).not.toBeInTheDocument(); - }, 15000); - - test('Modal opens and closes', async () => { - render( - - - - - - - - - , - ); - - await wait(); - - const createPostModalBtn = screen.getByTestId('createPostModalBtn'); - - userEvent.click(createPostModalBtn); - - const modalTitle = screen.getByTestId('modalOrganizationHeader'); - expect(modalTitle).toBeInTheDocument(); - - const closeButton = screen.getByTestId(/modalOrganizationHeader/i); - userEvent.click(closeButton); - - await wait(); - - const closedModalTitle = screen.queryByText(/postDetail/i); - expect(closedModalTitle).not.toBeInTheDocument(); - }); it('renders the form with input fields and buttons', async () => { render( @@ -507,7 +664,6 @@ describe('Organisation Post Page', () => { await wait(); userEvent.click(screen.getByTestId('createPostModalBtn')); - // Check if input fields and buttons are present expect(screen.getByTestId('modalTitle')).toBeInTheDocument(); expect(screen.getByTestId('modalinfo')).toBeInTheDocument(); expect(screen.getByTestId('createPostBtn')).toBeInTheDocument(); @@ -530,7 +686,6 @@ describe('Organisation Post Page', () => { await wait(); userEvent.click(screen.getByTestId('createPostModalBtn')); - // Simulate user input fireEvent.change(screen.getByTestId('modalTitle'), { target: { value: 'Test Title' }, }); @@ -538,12 +693,11 @@ describe('Organisation Post Page', () => { target: { value: 'Test Info' }, }); - // Check if input values are set correctly expect(screen.getByTestId('modalTitle')).toHaveValue('Test Title'); expect(screen.getByTestId('modalinfo')).toHaveValue('Test Info'); }); - test('allows users to upload an image', async () => { + it('allows users to upload an image', async () => { render( @@ -579,7 +733,8 @@ describe('Organisation Post Page', () => { const closeButton = screen.getByTestId('mediaCloseButton'); fireEvent.click(closeButton); }, 15000); - test('Create post, preview image, and close preview', async () => { + + it('Create post, preview image, and close preview', async () => { await act(async () => { render( @@ -620,19 +775,17 @@ describe('Organisation Post Page', () => { userEvent.upload(screen.getByTestId('addMediaField'), videoFile); }); - // Check if the video is displayed const videoPreview = await screen.findByTestId('videoPreview'); expect(videoPreview).toBeInTheDocument(); - // Check if the close button for the video works const closeVideoPreviewButton = screen.getByTestId('mediaCloseButton'); await act(async () => { fireEvent.click(closeVideoPreviewButton); }); expect(videoPreview).not.toBeInTheDocument(); }); - test('Sorting posts by pinned status', async () => { - // Mocked data representing posts with different pinned statuses + + it('Sorting posts by pinned status', async () => { const mockedPosts = [ { _id: '1', @@ -656,7 +809,6 @@ describe('Organisation Post Page', () => { }, ]; - // Render the OrgPost component and pass the mocked data to it render( @@ -674,7 +826,6 @@ describe('Organisation Post Page', () => { const sortedPosts = screen.getAllByTestId('post-item'); - // Assert that the posts are sorted correctly expect(sortedPosts).toHaveLength(mockedPosts.length); expect(sortedPosts[0]).toHaveTextContent( 'postoneThis is the first po... Aditya Shelke', @@ -686,4 +837,331 @@ describe('Organisation Post Page', () => { 'posttwoTis is the post two Aditya Shelke', ); }); + + it('successful post creation should reset form and close modal', async () => { + const customMocks = [...MOCKS, successMock]; + const customLink = new StaticMockLink(customMocks, true); + + render( + + + + + + + + + + , + ); + + await wait(); + + userEvent.click(screen.getByTestId('createPostModalBtn')); + + userEvent.type(screen.getByTestId('modalTitle'), 'Test Post'); + userEvent.type(screen.getByTestId('modalinfo'), 'Test Content'); + + await act(async () => { + fireEvent.click(screen.getByTestId('createPostBtn')); + }); + + await wait(); + + expect( + screen.queryByTestId('modalOrganizationHeader'), + ).not.toBeInTheDocument(); + + userEvent.click(screen.getByTestId('createPostModalBtn')); + expect(screen.getByTestId('modalTitle')).toHaveValue(''); + expect(screen.getByTestId('modalinfo')).toHaveValue(''); + expect(screen.getByTestId('pinPost')).not.toBeChecked(); + + const toastContainer = screen.getByRole('alert'); + expect(toastContainer).toBeInTheDocument(); + }); + + it('pagination controls work correctly', async () => { + const mockPostEdges = + MOCKS[0]?.result?.data?.organizations[0].posts.edges ?? []; + const paginationMock = { + request: { + query: ORGANIZATION_POST_LIST, + variables: { + id: undefined, + after: null, + before: null, + first: 6, + last: null, + }, + }, + result: { + data: { + organizations: [ + { + posts: { + edges: mockPostEdges, + pageInfo: { + startCursor: 'startCursor123', + endCursor: 'endCursor123', + hasNextPage: true, + hasPreviousPage: true, + }, + totalCount: 10, + }, + }, + ], + }, + }, + }; + + const customMocks = [...MOCKS, paginationMock]; + const customLink = new StaticMockLink(customMocks, true); + + render( + + + + + + + + + , + ); + + await wait(); + + const nextButton = screen.getByRole('button', { name: /next/i }); + const previousButton = screen.getByRole('button', { name: /previous/i }); + + expect(nextButton).toBeInTheDocument(); + expect(previousButton).toBeInTheDocument(); + + await act(async () => { + fireEvent.click(nextButton); + }); + await wait(); + + await act(async () => { + fireEvent.click(previousButton); + }); + await wait(); + + expect(nextButton).toBeInTheDocument(); + expect(previousButton).toBeInTheDocument(); + }); + + it('pagination buttons states are correctly set', async () => { + const mockPostEdges = + MOCKS[0]?.result?.data?.organizations[0].posts.edges ?? []; + const noPaginationMock = { + request: { + query: ORGANIZATION_POST_LIST, + variables: { + id: undefined, + after: null, + before: null, + first: 6, + last: null, + }, + }, + result: { + data: { + organizations: [ + { + posts: { + edges: mockPostEdges, + pageInfo: { + startCursor: 'startCursor123', + endCursor: 'endCursor123', + hasNextPage: false, + hasPreviousPage: false, + }, + totalCount: 4, + }, + }, + ], + }, + }, + }; + + const customMocks = [...MOCKS, noPaginationMock]; + const customLink = new StaticMockLink(customMocks, true); + + render( + + + + + + + + + , + ); + + await wait(); + + const nextButton = screen.getByRole('button', { name: /next/i }); + const previousButton = screen.getByRole('button', { name: /previous/i }); + + expect(nextButton).toHaveAttribute('disabled'); + expect(previousButton).toHaveAttribute('disabled'); + }); + + it('handleNextPage updates pagination variables correctly', async () => { + render( + + + + + + + + + , + ); + + await waitFor(() => { + expect(screen.getByTestId('nextButton')).not.toBeDisabled(); + }); + + await waitFor(() => { + fireEvent.click(screen.getByTestId('previousButton')); + }); + + await waitFor(() => { + fireEvent.click(screen.getByTestId('nextButton')); + expect(screen.getByTestId('nextButton')).toBeDisabled(); + }); + }); + + it('handlePreviousPage updates pagination variables correctly', async () => { + render( + + + + + + + + + , + ); + + await waitFor(() => { + expect(screen.getByTestId('nextButton')).not.toBeDisabled(); + }); + + await act(async () => { + fireEvent.click(screen.getByTestId('nextButton')); + }); + + await waitFor(() => { + expect(screen.getByTestId('previousButton')).not.toBeDisabled(); + }); + + await act(async () => { + fireEvent.click(screen.getByTestId('previousButton')); + }); + + await waitFor(() => { + expect(screen.getByTestId('previousButton')).toBeDisabled(); + expect(screen.getByTestId('nextButton')).not.toBeDisabled(); + }); + }); + + it('handles create post error cases', async () => { + const errorMock = { + request: { + query: CREATE_POST_MUTATION, + variables: { + title: '', + text: '', + organizationId: 'undefined', + file: '', + pinned: false, + }, + }, + error: new Error('Text fields cannot be empty strings'), + }; + + render( + + + + + + + + + + , + ); + + await wait(); + + userEvent.click(screen.getByTestId('createPostModalBtn')); + + await act(async () => { + fireEvent.click(screen.getByTestId('createPostBtn')); + }); + + await wait(); + + const toastContainer = screen.getByRole('alert'); + expect(toastContainer).toBeInTheDocument(); + }); + + it('handles empty search input', async () => { + render( + + + + + + + + + , + ); + + await wait(); + + const searchInput = screen.getByTestId('searchByName'); + fireEvent.change(searchInput, { target: { value: '' } }); + + await wait(); + + expect(searchInput).toHaveValue(''); + }); + + it('handles file input change with no file selected', async () => { + render( + + + + + + + + + , + ); + + await wait(); + + userEvent.click(screen.getByTestId('createPostModalBtn')); + + const fileInput = screen.getByTestId('addMediaField'); + fireEvent.change(fileInput, { target: { files: [] } }); + + await wait(); + + expect(screen.queryByTestId('mediaPreview')).not.toBeInTheDocument(); + }); }); diff --git a/src/screens/OrgPost/OrgPost.tsx b/src/screens/OrgPost/OrgPost.tsx index 857071ca25..2371b9e264 100644 --- a/src/screens/OrgPost/OrgPost.tsx +++ b/src/screens/OrgPost/OrgPost.tsx @@ -1,4 +1,5 @@ -import { useMutation, useQuery, type ApolloError } from '@apollo/client'; +import type { ApolloError } from '@apollo/client'; +import { useMutation, useQuery } from '@apollo/client'; import { CREATE_POST_MUTATION } from 'GraphQl/Mutations/mutations'; import { ORGANIZATION_POST_LIST } from 'GraphQl/Queries/Queries'; import Loader from 'components/Loader/Loader'; @@ -383,6 +384,7 @@ function orgPost(): JSX.Element { !orgPostListData?.organizations[0].posts.pageInfo .hasPreviousPage } + data-testid="previousButton" > {t('Previous')} @@ -394,6 +396,7 @@ function orgPost(): JSX.Element { disabled={ !orgPostListData?.organizations[0].posts.pageInfo.hasNextPage } + data-testid="nextButton" > {t('Next')}