@plone/volto 19.0.0-alpha.0 → 19.0.0-alpha.2
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/.eslintignore +1 -0
- package/.eslintrc +2 -0
- package/CHANGELOG.md +63 -2
- package/locales/ca/LC_MESSAGES/volto.po +15 -0
- package/locales/ca.json +1 -1
- package/locales/de/LC_MESSAGES/volto.po +15 -0
- package/locales/de.json +1 -1
- package/locales/en/LC_MESSAGES/volto.po +15 -0
- package/locales/en.json +1 -1
- package/locales/es/LC_MESSAGES/volto.po +15 -0
- package/locales/es.json +1 -1
- package/locales/eu/LC_MESSAGES/volto.po +15 -0
- package/locales/eu.json +1 -1
- package/locales/fi/LC_MESSAGES/volto.po +15 -0
- package/locales/fi.json +1 -1
- package/locales/fr/LC_MESSAGES/volto.po +15 -0
- package/locales/fr.json +1 -1
- package/locales/hi/LC_MESSAGES/volto.po +15 -0
- package/locales/hi.json +1 -1
- package/locales/it/LC_MESSAGES/volto.po +15 -0
- package/locales/it.json +1 -1
- package/locales/ja/LC_MESSAGES/volto.po +15 -0
- package/locales/ja.json +1 -1
- package/locales/nl/LC_MESSAGES/volto.po +15 -0
- package/locales/nl.json +1 -1
- package/locales/pt/LC_MESSAGES/volto.po +15 -0
- package/locales/pt.json +1 -1
- package/locales/pt_BR/LC_MESSAGES/volto.po +15 -0
- package/locales/pt_BR.json +1 -1
- package/locales/ro/LC_MESSAGES/volto.po +15 -0
- package/locales/ro.json +1 -1
- package/locales/ru/LC_MESSAGES/volto.po +15 -0
- package/locales/ru.json +1 -1
- package/locales/volto.pot +15 -0
- package/locales/zh_CN/LC_MESSAGES/volto.po +15 -0
- package/locales/zh_CN.json +1 -1
- package/package.json +15 -8
- package/src/actions/actions/actions.test.js +3 -3
- package/src/actions/addons/addons.test.js +15 -12
- package/src/actions/aliases/aliases.test.js +1 -1
- package/src/actions/querystring/querystring.test.js +2 -2
- package/src/actions/types/types.test.js +1 -1
- package/src/components/manage/Actions/Actions.test.jsx +5 -1
- package/src/components/manage/Add/Add.jsx +22 -20
- package/src/components/manage/Add/Add.test.jsx +6 -3
- package/src/components/manage/Aliases/Aliases.test.jsx +7 -7
- package/src/components/manage/Blocks/Block/BlocksForm.jsx +1 -0
- package/src/components/manage/Blocks/Block/BlocksForm.test.jsx +48 -16
- package/src/components/manage/Blocks/Block/Edit.jsx +2 -1
- package/src/components/manage/Blocks/Block/Settings.test.jsx +5 -1
- package/src/components/manage/Blocks/Block/StyleWrapper.jsx +11 -3
- package/src/components/manage/Blocks/Description/View.test.jsx +1 -1
- package/src/components/manage/Blocks/HTML/Edit.test.jsx +12 -5
- package/src/components/manage/Blocks/HTML/View.test.jsx +1 -1
- package/src/components/manage/Blocks/Image/ImageSidebar.test.jsx +6 -2
- package/src/components/manage/Blocks/LeadImage/LeadImageSidebar.test.jsx +8 -1
- package/src/components/manage/Blocks/Listing/View.test.jsx +3 -1
- package/src/components/manage/Blocks/Maps/MapsSidebar.test.jsx +5 -1
- package/src/components/manage/Blocks/Search/components/DateRangeFacet.test.jsx +13 -7
- package/src/components/manage/Blocks/Search/components/SelectFacet.test.jsx +12 -6
- package/src/components/manage/Blocks/Title/Edit.jsx +8 -2
- package/src/components/manage/Blocks/ToC/variations/DefaultTocRenderer.test.jsx +11 -1
- package/src/components/manage/Blocks/Video/VideoSidebar.test.jsx +5 -1
- package/src/components/manage/ConditionalLink/ConditionalLink.test.tsx +109 -0
- package/src/components/manage/ConditionalLink/ConditionalLink.tsx +36 -0
- package/src/components/manage/Contents/Contents.test.jsx +29 -13
- package/src/components/manage/Contents/ContentsPropertiesModal.test.jsx +5 -1
- package/src/components/manage/Contents/ContentsRenameModal.test.jsx +5 -1
- package/src/components/manage/Contents/ContentsTagsModal.test.jsx +5 -1
- package/src/components/manage/Contents/ContentsWorkflowModal.test.jsx +5 -1
- package/src/components/manage/Contents/__mocks__/index.tsx +16 -0
- package/src/components/manage/Contents/__mocks__/index.vitest.tsx +5 -0
- package/src/components/manage/Controlpanels/AddonsControlpanel.test.jsx +28 -3
- package/src/components/manage/Controlpanels/Aliases.test.jsx +35 -3
- package/src/components/manage/Controlpanels/ContentType.test.jsx +29 -3
- package/src/components/manage/Controlpanels/ContentTypeLayout.test.jsx +4 -2
- package/src/components/manage/Controlpanels/ContentTypes.test.jsx +25 -2
- package/src/components/manage/Controlpanels/Controlpanel.test.jsx +37 -6
- package/src/components/manage/Controlpanels/Controlpanels.test.jsx +47 -3
- package/src/components/manage/Controlpanels/Groups/GroupsControlpanel.test.jsx +15 -9
- package/src/components/manage/Controlpanels/ModerateComments.test.jsx +31 -5
- package/src/components/manage/Controlpanels/Rules/AddRule.test.jsx +13 -4
- package/src/components/manage/Controlpanels/Rules/ConfigureRule.test.jsx +9 -5
- package/src/components/manage/Controlpanels/Rules/EditRule.test.jsx +12 -4
- package/src/components/manage/Controlpanels/Rules/Rules.test.jsx +7 -3
- package/src/components/manage/Controlpanels/UndoControlpanel.test.jsx +33 -4
- package/src/components/manage/Controlpanels/Users/UserGroupMembershipControlPanel.test.jsx +3 -1
- package/src/components/manage/Controlpanels/Users/UsersControlpanel.test.jsx +15 -9
- package/src/components/manage/Delete/Delete.test.jsx +45 -4
- package/src/components/manage/Diff/Diff.test.jsx +15 -6
- package/src/components/manage/Diff/DiffField.test.jsx +12 -6
- package/src/components/manage/Display/Display.test.jsx +17 -6
- package/src/components/manage/Edit/Edit.test.jsx +11 -3
- package/src/components/manage/Form/BlockDataForm.test.jsx +5 -1
- package/src/components/manage/Form/Form.jsx +32 -0
- package/src/components/manage/Form/Form.test.jsx +27 -19
- package/src/components/manage/Form/InlineForm.test.jsx +5 -1
- package/src/components/manage/Form/ModalForm.test.jsx +5 -1
- package/src/components/manage/Form/__mocks__/index.tsx +17 -0
- package/src/components/manage/Form/__mocks__/index.vitest.tsx +73 -0
- package/src/components/manage/History/History.test.jsx +3 -1
- package/src/components/manage/LinksToItem/LinksToItem.test.jsx +6 -4
- package/src/components/manage/MaybeWrap/MaybeWrap.tsx +15 -0
- package/src/components/manage/Multilingual/ManageTranslations.test.jsx +3 -2
- package/src/components/manage/Preferences/ChangePassword.test.jsx +9 -2
- package/src/components/manage/Preferences/PersonalInformation.test.jsx +3 -1
- package/src/components/manage/Preferences/PersonalPreferences.test.jsx +20 -7
- package/src/components/manage/Rules/Rules.test.jsx +6 -3
- package/src/components/manage/Sharing/Sharing.test.jsx +3 -1
- package/src/components/manage/Sidebar/ObjectBrowserNav.test.jsx +3 -3
- package/src/components/manage/Toolbar/More.test.jsx +6 -7
- package/src/components/manage/UniversalLink/UniversalLink.test.jsx +196 -14
- package/src/components/manage/UniversalLink/UniversalLink.tsx +214 -0
- package/src/components/manage/Widgets/ArrayWidget.test.jsx +22 -5
- package/src/components/manage/Widgets/CheckboxGroupWidget.test.jsx +12 -5
- package/src/components/manage/Widgets/DatetimeWidget.test.jsx +21 -6
- package/src/components/manage/Widgets/ImageWidget.jsx +5 -2
- package/src/components/manage/Widgets/NumberWidget.test.jsx +8 -7
- package/src/components/manage/Widgets/ObjectListWidget.jsx +11 -1
- package/src/components/manage/Widgets/ObjectListWidget.test.jsx +18 -8
- package/src/components/manage/Widgets/ObjectWidget.test.jsx +5 -1
- package/src/components/manage/Widgets/RadioGroupWidget.test.jsx +12 -5
- package/src/components/manage/Widgets/RecurrenceWidget/RecurrenceWidget.test.jsx +12 -6
- package/src/components/manage/Widgets/RegistryImageWidget.test.jsx +43 -41
- package/src/components/manage/Widgets/SchemaWidget.test.jsx +12 -5
- package/src/components/manage/Widgets/SchemaWidgetFieldset.test.jsx +12 -5
- package/src/components/manage/Widgets/SelectAutoComplete.test.jsx +12 -6
- package/src/components/manage/Widgets/SelectWidget.jsx +3 -1
- package/src/components/manage/Widgets/SelectWidget.test.jsx +12 -6
- package/src/components/manage/Widgets/TimeWidget.test.jsx +13 -5
- package/src/components/manage/Widgets/TokenWidget.test.jsx +12 -6
- package/src/components/manage/Widgets/VocabularyTermsWidget.test.jsx +18 -9
- package/src/components/manage/Widgets/__mocks__/index.tsx +16 -0
- package/src/components/manage/Widgets/__mocks__/index.vitest.tsx +41 -0
- package/src/components/manage/Workflow/Workflow.test.jsx +17 -7
- package/src/components/theme/App/App.test.jsx +21 -17
- package/src/components/theme/AppExtras/AppExtras.test.jsx +6 -6
- package/src/components/theme/Comments/CommentEditModal.test.jsx +5 -1
- package/src/components/theme/Comments/Comments.test.jsx +29 -12
- package/src/components/theme/ContactForm/ContactForm.test.jsx +8 -4
- package/src/components/theme/Header/Header.test.jsx +19 -13
- package/src/components/theme/Logout/Logout.test.jsx +1 -1
- package/src/components/theme/PasswordReset/PasswordReset.test.jsx +10 -1
- package/src/components/theme/PasswordReset/RequestPasswordReset.test.jsx +5 -1
- package/src/components/theme/Register/Register.test.jsx +5 -1
- package/src/components/theme/Search/Search.test.jsx +6 -4
- package/src/components/theme/TsTest/TsTest.test.tsx +0 -1
- package/src/components/theme/View/EventDatesInfo.test.jsx +12 -5
- package/src/components/theme/View/EventView.test.jsx +12 -5
- package/src/components/theme/View/ListingView.test.jsx +2 -0
- package/src/components/theme/View/SummaryView.test.jsx +10 -0
- package/src/components/theme/View/TabularView.test.jsx +1 -0
- package/src/components/theme/View/View.test.jsx +42 -23
- package/src/helpers/Api/Api.plone.rest.test.js +11 -9
- package/src/helpers/Api/Api.test.js +11 -14
- package/src/helpers/AsyncConnect/AsyncConnect.test.jsx +145 -189
- package/src/helpers/AuthToken/AuthToken.test.js +60 -22
- package/src/helpers/Blocks/Blocks.test.js +1 -1
- package/src/helpers/Html/Html.test.jsx +32 -28
- package/src/helpers/Loadable/__mocks__/Loadable.jsx +16 -1
- package/src/helpers/Loadable/__mocks__/Loadable.vitest.jsx +39 -0
- package/src/helpers/Utils/withSaveAsDraft.jsx +241 -0
- package/src/middleware/Api.test.js +47 -0
- package/src/middleware/api.js +1 -1
- package/src/middleware/storeProtectLoadUtils.test.js +90 -78
- package/test-setup-globals-vitest.js +46 -0
- package/theme/themes/pastanaga/collections/table.overrides +9 -0
- package/theme/themes/pastanaga/extras/main.less +15 -0
- package/tsconfig.declarations.json +12 -1
- package/tsconfig.json +2 -1
- package/types/components/manage/ConditionalLink/ConditionalLink.d.ts +11 -15
- package/types/components/manage/Contents/__mocks__/index.vitest.d.ts +2 -0
- package/types/components/manage/Form/__mocks__/index.vitest.d.ts +8 -0
- package/types/components/manage/MaybeWrap/MaybeWrap.d.ts +7 -5
- package/types/components/manage/UniversalLink/UniversalLink.d.ts +54 -20
- package/types/components/manage/Widgets/__mocks__/index.vitest.d.ts +33 -0
- package/types/helpers/Loadable/__mocks__/Loadable.vitest.d.ts +3 -0
- package/types/helpers/Utils/withSaveAsDraft.d.ts +1 -0
- package/types/react-router-hash-link.d.ts +12 -0
- package/types/routes.d.ts +4 -0
- package/types/server.d.ts +1 -1
- package/vite-plugins/svg.mjs +81 -0
- package/vitest.config.mjs +77 -0
- package/src/components/manage/ConditionalLink/ConditionalLink.jsx +0 -27
- package/src/components/manage/ConditionalLink/ConditionalLink.test.jsx +0 -30
- package/src/components/manage/MaybeWrap/MaybeWrap.jsx +0 -9
- package/src/components/manage/UniversalLink/UniversalLink.jsx +0 -154
- package/src/components/manage/Widgets/FileWidget.test.jsx +0 -91
|
@@ -1,60 +1,94 @@
|
|
|
1
1
|
import Cookies from 'universal-cookie';
|
|
2
2
|
import jwt from 'jsonwebtoken';
|
|
3
3
|
import jwtDecode from 'jwt-decode';
|
|
4
|
-
|
|
5
4
|
import { getAuthToken, persistAuthToken } from './AuthToken';
|
|
6
5
|
|
|
7
|
-
|
|
6
|
+
vi.mock('universal-cookie', () => {
|
|
8
7
|
const mCookie = {
|
|
9
|
-
get:
|
|
10
|
-
.fn(() =>
|
|
11
|
-
.mockImplementationOnce(() => null),
|
|
12
|
-
remove:
|
|
13
|
-
set:
|
|
8
|
+
get: vi
|
|
9
|
+
.fn(() => jwt.sign({ exp: 1 }, 'secret'))
|
|
10
|
+
.mockImplementationOnce(() => null),
|
|
11
|
+
remove: vi.fn(),
|
|
12
|
+
set: vi.fn(),
|
|
13
|
+
};
|
|
14
|
+
return {
|
|
15
|
+
default: vi.fn(() => mCookie),
|
|
14
16
|
};
|
|
15
|
-
return jest.fn(() => mCookie);
|
|
16
17
|
});
|
|
17
18
|
|
|
18
19
|
describe('AuthToken', () => {
|
|
20
|
+
let cookies;
|
|
21
|
+
let mockStore;
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
cookies = new Cookies();
|
|
25
|
+
vi.clearAllMocks();
|
|
26
|
+
|
|
27
|
+
mockStore = {
|
|
28
|
+
subscribe: vi.fn(),
|
|
29
|
+
dispatch: vi.fn(),
|
|
30
|
+
getState: vi.fn(() => ({
|
|
31
|
+
userSession: {
|
|
32
|
+
token: null,
|
|
33
|
+
},
|
|
34
|
+
router: {
|
|
35
|
+
location: {
|
|
36
|
+
pathname: '/current-path',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
})),
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
|
|
19
43
|
describe('anonymousAuthToken', () => {
|
|
20
44
|
it('avoid unnecessary removing auth token', () => {
|
|
21
|
-
const cookies = new Cookies();
|
|
22
45
|
const store = {
|
|
23
|
-
|
|
24
|
-
getState:
|
|
46
|
+
...mockStore,
|
|
47
|
+
getState: vi.fn(() => ({
|
|
25
48
|
userSession: {
|
|
26
49
|
token: null,
|
|
27
50
|
},
|
|
51
|
+
router: {
|
|
52
|
+
location: {
|
|
53
|
+
pathname: '/current-path',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
28
56
|
})),
|
|
29
57
|
};
|
|
58
|
+
|
|
30
59
|
persistAuthToken(store);
|
|
31
|
-
expect(cookies.remove).not.
|
|
60
|
+
expect(cookies.remove).not.toHaveBeenCalledWith('auth_token', {
|
|
61
|
+
path: '/',
|
|
62
|
+
});
|
|
32
63
|
});
|
|
33
64
|
});
|
|
34
65
|
|
|
35
66
|
describe('getAuthToken', () => {
|
|
36
67
|
it('can get the auth token', () => {
|
|
37
|
-
const cookies = new Cookies();
|
|
38
68
|
getAuthToken();
|
|
39
|
-
expect(cookies.get).
|
|
69
|
+
expect(cookies.get).toHaveBeenCalledWith('auth_token');
|
|
40
70
|
});
|
|
41
71
|
});
|
|
42
72
|
|
|
43
73
|
describe('persistAuthToken', () => {
|
|
44
74
|
it('can set a new auth token', () => {
|
|
45
|
-
const cookies = new Cookies();
|
|
46
75
|
const store = {
|
|
47
|
-
|
|
48
|
-
getState:
|
|
76
|
+
...mockStore,
|
|
77
|
+
getState: vi.fn(() => ({
|
|
49
78
|
userSession: {
|
|
50
79
|
token: jwt.sign({ exp: 2 }, 'secret'),
|
|
51
80
|
},
|
|
81
|
+
router: {
|
|
82
|
+
location: {
|
|
83
|
+
pathname: '/current-path',
|
|
84
|
+
},
|
|
85
|
+
},
|
|
52
86
|
})),
|
|
53
87
|
};
|
|
54
88
|
const { token } = store.getState().userSession;
|
|
55
89
|
|
|
56
90
|
persistAuthToken(store);
|
|
57
|
-
expect(cookies.set).
|
|
91
|
+
expect(cookies.set).toHaveBeenCalledWith('auth_token', token, {
|
|
58
92
|
path: '/',
|
|
59
93
|
expires: new Date(jwtDecode(token).exp * 1000),
|
|
60
94
|
secure: false,
|
|
@@ -62,18 +96,22 @@ describe('AuthToken', () => {
|
|
|
62
96
|
});
|
|
63
97
|
|
|
64
98
|
it('can remove an auth token', () => {
|
|
65
|
-
const cookies = new Cookies();
|
|
66
99
|
const store = {
|
|
67
|
-
|
|
68
|
-
getState:
|
|
100
|
+
...mockStore,
|
|
101
|
+
getState: vi.fn(() => ({
|
|
69
102
|
userSession: {
|
|
70
103
|
token: null,
|
|
71
104
|
},
|
|
105
|
+
router: {
|
|
106
|
+
location: {
|
|
107
|
+
pathname: '/current-path',
|
|
108
|
+
},
|
|
109
|
+
},
|
|
72
110
|
})),
|
|
73
111
|
};
|
|
74
112
|
|
|
75
113
|
persistAuthToken(store);
|
|
76
|
-
expect(cookies.remove).
|
|
114
|
+
expect(cookies.remove).toHaveBeenCalledWith('auth_token', { path: '/' });
|
|
77
115
|
});
|
|
78
116
|
});
|
|
79
117
|
});
|
|
@@ -3,36 +3,40 @@ import renderer from 'react-test-renderer';
|
|
|
3
3
|
import config from '@plone/volto/registry';
|
|
4
4
|
import Html from './Html';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
6
|
+
vi.mock('../Helmet/Helmet', () => ({
|
|
7
|
+
default: {
|
|
8
|
+
rewind: () => ({
|
|
9
|
+
base: {
|
|
10
|
+
toComponent: () => '',
|
|
11
|
+
},
|
|
12
|
+
title: {
|
|
13
|
+
toComponent: () => '',
|
|
14
|
+
},
|
|
15
|
+
meta: {
|
|
16
|
+
toComponent: () => '',
|
|
17
|
+
},
|
|
18
|
+
link: {
|
|
19
|
+
toComponent: () => '',
|
|
20
|
+
},
|
|
21
|
+
script: {
|
|
22
|
+
toComponent: () => '',
|
|
23
|
+
},
|
|
24
|
+
style: {
|
|
25
|
+
toComponent: () => '',
|
|
26
|
+
},
|
|
27
|
+
htmlAttributes: {
|
|
28
|
+
toComponent: () => ({
|
|
29
|
+
lang: 'en',
|
|
30
|
+
}),
|
|
31
|
+
},
|
|
32
|
+
}),
|
|
33
|
+
},
|
|
32
34
|
}));
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
vi.mock('../BodyClass/BodyClass', () => ({
|
|
37
|
+
default: {
|
|
38
|
+
rewind: () => ['class1', 'class2'],
|
|
39
|
+
},
|
|
36
40
|
}));
|
|
37
41
|
|
|
38
42
|
config.settings = {};
|
|
@@ -1,4 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
/* TODO: When the Volto Team removes Jest configuration support from Volto core, update this file with the Vitest version of the mock.
|
|
2
|
+
Then, in the tests, we need to replace:
|
|
3
|
+
|
|
4
|
+
vi.mock('@plone/volto/helpers/Loadable/Loadable', async () => {
|
|
5
|
+
return await import(
|
|
6
|
+
'@plone/volto/helpers/Loadable/__mocks__/Loadable.vitest.jsx'
|
|
7
|
+
);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
with the following:
|
|
11
|
+
|
|
12
|
+
vi.mock('@plone/volto/helpers/Loadable/Loadable');
|
|
13
|
+
|
|
14
|
+
Finally, remove this comment.
|
|
15
|
+
*/
|
|
16
|
+
|
|
2
17
|
import config from '@plone/volto/registry';
|
|
3
18
|
const loadables = config.settings.loadables;
|
|
4
19
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import config from '@plone/volto/registry';
|
|
3
|
+
const loadables = config.settings.loadables;
|
|
4
|
+
|
|
5
|
+
let mockAllLoadables = Object.create(null);
|
|
6
|
+
|
|
7
|
+
export const __setLoadables = async () => {
|
|
8
|
+
if (Object.keys(mockAllLoadables).length > 0) return;
|
|
9
|
+
const resolved = await Promise.all(
|
|
10
|
+
Object.keys(loadables).map(async (n) => {
|
|
11
|
+
const lib = await Promise.resolve(loadables[n].load());
|
|
12
|
+
return [n, { current: lib }];
|
|
13
|
+
}),
|
|
14
|
+
);
|
|
15
|
+
resolved.forEach(([name, { current }]) => {
|
|
16
|
+
mockAllLoadables[name] = current;
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// TODO: filter mockAllLoadables
|
|
21
|
+
export const injectLazyLibs = vi.fn().mockImplementation(function ([
|
|
22
|
+
libraries,
|
|
23
|
+
]) {
|
|
24
|
+
return vi.fn((WrappedComponent) =>
|
|
25
|
+
vi.fn((props) => {
|
|
26
|
+
return <WrappedComponent {...props} {...mockAllLoadables} />;
|
|
27
|
+
}),
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export const preloadLazyLibs = vi.fn().mockImplementation(function ([
|
|
32
|
+
libraries,
|
|
33
|
+
]) {
|
|
34
|
+
return vi.fn((WrappedComponent) =>
|
|
35
|
+
vi.fn((props) => {
|
|
36
|
+
return <WrappedComponent {...props} />;
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
});
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import hoistNonReactStatics from 'hoist-non-react-statics';
|
|
3
|
+
import isEqual from 'react-fast-compare';
|
|
4
|
+
import { toast } from 'react-toastify';
|
|
5
|
+
import Icon from '@plone/volto/components/theme/Icon/Icon';
|
|
6
|
+
import Toast from '@plone/volto/components/manage/Toast/Toast';
|
|
7
|
+
import { Button } from 'semantic-ui-react';
|
|
8
|
+
import checkSVG from '@plone/volto/icons/check.svg';
|
|
9
|
+
import clearSVG from '@plone/volto/icons/clear.svg';
|
|
10
|
+
import { useIntl, defineMessages } from 'react-intl';
|
|
11
|
+
import { useLocation } from 'react-router-dom';
|
|
12
|
+
|
|
13
|
+
const messages = defineMessages({
|
|
14
|
+
autoSaveFound: {
|
|
15
|
+
id: 'Autosaved content found',
|
|
16
|
+
defaultMessage: 'Autosaved content found',
|
|
17
|
+
},
|
|
18
|
+
loadData: {
|
|
19
|
+
id: 'Do you want to restore your autosaved content?',
|
|
20
|
+
defaultMessage: 'Do you want to restore your autosaved content?',
|
|
21
|
+
},
|
|
22
|
+
loadExpiredData: {
|
|
23
|
+
id: "Another person edited this content, and it's currently displayed. Do you want to replace it with your autosaved content?",
|
|
24
|
+
defaultMessage:
|
|
25
|
+
"Another person edited this content, and it's currently displayed. Do you want to replace it with your autosaved content?",
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
function getDisplayName(WrappedComponent) {
|
|
30
|
+
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const mapSchemaToData = (schema, data) => {
|
|
34
|
+
if (!data) return {};
|
|
35
|
+
const dataKeys = Object.keys(data);
|
|
36
|
+
return Object.assign(
|
|
37
|
+
{},
|
|
38
|
+
...Object.keys(schema.properties)
|
|
39
|
+
.filter((k) => dataKeys.includes(k))
|
|
40
|
+
.map((k) => ({ [k]: data[k] })),
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// will be used to avoid using the first mount call if there is a second call
|
|
45
|
+
let mountTime;
|
|
46
|
+
|
|
47
|
+
const getFormId = (props, location) => {
|
|
48
|
+
const { type, pathname = location.pathname, isEditForm, schema } = props;
|
|
49
|
+
const id = isEditForm
|
|
50
|
+
? ['form', type, pathname].join('-')
|
|
51
|
+
: type
|
|
52
|
+
? ['form', pathname, type].join('-')
|
|
53
|
+
: schema?.properties?.comment
|
|
54
|
+
? ['form', pathname, 'comment'].join('-')
|
|
55
|
+
: ['form', pathname].join('-');
|
|
56
|
+
|
|
57
|
+
return id;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Toast content that has OK and Cancel buttons
|
|
62
|
+
* @param {function} onUpdate
|
|
63
|
+
* @param {function} onClose
|
|
64
|
+
* @param {string} userMessage
|
|
65
|
+
* @returns
|
|
66
|
+
*/
|
|
67
|
+
const ConfirmAutoSave = ({ onUpdate, onClose, userMessage }) => {
|
|
68
|
+
const handleClickOK = () => onUpdate();
|
|
69
|
+
const handleClickCancel = () => onClose();
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div className="toast-box-center">
|
|
73
|
+
<div>{userMessage}</div>
|
|
74
|
+
<Button
|
|
75
|
+
icon
|
|
76
|
+
aria-label="Unchecked"
|
|
77
|
+
className="save toast-box"
|
|
78
|
+
onClick={handleClickOK}
|
|
79
|
+
>
|
|
80
|
+
<Icon
|
|
81
|
+
name={checkSVG}
|
|
82
|
+
size="24px"
|
|
83
|
+
className="circled toast-box-blue-icon"
|
|
84
|
+
/>
|
|
85
|
+
</Button>
|
|
86
|
+
<Button
|
|
87
|
+
icon
|
|
88
|
+
aria-label="Unchecked"
|
|
89
|
+
className="save toast-box"
|
|
90
|
+
onClick={handleClickCancel}
|
|
91
|
+
>
|
|
92
|
+
<Icon
|
|
93
|
+
name={clearSVG}
|
|
94
|
+
size="24px"
|
|
95
|
+
className="circled toast-box-blue-icon"
|
|
96
|
+
/>
|
|
97
|
+
</Button>
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Will remove localStorage item using debounce
|
|
104
|
+
* @param {string} id
|
|
105
|
+
* @param {number} timerForDeletion
|
|
106
|
+
*/
|
|
107
|
+
const clearStorage = (id, timerForDeletion) => {
|
|
108
|
+
timerForDeletion.current && clearTimeout(timerForDeletion.current);
|
|
109
|
+
timerForDeletion.current = setTimeout(() => {
|
|
110
|
+
localStorage.removeItem(id);
|
|
111
|
+
}, 500);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Stale if server date is more recent
|
|
116
|
+
* @param {string} serverModifiedDate
|
|
117
|
+
* @param {string} autoSaveDate
|
|
118
|
+
* @returns {Boolean}
|
|
119
|
+
*/
|
|
120
|
+
const autoSaveFoundIsStale = (serverModifiedDate, autoSaveDate) => {
|
|
121
|
+
const result = !serverModifiedDate
|
|
122
|
+
? false
|
|
123
|
+
: new Date(serverModifiedDate) > new Date(autoSaveDate);
|
|
124
|
+
return result;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const draftApi = (id, schema, timer, timerForDeletion, intl) => ({
|
|
128
|
+
// - since Add Content Type will call componentDidMount twice, we will
|
|
129
|
+
// use the second call (using debounce)- the first will ignore any setState comands;
|
|
130
|
+
// - Delete local data only if user confirms Cancel
|
|
131
|
+
// - Will tell user that it has local stored data, even if its less recent than the server data
|
|
132
|
+
checkSavedDraft(state, updateCallback) {
|
|
133
|
+
if (!schema) return;
|
|
134
|
+
const saved = localStorage.getItem(id);
|
|
135
|
+
|
|
136
|
+
if (saved && Object.keys(JSON.parse(saved)).length > 1) {
|
|
137
|
+
const formData = mapSchemaToData(schema, state);
|
|
138
|
+
// includes autoSaveDate
|
|
139
|
+
const foundSavedData = JSON.parse(saved);
|
|
140
|
+
// includes only form data found in schema (no autoSaveDate)
|
|
141
|
+
const foundSavedSchemaData = mapSchemaToData(schema, foundSavedData);
|
|
142
|
+
|
|
143
|
+
if (!isEqual(formData, foundSavedSchemaData)) {
|
|
144
|
+
// eslint-disable-next-line no-alert
|
|
145
|
+
// cancel existing setTimeout to avoid using first call if
|
|
146
|
+
// successive calls are made
|
|
147
|
+
mountTime && clearTimeout(mountTime);
|
|
148
|
+
mountTime = setTimeout(() => {
|
|
149
|
+
toast.info(
|
|
150
|
+
<Toast
|
|
151
|
+
position="top-right"
|
|
152
|
+
info
|
|
153
|
+
autoClose={false}
|
|
154
|
+
title={intl.formatMessage(messages.autoSaveFound)}
|
|
155
|
+
content={
|
|
156
|
+
<ConfirmAutoSave
|
|
157
|
+
onUpdate={() => updateCallback(foundSavedSchemaData)}
|
|
158
|
+
onClose={() => clearStorage(id, timerForDeletion)}
|
|
159
|
+
userMessage={
|
|
160
|
+
autoSaveFoundIsStale(
|
|
161
|
+
state.modified,
|
|
162
|
+
foundSavedData.autoSaveDate,
|
|
163
|
+
)
|
|
164
|
+
? intl.formatMessage(messages.loadExpiredData)
|
|
165
|
+
: intl.formatMessage(messages.loadData)
|
|
166
|
+
}
|
|
167
|
+
/>
|
|
168
|
+
}
|
|
169
|
+
/>,
|
|
170
|
+
);
|
|
171
|
+
}, 300);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
// use debounce mode
|
|
176
|
+
onSaveDraft(state) {
|
|
177
|
+
if (!schema) return;
|
|
178
|
+
timer.current && clearTimeout(timer.current);
|
|
179
|
+
timer.current = setTimeout(() => {
|
|
180
|
+
const formData = mapSchemaToData(schema, state);
|
|
181
|
+
const saved = localStorage.getItem(id);
|
|
182
|
+
const newData = JSON.parse(saved);
|
|
183
|
+
|
|
184
|
+
localStorage.setItem(
|
|
185
|
+
id,
|
|
186
|
+
JSON.stringify({
|
|
187
|
+
...newData,
|
|
188
|
+
...formData,
|
|
189
|
+
autoSaveDate: new Date(),
|
|
190
|
+
}),
|
|
191
|
+
);
|
|
192
|
+
}, 300);
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
onCancelDraft() {
|
|
196
|
+
if (!schema) return;
|
|
197
|
+
clearStorage(id, timerForDeletion);
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
export default function withSaveAsDraft(options) {
|
|
202
|
+
const { forwardRef } = options;
|
|
203
|
+
|
|
204
|
+
return (WrappedComponent) => {
|
|
205
|
+
function WithSaveAsDraft(props) {
|
|
206
|
+
const { schema } = props;
|
|
207
|
+
const intl = useIntl();
|
|
208
|
+
const location = useLocation();
|
|
209
|
+
const id = getFormId(props, location);
|
|
210
|
+
const timmeRef = React.useRef();
|
|
211
|
+
const timmerForDeletionRef = React.useRef();
|
|
212
|
+
const api = React.useMemo(
|
|
213
|
+
() => draftApi(id, schema, timmeRef, timmerForDeletionRef, intl),
|
|
214
|
+
[id, schema, timmeRef, timmerForDeletionRef, intl],
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
return (
|
|
218
|
+
<WrappedComponent
|
|
219
|
+
{...props}
|
|
220
|
+
{...api}
|
|
221
|
+
ref={forwardRef ? props.forwardedRef : null}
|
|
222
|
+
/>
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
WithSaveAsDraft.displayName = `WithSaveAsDraft(${getDisplayName(
|
|
227
|
+
WrappedComponent,
|
|
228
|
+
)})`;
|
|
229
|
+
|
|
230
|
+
if (forwardRef) {
|
|
231
|
+
return hoistNonReactStatics(
|
|
232
|
+
React.forwardRef((props, ref) => (
|
|
233
|
+
<WithSaveAsDraft {...props} forwardedRef={ref} />
|
|
234
|
+
)),
|
|
235
|
+
WrappedComponent,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return hoistNonReactStatics(WithSaveAsDraft, WrappedComponent);
|
|
240
|
+
};
|
|
241
|
+
}
|
|
@@ -282,4 +282,51 @@ describe('api middleware helpers', () => {
|
|
|
282
282
|
'/de/mypage?expand=navigation&expand.navigation.coolness=1&expand.navigation.depth=3&someotherquery=1&someotherquery=2',
|
|
283
283
|
);
|
|
284
284
|
});
|
|
285
|
+
|
|
286
|
+
it('addExpandersToPath - inherit expander merged using querystring as a function', () => {
|
|
287
|
+
config.settings.apiExpanders = [
|
|
288
|
+
{
|
|
289
|
+
match: '/',
|
|
290
|
+
GET_CONTENT: ['navigation'],
|
|
291
|
+
querystring: {
|
|
292
|
+
'expand.navigation.depth': 3,
|
|
293
|
+
'expand.navigation.coolness': 1,
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
match: '/',
|
|
298
|
+
GET_CONTENT: ['inherit'],
|
|
299
|
+
querystring: {
|
|
300
|
+
'expand.inherit.behaviors':
|
|
301
|
+
'voltolighttheme.header,voltolighttheme.theme,voltolighttheme.footer',
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
match: '/',
|
|
306
|
+
GET_CONTENT: ['inherit'],
|
|
307
|
+
querystring: (config, querystring) => {
|
|
308
|
+
if (querystring['expand.inherit.behaviors']) {
|
|
309
|
+
return {
|
|
310
|
+
'expand.inherit.behaviors': querystring[
|
|
311
|
+
'expand.inherit.behaviors'
|
|
312
|
+
].concat(',', 'plonegovbr.socialmedia.settings'),
|
|
313
|
+
};
|
|
314
|
+
} else {
|
|
315
|
+
return {
|
|
316
|
+
'expand.inherit.behaviors': 'plonegovbr.socialmedia.settings',
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
];
|
|
322
|
+
|
|
323
|
+
const result = addExpandersToPath(
|
|
324
|
+
'/de/mypage?someotherquery=1&someotherquery=2',
|
|
325
|
+
GET_CONTENT,
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
expect(result).toEqual(
|
|
329
|
+
'/de/mypage?expand=navigation,inherit&expand.inherit.behaviors=voltolighttheme.header,voltolighttheme.theme,voltolighttheme.footer,plonegovbr.socialmedia.settings&expand.navigation.coolness=1&expand.navigation.depth=3&someotherquery=1&someotherquery=2',
|
|
330
|
+
);
|
|
331
|
+
});
|
|
285
332
|
});
|
package/src/middleware/api.js
CHANGED
|
@@ -75,7 +75,7 @@ export function addExpandersToPath(path, type, isAnonymous) {
|
|
|
75
75
|
// The querystring accepts being a function to be able to take other
|
|
76
76
|
// config parameters
|
|
77
77
|
if (typeof querystring === 'function') {
|
|
78
|
-
querystring = querystring(config);
|
|
78
|
+
querystring = querystring(config, acc);
|
|
79
79
|
}
|
|
80
80
|
return { ...acc, ...querystring };
|
|
81
81
|
}, {});
|