@eeacms/volto-cca-policy 0.3.116 → 0.3.118

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,41 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ ### [0.3.118](https://github.com/eea/volto-cca-policy/compare/0.3.117...0.3.118) - 28 April 2026
8
+
9
+ #### :rocket: New Features
10
+
11
+ - feat: Add archived version notice and versions group for indicators [Tiberiu Ichim - [`10a4a52`](https://github.com/eea/volto-cca-policy/commit/10a4a5285cfd352e48300fbc6d8b89652151dda1)]
12
+
13
+ #### :bug: Bug Fixes
14
+
15
+ - fix: Import INDICATOR from correct constants module [Tiberiu Ichim - [`b2c3ae4`](https://github.com/eea/volto-cca-policy/commit/b2c3ae4b43ae69ae5a8da92ed6a6461d1ca2fdba)]
16
+ - fix: Add missing INDICATOR import, comment out unused VersionsGroup import [Tiberiu Ichim - [`0578df8`](https://github.com/eea/volto-cca-policy/commit/0578df8636e9ed4f48b0ed7f72114755c6d12dc4)]
17
+ - fix: Use expandToBackendURL for API POST to avoid 404 [Tiberiu Ichim - [`9eb849c`](https://github.com/eea/volto-cca-policy/commit/9eb849cf2291e80610d50cf73d0b693a8feef051)]
18
+
19
+ #### :nail_care: Enhancements
20
+
21
+ - change: Stay on page and show toast after creating archived copy [Tiberiu Ichim - [`26665ba`](https://github.com/eea/volto-cca-policy/commit/26665ba17d33a48faf3d2d69a6de3e86c2a43cb6)]
22
+
23
+ #### :house: Internal changes
24
+
25
+ - style: Automated code fix [eea-jenkins - [`4c4f710`](https://github.com/eea/volto-cca-policy/commit/4c4f7107cff730f022da14506385f96a410a8654)]
26
+ - style: Automated code fix [eea-jenkins - [`b520b39`](https://github.com/eea/volto-cca-policy/commit/b520b3945fccd8075912cac73aa17faf42dbf49f)]
27
+ - style: Automated code fix [eea-jenkins - [`d8224b1`](https://github.com/eea/volto-cca-policy/commit/d8224b1a96182fa1fdeabe0fb4ca95fb20c27cd5)]
28
+
29
+ #### :hammer_and_wrench: Others
30
+
31
+ - Add tests for archived indicator components [Tiberiu Ichim - [`bf6209e`](https://github.com/eea/volto-cca-policy/commit/bf6209eb71893d8d501af212d54929afe35e1ddc)]
32
+ - Merge develop into archived_indicators [Tiberiu Ichim - [`b269ff4`](https://github.com/eea/volto-cca-policy/commit/b269ff4f157f52b4d837f3818b1c621a5644de59)]
33
+ - comment out VersionsGroup - relatedItems already shows versions [Tiberiu Ichim - [`a86c9d4`](https://github.com/eea/volto-cca-policy/commit/a86c9d4b6d2fbd94a107f76625f11c9aeb557117)]
34
+ - Merge remote automated code fix [Tiberiu Ichim - [`c52789f`](https://github.com/eea/volto-cca-policy/commit/c52789f4c87683fd10690b7a78cd048663b407fe)]
35
+ - Add button [Tiberiu Ichim - [`1ea8db9`](https://github.com/eea/volto-cca-policy/commit/1ea8db90e7ca741700b87caa315c029235b13684)]
36
+ ### [0.3.117](https://github.com/eea/volto-cca-policy/compare/0.3.116...0.3.117) - 28 April 2026
37
+
38
+ #### :bug: Bug Fixes
39
+
40
+ - fix: update Moldova naming [kreafox - [`33476d7`](https://github.com/eea/volto-cca-policy/commit/33476d77e4347fcae6fde1b3d963064bb8f7476f)]
41
+
7
42
  ### [0.3.116](https://github.com/eea/volto-cca-policy/compare/0.3.115...0.3.116) - 27 April 2026
8
43
 
9
44
  #### :rocket: New Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-cca-policy",
3
- "version": "0.3.116",
3
+ "version": "0.3.118",
4
4
  "description": "@eeacms/volto-cca-policy: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -11,3 +11,10 @@ export { default as AccordionList } from './theme/AccordionList/AccordionList';
11
11
  // Widgets
12
12
  export { default as RASTWidgetView } from './theme/Widgets/RASTWidgetView';
13
13
  export { default as ImageWidget } from './theme/Widgets/ImageWidget';
14
+
15
+ // Manage
16
+ export { default as CreateArchivedCopyButton } from './manage/CreateArchivedCopyButton/CreateArchivedCopyButton';
17
+
18
+ // Views
19
+ export { default as ArchivedVersionNotice } from './theme/Views/ArchivedVersionNotice';
20
+ export { default as VersionsGroup } from './theme/Views/VersionsGroup';
@@ -0,0 +1,153 @@
1
+ import React from 'react';
2
+ import { connect } from 'react-redux';
3
+ import { Plug } from '@plone/volto/components/manage/Pluggable';
4
+ import { Modal, Button, Form, Message } from 'semantic-ui-react';
5
+ import superagent from 'superagent';
6
+ import { toast } from 'react-toastify';
7
+ import { Toast } from '@plone/volto/components';
8
+ import { flattenToAppURL, expandToBackendURL } from '@plone/volto/helpers';
9
+ import { INDICATOR } from '@eeacms/volto-cca-policy/constants';
10
+
11
+ function CreateArchivedCopyButton(props) {
12
+ const { content, token } = props;
13
+ const [open, setOpen] = React.useState(false);
14
+ const [title, setTitle] = React.useState('');
15
+ const [id, setId] = React.useState('');
16
+ const [error, setError] = React.useState(null);
17
+ const [loading, setLoading] = React.useState(false);
18
+
19
+ const contentId = content?.['@id'] || '';
20
+ const contentType = content?.['@type'];
21
+ const reviewState = content?.review_state;
22
+ const originalId = content?.id;
23
+
24
+ const show =
25
+ !!token && contentType === INDICATOR && reviewState === 'published';
26
+
27
+ const handleOpen = () => {
28
+ setTitle(content?.title ? `${content.title} (Archived)` : '');
29
+ setId(originalId ? `${originalId}-v2` : '');
30
+ setError(null);
31
+ setOpen(true);
32
+ };
33
+
34
+ const handleClose = () => {
35
+ setOpen(false);
36
+ setError(null);
37
+ };
38
+
39
+ const handleSubmit = async () => {
40
+ if (!id || id === originalId) {
41
+ setError('You must change the ID of the archived copy.');
42
+ return;
43
+ }
44
+
45
+ setLoading(true);
46
+ setError(null);
47
+
48
+ try {
49
+ const url = expandToBackendURL(contentId);
50
+ const response = await superagent
51
+ .post(`${url}/@create-archived-copy`)
52
+ .set('Accept', 'application/json')
53
+ .set('Content-Type', 'application/json')
54
+ .send({ title, id });
55
+
56
+ const newUrl = flattenToAppURL(response.body['@id']);
57
+ setOpen(false);
58
+ setLoading(false);
59
+ setTitle('');
60
+ setId('');
61
+ toast.success(
62
+ <Toast
63
+ success
64
+ title="Archived copy created"
65
+ content={`The archived copy has been created. You can view it here: ${newUrl}`}
66
+ />,
67
+ );
68
+ } catch (err) {
69
+ const message =
70
+ err.response?.body?.message ||
71
+ err.response?.text ||
72
+ err.message ||
73
+ 'An error occurred while creating the archived copy.';
74
+ setError(message);
75
+ setLoading(false);
76
+ }
77
+ };
78
+
79
+ if (!show) return null;
80
+
81
+ return (
82
+ <>
83
+ <Plug
84
+ pluggable="main.toolbar.top"
85
+ id="create-archived-copy"
86
+ order={5}
87
+ dependencies={[contentId]}
88
+ >
89
+ <button
90
+ className="circle-right-btn"
91
+ id="create-archived-copy-btn"
92
+ onClick={handleOpen}
93
+ title="Create an archived copy"
94
+ >
95
+ AC
96
+ </button>
97
+ </Plug>
98
+
99
+ <Modal size="small" open={open} onClose={handleClose} closeIcon>
100
+ <Modal.Header>Create an archived copy</Modal.Header>
101
+ <Modal.Content>
102
+ {error && (
103
+ <Message negative>
104
+ <Message.Header>Error</Message.Header>
105
+ <p>{error}</p>
106
+ </Message>
107
+ )}
108
+ <Form>
109
+ <Form.Field>
110
+ <label>Title</label>
111
+ <input
112
+ value={title}
113
+ onChange={(e) => setTitle(e.target.value)}
114
+ placeholder="Title of the archived copy"
115
+ />
116
+ </Form.Field>
117
+ <Form.Field>
118
+ <label>ID</label>
119
+ <input
120
+ value={id}
121
+ onChange={(e) => setId(e.target.value)}
122
+ placeholder="ID of the archived copy"
123
+ />
124
+ {id === originalId && (
125
+ <small style={{ color: '#db2828' }}>
126
+ The ID must be different from the original indicator.
127
+ </small>
128
+ )}
129
+ </Form.Field>
130
+ </Form>
131
+ </Modal.Content>
132
+ <Modal.Actions>
133
+ <Button onClick={handleClose} disabled={loading}>
134
+ Cancel
135
+ </Button>
136
+ <Button
137
+ primary
138
+ onClick={handleSubmit}
139
+ loading={loading}
140
+ disabled={!id || id === originalId}
141
+ >
142
+ Create
143
+ </Button>
144
+ </Modal.Actions>
145
+ </Modal>
146
+ </>
147
+ );
148
+ }
149
+
150
+ export default connect((state) => ({
151
+ content: state.content.data,
152
+ token: state.userSession.token,
153
+ }))(CreateArchivedCopyButton);
@@ -0,0 +1,134 @@
1
+ import React from 'react';
2
+ import { MemoryRouter } from 'react-router-dom';
3
+ import configureStore from 'redux-mock-store';
4
+ import '@testing-library/jest-dom/extend-expect';
5
+ import { Provider } from 'react-intl-redux';
6
+ import { render, fireEvent } from '@testing-library/react';
7
+ import CreateArchivedCopyButton from './CreateArchivedCopyButton';
8
+ import { INDICATOR } from '@eeacms/volto-cca-policy/constants';
9
+
10
+ jest.mock('@plone/volto/components/manage/Pluggable', () => ({
11
+ Plug: ({ children }) => <>{children}</>,
12
+ }));
13
+
14
+ jest.mock('superagent', () => ({
15
+ post: jest.fn(() => ({
16
+ set: jest.fn(() => ({
17
+ set: jest.fn(() => ({
18
+ send: jest.fn(() =>
19
+ Promise.resolve({
20
+ body: { '@id': 'http://localhost:3000/my-indicator-v2' },
21
+ }),
22
+ ),
23
+ })),
24
+ })),
25
+ })),
26
+ }));
27
+
28
+ jest.mock('react-toastify', () => ({
29
+ toast: { success: jest.fn(), error: jest.fn() },
30
+ }));
31
+
32
+ jest.mock('@plone/volto/components', () => ({
33
+ Toast: () => null,
34
+ }));
35
+
36
+ jest.mock('@plone/volto/helpers', () => ({
37
+ flattenToAppURL: (url) => url.replace('http://localhost:3000', ''),
38
+ expandToBackendURL: (url) =>
39
+ url.replace('http://localhost:3000', 'http://localhost:8080/Plone'),
40
+ }));
41
+
42
+ const mockStore = configureStore();
43
+
44
+ function makeStore(contentOverrides = {}, token = '1234') {
45
+ return mockStore({
46
+ content: {
47
+ data: {
48
+ '@type': INDICATOR,
49
+ review_state: 'published',
50
+ '@id': 'http://localhost:3000/my-indicator',
51
+ id: 'my-indicator',
52
+ title: 'My Indicator',
53
+ ...contentOverrides,
54
+ },
55
+ },
56
+ userSession: { token },
57
+ intl: { locale: 'en', messages: {} },
58
+ });
59
+ }
60
+
61
+ function renderWithProviders(ui, store) {
62
+ return render(
63
+ <Provider store={store}>
64
+ <MemoryRouter>{ui}</MemoryRouter>
65
+ </Provider>,
66
+ );
67
+ }
68
+
69
+ describe('CreateArchivedCopyButton', () => {
70
+ it('returns null when user is not logged in', () => {
71
+ const store = makeStore({}, null);
72
+ const { container } = renderWithProviders(
73
+ <CreateArchivedCopyButton />,
74
+ store,
75
+ );
76
+ expect(container.innerHTML).toBe('');
77
+ });
78
+
79
+ it('returns null for non-indicator content type', () => {
80
+ const store = makeStore({ '@type': 'Document' });
81
+ const { container } = renderWithProviders(
82
+ <CreateArchivedCopyButton />,
83
+ store,
84
+ );
85
+ expect(container.innerHTML).toBe('');
86
+ });
87
+
88
+ it('returns null for non-published indicator', () => {
89
+ const store = makeStore({ review_state: 'draft' });
90
+ const { container } = renderWithProviders(
91
+ <CreateArchivedCopyButton />,
92
+ store,
93
+ );
94
+ expect(container.innerHTML).toBe('');
95
+ });
96
+
97
+ it('renders the button for a published indicator with a logged-in user', () => {
98
+ const store = makeStore();
99
+ const { container } = renderWithProviders(
100
+ <CreateArchivedCopyButton />,
101
+ store,
102
+ );
103
+ expect(
104
+ container.querySelector('#create-archived-copy-btn'),
105
+ ).toBeInTheDocument();
106
+ expect(container.querySelector('#create-archived-copy-btn').title).toBe(
107
+ 'Create an archived copy',
108
+ );
109
+ });
110
+
111
+ it('opens modal when button is clicked', () => {
112
+ const store = makeStore();
113
+ const { container, getByText } = renderWithProviders(
114
+ <CreateArchivedCopyButton />,
115
+ store,
116
+ );
117
+ fireEvent.click(container.querySelector('#create-archived-copy-btn'));
118
+ expect(getByText('Create an archived copy')).toBeInTheDocument();
119
+ expect(getByText('Cancel')).toBeInTheDocument();
120
+ expect(getByText('Create')).toBeInTheDocument();
121
+ });
122
+
123
+ it('closes modal on cancel', () => {
124
+ const store = makeStore();
125
+ const { container, getByText, queryByText } = renderWithProviders(
126
+ <CreateArchivedCopyButton />,
127
+ store,
128
+ );
129
+ fireEvent.click(container.querySelector('#create-archived-copy-btn'));
130
+ expect(getByText('Create an archived copy')).toBeInTheDocument();
131
+ fireEvent.click(getByText('Cancel'));
132
+ expect(queryByText('Title')).toBeNull();
133
+ });
134
+ });
@@ -0,0 +1,37 @@
1
+ import React from 'react';
2
+ import { Link } from 'react-router-dom';
3
+ import { Message } from 'semantic-ui-react';
4
+ import { FormattedMessage } from 'react-intl';
5
+ import { flattenToAppURL } from '@plone/volto/helpers';
6
+
7
+ function ArchivedVersionNotice({ content }) {
8
+ if (content?.review_state !== 'archived') return null;
9
+
10
+ const relatedItems = content?.relatedItems || [];
11
+ const latestVersion = relatedItems.find(
12
+ (item) => item.review_state === 'published',
13
+ );
14
+
15
+ if (!latestVersion) return null;
16
+
17
+ const latestUrl = flattenToAppURL(latestVersion['@id']);
18
+
19
+ return (
20
+ <Message info className="archived-version-notice">
21
+ <Message.Content>
22
+ <FormattedMessage
23
+ id="You are viewing an archived version."
24
+ defaultMessage="You are viewing an archived version."
25
+ />{' '}
26
+ <Link to={latestUrl}>
27
+ <FormattedMessage
28
+ id="View latest version"
29
+ defaultMessage="View latest version"
30
+ />
31
+ </Link>
32
+ </Message.Content>
33
+ </Message>
34
+ );
35
+ }
36
+
37
+ export default ArchivedVersionNotice;
@@ -0,0 +1,86 @@
1
+ import React from 'react';
2
+ import { MemoryRouter } from 'react-router-dom';
3
+ import configureStore from 'redux-mock-store';
4
+ import '@testing-library/jest-dom/extend-expect';
5
+ import { Provider } from 'react-intl-redux';
6
+ import { render } from '@testing-library/react';
7
+ import ArchivedVersionNotice from './ArchivedVersionNotice';
8
+
9
+ const mockStore = configureStore();
10
+
11
+ const store = mockStore({
12
+ userSession: { token: '1234' },
13
+ intl: {
14
+ locale: 'en',
15
+ messages: {},
16
+ },
17
+ });
18
+
19
+ describe('ArchivedVersionNotice', () => {
20
+ it('returns null when content is not archived', () => {
21
+ const content = {
22
+ '@type': 'Document',
23
+ review_state: 'published',
24
+ relatedItems: [],
25
+ };
26
+ const { container } = render(
27
+ <Provider store={store}>
28
+ <MemoryRouter>
29
+ <ArchivedVersionNotice content={content} />
30
+ </MemoryRouter>
31
+ </Provider>,
32
+ );
33
+ expect(container.innerHTML).toBe('');
34
+ });
35
+
36
+ it('returns null when archived but no published related item', () => {
37
+ const content = {
38
+ '@type': 'Document',
39
+ review_state: 'archived',
40
+ relatedItems: [
41
+ {
42
+ '@id': 'http://localhost:3000/another-archived',
43
+ review_state: 'archived',
44
+ },
45
+ ],
46
+ };
47
+ const { container } = render(
48
+ <Provider store={store}>
49
+ <MemoryRouter>
50
+ <ArchivedVersionNotice content={content} />
51
+ </MemoryRouter>
52
+ </Provider>,
53
+ );
54
+ expect(container.innerHTML).toBe('');
55
+ });
56
+
57
+ it('renders a notice with a link to the published version', () => {
58
+ const content = {
59
+ '@id': 'http://localhost:3000/archived-indicator',
60
+ '@type': 'Indicator',
61
+ review_state: 'archived',
62
+ title: 'My Indicator (Archived)',
63
+ relatedItems: [
64
+ {
65
+ '@id': 'http://localhost:3000/my-indicator',
66
+ review_state: 'published',
67
+ title: 'My Indicator',
68
+ },
69
+ ],
70
+ };
71
+ const { container } = render(
72
+ <Provider store={store}>
73
+ <MemoryRouter>
74
+ <ArchivedVersionNotice content={content} />
75
+ </MemoryRouter>
76
+ </Provider>,
77
+ );
78
+ expect(
79
+ container.querySelector('.archived-version-notice'),
80
+ ).toBeInTheDocument();
81
+ expect(container.querySelector('a')).toHaveAttribute(
82
+ 'href',
83
+ '/my-indicator',
84
+ );
85
+ });
86
+ });
@@ -12,7 +12,11 @@ import {
12
12
  flourishDataprotection,
13
13
  getDataSrcFromEmbedCode,
14
14
  } from '@eeacms/volto-cca-policy/helpers/flourishUtils';
15
- import { VIDEO, CONTENT_TYPE_LABELS } from '@eeacms/volto-cca-policy/constants';
15
+ import {
16
+ VIDEO,
17
+ INDICATOR,
18
+ CONTENT_TYPE_LABELS,
19
+ } from '@eeacms/volto-cca-policy/constants';
16
20
  import {
17
21
  HTMLField,
18
22
  ReferenceInfo,
@@ -24,6 +28,10 @@ import {
24
28
  ExternalLink,
25
29
  BannerTitle,
26
30
  } from '@eeacms/volto-cca-policy/helpers';
31
+ import {
32
+ ArchivedVersionNotice,
33
+ // VersionsGroup, // commented out - relatedItems already shows versions
34
+ } from '@eeacms/volto-cca-policy/components';
27
35
 
28
36
  const SHARE_EEA = ['https://cmshare.eea.eu', 'shareit.eea.europa.eu'];
29
37
 
@@ -133,6 +141,7 @@ const BottomInfo = (props) => {
133
141
 
134
142
  <ContentRelatedItems {...props} />
135
143
  <PublishedModifiedInfo {...props} />
144
+ {/* <VersionsGroup {...props} /> */}
136
145
  <ShareInfoButton {...props} />
137
146
  </>
138
147
  );
@@ -176,6 +185,7 @@ const DatabaseItemView = (props) => {
176
185
  />
177
186
 
178
187
  <Container>
188
+ {type === INDICATOR && <ArchivedVersionNotice content={content} />}
179
189
  <PortalMessage content={content} />
180
190
  <Grid columns="12">
181
191
  <Grid.Row>
@@ -0,0 +1,101 @@
1
+ import React from 'react';
2
+ import { Link } from 'react-router-dom';
3
+ import { FormattedMessage } from 'react-intl';
4
+ import { flattenToAppURL } from '@plone/volto/helpers';
5
+
6
+ function VersionsGroup({ content }) {
7
+ const relatedItems = content?.relatedItems || [];
8
+ const currentId = content?.['@id'];
9
+ const currentState = content?.review_state;
10
+
11
+ // Find the latest (published) version
12
+ const latestFromRelated = relatedItems.find(
13
+ (item) => item.review_state === 'published',
14
+ );
15
+
16
+ // The latest version is either the current object (if published)
17
+ // or the published item found in relatedItems
18
+ const latestVersion =
19
+ currentState === 'published'
20
+ ? { '@id': currentId, title: content?.title, created: content?.created }
21
+ : latestFromRelated;
22
+
23
+ // Collect archived versions:
24
+ // - If current is published: archived copies are in relatedItems
25
+ // - If current is archived: include current object + any archived copies in relatedItems
26
+ let archivedVersions = relatedItems.filter(
27
+ (item) => item.review_state === 'archived',
28
+ );
29
+
30
+ if (currentState === 'archived') {
31
+ // Ensure the current archived copy is included in the list
32
+ const currentInList = archivedVersions.some(
33
+ (item) => item['@id'] === currentId,
34
+ );
35
+ if (!currentInList) {
36
+ archivedVersions = [
37
+ ...archivedVersions,
38
+ {
39
+ '@id': currentId,
40
+ title: content?.title,
41
+ created: content?.created,
42
+ },
43
+ ];
44
+ }
45
+ }
46
+
47
+ // Sort by creation date ascending
48
+ archivedVersions.sort((a, b) => new Date(a.created) - new Date(b.created));
49
+
50
+ // Build the full version list: latest first, then archived copies
51
+ const versions = [];
52
+ if (latestVersion) {
53
+ versions.push({ ...latestVersion, isLatest: true });
54
+ }
55
+ versions.push(...archivedVersions.map((v) => ({ ...v, isLatest: false })));
56
+
57
+ if (versions.length <= 1) return null;
58
+
59
+ return (
60
+ <div className="versions-group">
61
+ <h5>
62
+ <FormattedMessage id="Versions" defaultMessage="Versions" />
63
+ </h5>
64
+ <ul>
65
+ {versions.map((version) => {
66
+ const url = flattenToAppURL(version['@id']);
67
+ const isCurrent = version['@id'] === currentId;
68
+ return (
69
+ <li key={version['@id']}>
70
+ {isCurrent ? (
71
+ <strong>
72
+ {version.title}
73
+ {version.isLatest && (
74
+ <span>
75
+ {' '}
76
+ (
77
+ <FormattedMessage id="latest" defaultMessage="latest" />)
78
+ </span>
79
+ )}
80
+ </strong>
81
+ ) : (
82
+ <Link to={url}>
83
+ {version.title}
84
+ {version.isLatest && (
85
+ <span>
86
+ {' '}
87
+ (
88
+ <FormattedMessage id="latest" defaultMessage="latest" />)
89
+ </span>
90
+ )}
91
+ </Link>
92
+ )}
93
+ </li>
94
+ );
95
+ })}
96
+ </ul>
97
+ </div>
98
+ );
99
+ }
100
+
101
+ export default VersionsGroup;
@@ -0,0 +1,130 @@
1
+ import React from 'react';
2
+ import { MemoryRouter } from 'react-router-dom';
3
+ import configureStore from 'redux-mock-store';
4
+ import '@testing-library/jest-dom/extend-expect';
5
+ import { Provider } from 'react-intl-redux';
6
+ import { render } from '@testing-library/react';
7
+ import VersionsGroup from './VersionsGroup';
8
+
9
+ const mockStore = configureStore();
10
+
11
+ const store = mockStore({
12
+ userSession: { token: '1234' },
13
+ intl: {
14
+ locale: 'en',
15
+ messages: {},
16
+ },
17
+ });
18
+
19
+ describe('VersionsGroup', () => {
20
+ it('returns null when there is only one version', () => {
21
+ const content = {
22
+ '@id': 'http://localhost:3000/my-indicator',
23
+ '@type': 'Indicator',
24
+ review_state: 'published',
25
+ title: 'My Indicator',
26
+ created: '2024-01-01T00:00:00Z',
27
+ relatedItems: [],
28
+ };
29
+ const { container } = render(
30
+ <Provider store={store}>
31
+ <MemoryRouter>
32
+ <VersionsGroup content={content} />
33
+ </MemoryRouter>
34
+ </Provider>,
35
+ );
36
+ expect(container.innerHTML).toBe('');
37
+ });
38
+
39
+ it('renders versions list with published as latest', () => {
40
+ const content = {
41
+ '@id': 'http://localhost:3000/my-indicator',
42
+ '@type': 'Indicator',
43
+ review_state: 'published',
44
+ title: 'My Indicator',
45
+ created: '2024-01-01T00:00:00Z',
46
+ relatedItems: [
47
+ {
48
+ '@id': 'http://localhost:3000/my-indicator-v2',
49
+ review_state: 'archived',
50
+ title: 'My Indicator (Archived)',
51
+ created: '2024-06-01T00:00:00Z',
52
+ },
53
+ ],
54
+ };
55
+ const { container } = render(
56
+ <Provider store={store}>
57
+ <MemoryRouter>
58
+ <VersionsGroup content={content} />
59
+ </MemoryRouter>
60
+ </Provider>,
61
+ );
62
+ expect(container.querySelector('.versions-group')).toBeInTheDocument();
63
+ expect(container.querySelectorAll('li')).toHaveLength(2);
64
+ expect(container.querySelector('strong')).toBeInTheDocument();
65
+ });
66
+
67
+ it('renders versions when viewing an archived copy', () => {
68
+ const content = {
69
+ '@id': 'http://localhost:3000/my-indicator-v2',
70
+ '@type': 'Indicator',
71
+ review_state: 'archived',
72
+ title: 'My Indicator (Archived)',
73
+ created: '2024-06-01T00:00:00Z',
74
+ relatedItems: [
75
+ {
76
+ '@id': 'http://localhost:3000/my-indicator',
77
+ review_state: 'published',
78
+ title: 'My Indicator',
79
+ created: '2024-01-01T00:00:00Z',
80
+ },
81
+ ],
82
+ };
83
+ const { container } = render(
84
+ <Provider store={store}>
85
+ <MemoryRouter>
86
+ <VersionsGroup content={content} />
87
+ </MemoryRouter>
88
+ </Provider>,
89
+ );
90
+ expect(container.querySelector('.versions-group')).toBeInTheDocument();
91
+ expect(container.querySelectorAll('li')).toHaveLength(2);
92
+ const links = container.querySelectorAll('li a');
93
+ expect(links.length).toBeGreaterThanOrEqual(1);
94
+ });
95
+
96
+ it('sorts archived versions by creation date ascending', () => {
97
+ const content = {
98
+ '@id': 'http://localhost:3000/my-indicator',
99
+ '@type': 'Indicator',
100
+ review_state: 'published',
101
+ title: 'My Indicator',
102
+ created: '2024-01-01T00:00:00Z',
103
+ relatedItems: [
104
+ {
105
+ '@id': 'http://localhost:3000/my-indicator-v3',
106
+ review_state: 'archived',
107
+ title: 'My Indicator v3',
108
+ created: '2024-12-01T00:00:00Z',
109
+ },
110
+ {
111
+ '@id': 'http://localhost:3000/my-indicator-v2',
112
+ review_state: 'archived',
113
+ title: 'My Indicator v2',
114
+ created: '2024-06-01T00:00:00Z',
115
+ },
116
+ ],
117
+ };
118
+ const { container } = render(
119
+ <Provider store={store}>
120
+ <MemoryRouter>
121
+ <VersionsGroup content={content} />
122
+ </MemoryRouter>
123
+ </Provider>,
124
+ );
125
+ const items = container.querySelectorAll('li');
126
+ expect(items).toHaveLength(3);
127
+ expect(items[1].textContent).toContain('v2');
128
+ expect(items[2].textContent).toContain('v3');
129
+ });
130
+ });
@@ -58,7 +58,7 @@ export const ACE_COUNTRIES = {
58
58
  IE: 'Ireland',
59
59
  ES: 'Spain',
60
60
  ME: 'Montenegro',
61
- MD: 'Moldova, Republic of',
61
+ MD: 'Republic of Moldova',
62
62
  MC: 'Monaco',
63
63
  IL: 'Israel',
64
64
  RS: 'Serbia',
package/src/index.js CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  TranslationDisclaimer,
10
10
  RedirectToLogin,
11
11
  MissionSignatoryProfileView,
12
+ CreateArchivedCopyButton,
12
13
  ImageWidget,
13
14
  } from '@eeacms/volto-cca-policy/components';
14
15
 
@@ -509,6 +510,10 @@ const applyConfig = (config) => {
509
510
  match: '',
510
511
  component: TranslationDisclaimer,
511
512
  },
513
+ {
514
+ match: '',
515
+ component: CreateArchivedCopyButton,
516
+ },
512
517
  {
513
518
  match: {
514
519
  path: /^.*\/add$/,