Skip to content
Merged
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
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/packages/modules-datasets/datasets/edit/edit.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ export const Component = () => {
<StatisticalInformation
editingDataset={editingDataset}
setEditingDataset={setEditingDataset}
clientSideErrors={clientSideErrors}
/>
),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { NumberInput } from '@components/form/input';
import { Row } from '@components/layout';

import { withCodesLists } from '@utils/hoc/withCodesLists';
import { useStructures } from '@utils/hooks/structures';

import { D1 } from '../../../../deprecated-locales';
import {
Expand All @@ -16,20 +15,16 @@ import {
} from '../../../../redux/actions/constants/codeList';
import { convertCodesListsToSelectOption } from '../../../utils/codelist-to-select-options';
import { TemporalField } from '../../components/temporalField';
import { DataStructure } from './statistical-information/data-structure';

const StatisticalInformationTab = ({
editingDataset,
setEditingDataset,
clientSideErrors,
...props
}) => {
const clDataTypes = convertCodesListsToSelectOption(props[CL_DATA_TYPES]);

const { data: structures } = useStructures();

const structuresOptions =
structures?.map(({ iri, labelLg1 }) => ({ value: iri, label: labelLg1 })) ??
[];

const clStatUnit = convertCodesListsToSelectOption(props[CL_STAT_UNIT]);

const clFreqOptions = convertCodesListsToSelectOption(props[CL_FREQ]);
Expand Down Expand Up @@ -58,21 +53,16 @@ const StatisticalInformationTab = ({
</div>
</Row>
<Row>
<div className="col-md-12 form-group">
<label className="w-100 wilco-label-required">
{D1.datasetsDataStructure}
<ReactSelect
value={editingDataset.dataStructure}
options={structuresOptions}
onChange={(option) => {
setEditingDataset({
...editingDataset,
dataStructure: option?.value,
});
}}
/>
</label>
</div>
<DataStructure
value={editingDataset.dataStructure}
error={clientSideErrors?.fields?.dataStructure}
onChange={(value) => {
setEditingDataset({
...editingDataset,
dataStructure: value,
});
}}
/>
</Row>
<Row>
<div className="col-md-12 form-group">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.data-structure-input {
display: flex;
gap: 1em;
}

.data-structure-input button {
height: fit-content;
align-self: end;
margin-bottom: 5px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { describe, expect, it, Mock, vi } from 'vitest';

import { useStructures } from '@utils/hooks/structures';

import { DataStructure } from './data-structure';

// Mock useStructures hook
vi.mock('@utils/hooks/structures', () => ({
useStructures: vi.fn(),
}));

describe('DataStructure Component', () => {
it('should display a text input in URN mode by default if value does not match a structure', () => {
(useStructures as Mock).mockReturnValue({ data: [] });

render(<DataStructure value="urn:1234" onChange={vi.fn()} />);

expect(screen.getByRole('textbox')).toBeInTheDocument();
expect(
screen.getByRole('button', { name: /Choisir une structure/i }),
).toBeInTheDocument();
});

it('should display a select dropdown in URL mode if the value matches a structure', () => {
const structures = [
{ iri: 'http://example.com/structure1', labelLg1: 'Structure 1' },
];
(useStructures as Mock).mockReturnValue({ data: structures });

render(
<DataStructure
value="http://example.com/structure1"
onChange={vi.fn()}
/>,
);

expect(screen.getByText('Structure 1')).toBeInTheDocument();
expect(
screen.getByRole('button', { name: /Saisir une URN/i }),
).toBeInTheDocument();
});

it('should switch from URN mode to URL mode when clicking the button', async () => {
(useStructures as Mock).mockReturnValue({ data: [] });
const onChangeMock = vi.fn();

render(<DataStructure value="urn:1234" onChange={onChangeMock} />);

const button = screen.getByRole('button', {
name: /Choisir une structure/i,
});
fireEvent.click(button);

expect(screen.getByRole('combobox')).toBeInTheDocument();
});

it('should switch from URL mode to URN mode when clicking the button', async () => {
const structures = [
{ iri: 'http://example.com/structure1', labelLg1: 'Structure 1' },
];
(useStructures as Mock).mockReturnValue({ data: structures });

render(
<DataStructure
value="http://example.com/structure1"
onChange={vi.fn()}
/>,
);

const button = screen.getByRole('button', { name: /Saisir une URN/i });
fireEvent.click(button);

expect(screen.getByRole('textbox')).toBeInTheDocument();
});

it('should call onChange when the user enters a value in URN mode', async () => {
(useStructures as Mock).mockReturnValue({ data: [] });
const onChangeMock = vi.fn();

render(<DataStructure value="urn:1234" onChange={onChangeMock} />);

const input = screen.getByRole('textbox');
fireEvent.change(input, { target: { value: 'urn:5678' } });

expect(onChangeMock).toHaveBeenCalledWith('urn:5678');
});

it('should display an error message if an error is provided', async () => {
(useStructures as Mock).mockReturnValue({ data: [] });

render(
<DataStructure
value="urn:1234"
onChange={vi.fn()}
error="Required field"
/>,
);

expect(screen.getByText('Required field')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { Option } from '@model/SelectOption';
import { useState } from 'react';
import ReactSelect from 'react-select';

import { ClientSideError } from '@components/errors-bloc';
import { TextInput } from '@components/form/input';

import { createDictionary, firstLang } from '@utils/dictionnary';
import { useStructures } from '@utils/hooks/structures';

import { D1 } from '../../../../../deprecated-locales';
import './data-structure.css';

const URN_MODE = 'URN_MODE';
const URL_MODE = 'URL_MODE';

const D = createDictionary(firstLang, {
chooseUrn: {
fr: 'Saisir une URN',
en: 'Type a URN',
},
chooseUrl: {
fr: 'Choisir une structure',
en: 'Choose a structure',
},
});
export const DataStructure = ({
value,
onChange,
error,
}: Readonly<{
value: string;
onChange: (value: string) => void;
error?: string;
}>) => {
const { data: structures } = useStructures();

const options: Option[] =
structures?.map(({ iri, labelLg1 }) => ({ value: iri, label: labelLg1 })) ??
[];

const [mode, setMode] = useState<typeof URN_MODE | typeof URL_MODE>(
structures?.find((s) => s.iri === value) ? URL_MODE : URN_MODE,
);

if (mode === URN_MODE) {
return (
<div className="data-structure-input col-md-12 form-group">
<div className="w-100">
<label className="w-100 wilco-label-required">
{D1.datasetsDataStructure}
<TextInput
aria-describedby="datastructure-error"
value={value}
onChange={(e) => {
onChange(e.target.value);
}}
/>
</label>
<ClientSideError
id="datastructure-error"
error={error}
></ClientSideError>
</div>
<button
type="button"
className="btn btn-default"
onClick={() => setMode('URL_MODE')}
>
{D.chooseUrl}
</button>
</div>
);
}
return (
<div className="data-structure-input col-md-12 form-group">
<label className="w-100 wilco-label-required">
{D1.datasetsDataStructure}
<ReactSelect
value={value}
options={options}
onChange={(option: Option) => {
onChange(option?.value);
}}
/>
</label>
<button
type="button"
className="btn btn-default"
onClick={() => setMode('URN_MODE')}
>
{D.chooseUrn}
</button>
</div>
);
};
54 changes: 54 additions & 0 deletions src/packages/modules-datasets/datasets/edit/validation.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ describe('validation', function () {
altIdentifier: '',
creator: '',
contributor: '',
dataStructure: '',
disseminationStatus: '',
wasGeneratedIRIs: '',
},
Expand All @@ -44,6 +45,7 @@ describe('validation', function () {
altIdentifier: '',
creator: '',
contributor: '',
dataStructure: '',
disseminationStatus: '',
wasGeneratedIRIs: '',
},
Expand All @@ -70,6 +72,7 @@ describe('validation', function () {
altIdentifier: '',
creator: 'The property <strong>Owner</strong> is required.',
contributor: 'The property <strong>Contributors</strong> is required.',
dataStructure: '',
disseminationStatus:
'The property <strong>Dissemination status</strong> is required.',
wasGeneratedIRIs:
Expand All @@ -96,13 +99,63 @@ describe('validation', function () {
altIdentifier: '',
creator: '',
contributor: '',
dataStructure: '',
disseminationStatus: '',
wasGeneratedIRIs:
'The property <strong>Produced from</strong> is required.',
},
});
});

it('should return an error if datastructure is not a URI', function () {
expect(
validate({
labelLg1: 'labelLg2',
labelLg2: 'labelLg2',
catalogRecord,
disseminationStatus: 'status',
wasGeneratedIRIs: ['id'],
dataStructure: 'dataset',
}),
).toEqual({
errorMessage: ['Invalid url'],
fields: {
labelLg1: '',
labelLg2: '',
altIdentifier: '',
creator: '',
contributor: '',
dataStructure: 'Invalid url',
disseminationStatus: '',
wasGeneratedIRIs: '',
},
});
});
it('should return no error', function () {
expect(
validate({
labelLg1: 'labelLg2',
labelLg2: 'labelLg2',
catalogRecord,
disseminationStatus: 'status',
wasGeneratedIRIs: ['id'],
dataStructure: 'http://dataset',
}),
).toEqual({
errorMessage: [],
fields: {
labelLg1: '',
labelLg2: '',
altIdentifier: '',
creator: '',
contributor: '',
dataStructure: '',
disseminationStatus: '',
wasGeneratedIRIs: '',
},
});
});
it('should return no error if datastructure is undefined', function () {
expect(
validate({
labelLg1: 'labelLg2',
Expand All @@ -119,6 +172,7 @@ describe('validation', function () {
altIdentifier: '',
creator: '',
contributor: '',
dataStructure: '',
disseminationStatus: '',
wasGeneratedIRIs: '',
},
Expand Down
Loading
Loading