Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Created 'Create Section' page, hooked in with real data and added translations #216

Open
wants to merge 5 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
### Updated
=======
- Updated app/template/[templateId]/section/create page to use Remirror text editors, and checkboxes with info popovers [#187]
- Updated DMPEditor to use a skeleton while the text editors are loading, since it can be slow [#187]
- Updated app/template/[templateId]/section/new page to hook it up to backend data, handle errors, and add translations [#189]
- Updated app/template/[templateId] page to hook it up to the backend and handle errors and translations[#206]
- Updated app/[locale]/template page to hook it up to backend and handle errors and translations[#82]
Expand Down
7 changes: 4 additions & 3 deletions app/[locale]/styleguide/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,7 @@ function Page() {
<DmpIcon icon="favorite" />
<DmpIcon icon="format_bold" />
<DmpIcon icon="double_arrow" />
<DmpIcon icon="info" />
</div>
</Example>

Expand Down Expand Up @@ -1073,7 +1074,7 @@ function Page() {
<div id="_containers">
<h2><code>Layout Container</code></h2>
<p>The standard <code>{`<LayoutContainer>`}</code> wraps content containers to provide
some common container within the layout container.</p>
some common container within the layout container.</p>
<LayoutContainer>
<ContentContainer>
<div><pre><code>
Expand Down Expand Up @@ -1207,7 +1208,7 @@ function Page() {
TODO: Write about this layout here
</ContentContainer>

<DrawerPanel isOpen={drawerOpen} onClose={() => setDrawerOpen(false) }>
<DrawerPanel isOpen={drawerOpen} onClose={() => setDrawerOpen(false)}>
<p>This is the Drawer Content</p>
</DrawerPanel>
</LayoutWithPanel>
Expand Down Expand Up @@ -1593,7 +1594,7 @@ function Page() {
<QuestionEdit
key={2552}
id="24"
name="This is a question"
text="This is a question"
link="/edit"
/>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import React from "react";
import { render, screen, act, fireEvent } from '@/utils/test-utils';
import {
useAddSectionMutation,
useTagsQuery,
} from '@/generated/graphql';

import { axe, toHaveNoViolations } from 'jest-axe';
import { useParams } from 'next/navigation';
import { useTranslations as OriginalUseTranslations } from 'next-intl';
import CreateSectionPage from '../page';
expect.extend(toHaveNoViolations);

// Mock the useTemplateQuery hook
jest.mock("@/generated/graphql", () => ({
useAddSectionMutation: jest.fn(),
useTagsQuery: jest.fn()
}));

jest.mock('next/navigation', () => ({
useParams: jest.fn(),
}))

// Create a mock for scrollIntoView and focus
const mockScrollIntoView = jest.fn();

type UseTranslationsType = ReturnType<typeof OriginalUseTranslations>;

// Mock useTranslations from next-intl
jest.mock('next-intl', () => ({
useTranslations: jest.fn(() => {
const mockUseTranslations: UseTranslationsType = ((key: string) => key) as UseTranslationsType;

/*eslint-disable @typescript-eslint/no-explicit-any */
mockUseTranslations.rich = (
key: string,
values?: Record<string, any>
) => {
// Handle rich text formatting
if (values?.p) {
return values.p(key); // Simulate rendering the `p` tag function
}
return key;
};

return mockUseTranslations;
}),
}));


const mockTagsData = {
"tags": [
{
"id": 1,
"description": "The types of data that will be collected along with their formats and estimated volumes.",
"name": "Data description"
},
{
"id": 2,
"description": "Descriptions naming conventions, metadata standards that will be used along with data dictionaries and glossaries",
"name": "Data organization & documentation"
},
{
"id": 3,
"description": "Who will have access to the data and how that access will be controlled, how the data will be encrypted and relevant compliance with regulations or standards (e.g. HIPAA, GDPR)",
"name": "Security & privacy"
},
{
"id": 4,
"description": "Ethical considerations during data collection, use or sharing and how informed consent will be obtained from participants",
"name": "Ethical considerations"
},
{
"id": 5,
"description": "Training that will be provided to team members on data management practices and support for data issues",
"name": "Training & support"
},
{
"id": 6,
"description": "Policies and procedures for how the data will be shared with collaborators and/or the public, restrictions to access and the licenses and permissions used",
"name": "Data sharing"
},
{
"id": 7,
"description": "Where the data will be stored, the backup strategy and frequency and how long it will be retained",
"name": "Data storage & backup"
},
{
"id": 8,
"description": "Methods used to ensure data quality and integrity and any procedures used for validation",
"name": "Data quality & integrity"
},
{
"id": 9,
"description": "Desriptions of the project team members and their roles",
"name": "Roles & responsibilities"
},
{
"id": 10,
"description": "Description of the budget available for data collection, use and preservation including software licensing, personnel and storage costs",
"name": "Budget"
},
{
"id": 11,
"description": "How the data will be collected or generated, primary and secondary sources that will be used and any instruments that will be used",
"name": "Data collection"
}
]
};

describe("CreateSectionPage", () => {
beforeEach(() => {
HTMLElement.prototype.scrollIntoView = mockScrollIntoView;
window.scrollTo = jest.fn(); // Called by the wrapping PageHeader
const mockTemplateId = 123;
const mockUseParams = useParams as jest.Mock;

// Mock the return value of useParams
mockUseParams.mockReturnValue({ templateId: `${mockTemplateId}` });
(useTagsQuery as jest.Mock).mockReturnValue({
data: mockTagsData,
loading: true,
error: null,
});
});

it("should render correct fields", async () => {
(useAddSectionMutation as jest.Mock).mockReturnValue([
jest.fn().mockResolvedValueOnce({ data: { key: 'value' } }), // Correct way to mock a resolved promise
{ loading: false, error: undefined },
]);

await act(async () => {
render(
<CreateSectionPage />
);
});

const heading = screen.getByRole('heading', { level: 1 });
expect(heading).toHaveTextContent('title');
const editQuestionTab = screen.getByRole('tab', { name: 'tabs.editSection' });
expect(editQuestionTab).toBeInTheDocument();
const editOptionsTab = screen.getByRole('tab', { name: 'tabs.options' });
expect(editOptionsTab).toBeInTheDocument();
const editLogicTab = screen.getByRole('tab', { name: 'tabs.logic' });
expect(editLogicTab).toBeInTheDocument();
const sectionNameEditor = screen.getByRole('textbox', { name: /sectionName/i });
expect(sectionNameEditor).toBeInTheDocument();
const sectionIntroductionEditor = screen.getByRole('textbox', { name: /sectionIntroduction/i });
expect(sectionIntroductionEditor).toBeInTheDocument();
const sectionRequirementsEditor = screen.getByRole('textbox', { name: /sectionRequirements/i });
expect(sectionRequirementsEditor).toBeInTheDocument();
const sectionGuidanceEditor = screen.getByRole('textbox', { name: /sectionGuidance/i });
expect(sectionGuidanceEditor).toBeInTheDocument();
const tagsHeader = screen.getByText('labels.bestPracticeTags');
expect(tagsHeader).toBeInTheDocument();
const checkboxLabels = screen.getAllByTestId('checkboxLabel');
expect(checkboxLabels).toHaveLength(11);
});

it('should display error when no value is entered in section name field', async () => {
(useAddSectionMutation as jest.Mock).mockReturnValue([
jest.fn().mockResolvedValueOnce({ data: { key: 'value' } }), // Correct way to mock a resolved promise
{ loading: false, error: undefined },
]);

await act(async () => {
render(
<CreateSectionPage />
);
});

const searchButton = screen.getByRole('button', { name: /button.createSection/i });
fireEvent.click(searchButton);

const errorMessage = screen.getByRole('alert');
expect(errorMessage).toBeInTheDocument();
expect(errorMessage).toHaveTextContent('messages.fieldLengthValidation');
})

it('should pass axe accessibility test', async () => {
(useAddSectionMutation as jest.Mock).mockReturnValue([
jest.fn().mockResolvedValueOnce({ data: { key: 'value' } }),
]);

const { container } = render(
<CreateSectionPage />
);
await act(async () => {
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
});
Loading