@eeacms/volto-slate-footnote 6.3.0 → 7.0.0
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/.env +3 -0
- package/.eslintrc.js +65 -0
- package/CHANGELOG.md +9 -1
- package/Jenkinsfile +144 -46
- package/Makefile +11 -8
- package/cypress/support/e2e.js +0 -4
- package/jest-addon.config.js +19 -4
- package/jest.setup.js +65 -0
- package/package.json +2 -1
- package/src/editor/MultiSelectSearchWidget.jsx +132 -132
- package/src/editor/SearchWidget.test.jsx +66 -6
- package/src/editor/styles.less +1 -1
- package/src/editor/utils.test.js +2 -2
- package/.project.eslintrc.js +0 -48
- package/src/editor/__snapshots__/SearchWidget.test.jsx.snap +0 -72
package/.env
ADDED
package/.eslintrc.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const projectRootPath = fs.realpathSync(__dirname + '/../../../');
|
|
4
|
+
|
|
5
|
+
let voltoPath = path.join(projectRootPath, 'node_modules/@plone/volto');
|
|
6
|
+
let configFile;
|
|
7
|
+
if (fs.existsSync(`${projectRootPath}/tsconfig.json`))
|
|
8
|
+
configFile = `${projectRootPath}/tsconfig.json`;
|
|
9
|
+
else if (fs.existsSync(`${projectRootPath}/jsconfig.json`))
|
|
10
|
+
configFile = `${projectRootPath}/jsconfig.json`;
|
|
11
|
+
|
|
12
|
+
if (configFile) {
|
|
13
|
+
const jsConfig = require(configFile).compilerOptions;
|
|
14
|
+
const pathsConfig = jsConfig.paths;
|
|
15
|
+
if (pathsConfig['@plone/volto'])
|
|
16
|
+
voltoPath = `./${jsConfig.baseUrl}/${pathsConfig['@plone/volto'][0]}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const AddonConfigurationRegistry = require(`${voltoPath}/addon-registry.js`);
|
|
20
|
+
const reg = new AddonConfigurationRegistry(projectRootPath);
|
|
21
|
+
|
|
22
|
+
// Extends ESlint configuration for adding the aliases to `src` directories in Volto addons
|
|
23
|
+
const addonAliases = Object.keys(reg.packages).map((o) => [
|
|
24
|
+
o,
|
|
25
|
+
reg.packages[o].modulePath,
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
const addonExtenders = reg.getEslintExtenders().map((m) => require(m));
|
|
29
|
+
|
|
30
|
+
const defaultConfig = {
|
|
31
|
+
extends: `${voltoPath}/.eslintrc`,
|
|
32
|
+
settings: {
|
|
33
|
+
'import/resolver': {
|
|
34
|
+
alias: {
|
|
35
|
+
map: [
|
|
36
|
+
['@plone/volto', '@plone/volto/src'],
|
|
37
|
+
['@plone/volto-slate', '@plone/volto/packages/volto-slate/src'],
|
|
38
|
+
...addonAliases,
|
|
39
|
+
['@package', `${__dirname}/src`],
|
|
40
|
+
['@root', `${__dirname}/src`],
|
|
41
|
+
['~', `${__dirname}/src`],
|
|
42
|
+
],
|
|
43
|
+
extensions: ['.js', '.jsx', '.json'],
|
|
44
|
+
},
|
|
45
|
+
'babel-plugin-root-import': {
|
|
46
|
+
rootPathSuffix: 'src',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
rules: {
|
|
51
|
+
'react/jsx-no-target-blank': [
|
|
52
|
+
'error',
|
|
53
|
+
{
|
|
54
|
+
allowReferrer: true,
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const config = addonExtenders.reduce(
|
|
61
|
+
(acc, extender) => extender.modify(acc),
|
|
62
|
+
defaultConfig,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
module.exports = config;
|
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,15 @@ 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
|
-
### [
|
|
7
|
+
### [7.0.0](https://github.com/eea/volto-slate-footnote/compare/6.3.0...7.0.0) - 22 April 2024
|
|
8
|
+
|
|
9
|
+
#### :rocket: New Features
|
|
10
|
+
|
|
11
|
+
- feat: Release 7.0.0 - Volto 17 support [alin - [`9d04cb5`](https://github.com/eea/volto-slate-footnote/commit/9d04cb54fca5e925785511883573fab7a5ff4c76)]
|
|
12
|
+
|
|
13
|
+
#### :hammer_and_wrench: Others
|
|
14
|
+
|
|
15
|
+
### [6.3.0](https://github.com/eea/volto-slate-footnote/compare/6.2.3...6.3.0) - 28 March 2024
|
|
8
16
|
|
|
9
17
|
#### :hammer_and_wrench: Others
|
|
10
18
|
|
package/Jenkinsfile
CHANGED
|
@@ -9,11 +9,12 @@ pipeline {
|
|
|
9
9
|
environment {
|
|
10
10
|
GIT_NAME = "volto-slate-footnote"
|
|
11
11
|
NAMESPACE = "@eeacms"
|
|
12
|
-
SONARQUBE_TAGS = "volto.eea.europa.eu,biodiversity.europa.eu,www.eea.europa.eu-ims,climate-energy.eea.europa.eu,
|
|
12
|
+
SONARQUBE_TAGS = "volto.eea.europa.eu,biodiversity.europa.eu,www.eea.europa.eu-ims,climate-energy.eea.europa.eu,forest.eea.europa.eu,clms.land.copernicus.eu,industry.eea.europa.eu,water.europa.eu-freshwater,demo-www.eea.europa.eu,clmsdemo.devel6cph.eea.europa.eu,water.europa.eu-marine,climate-adapt.eea.europa.eu,climate-advisory-board.devel4cph.eea.europa.eu,climate-advisory-board.europa.eu,www.eea.europa.eu-en,insitu-frontend.eionet.europa.eu,insitu.copernicus.eu"
|
|
13
13
|
DEPENDENCIES = ""
|
|
14
14
|
BACKEND_PROFILES = "eea.kitkat:testing"
|
|
15
15
|
BACKEND_ADDONS = ""
|
|
16
|
-
VOLTO = "
|
|
16
|
+
VOLTO = "17"
|
|
17
|
+
VOLTO16_BREAKING_CHANGES = "no"
|
|
17
18
|
IMAGE_NAME = BUILD_TAG.toLowerCase()
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -44,6 +45,7 @@ pipeline {
|
|
|
44
45
|
}
|
|
45
46
|
steps {
|
|
46
47
|
script {
|
|
48
|
+
checkout scm
|
|
47
49
|
withCredentials([string(credentialsId: 'eea-jenkins-token', variable: 'GITHUB_TOKEN')]) {
|
|
48
50
|
check_result = sh script: '''docker run --pull always -i --rm --name="$IMAGE_NAME-gitflow-check" -e GIT_TOKEN="$GITHUB_TOKEN" -e GIT_BRANCH="$BRANCH_NAME" -e GIT_ORG="$GIT_ORG" -e GIT_NAME="$GIT_NAME" eeacms/gitflow /check_if_testing_needed.sh''', returnStatus: true
|
|
49
51
|
|
|
@@ -61,7 +63,6 @@ pipeline {
|
|
|
61
63
|
allOf {
|
|
62
64
|
not { environment name: 'CHANGE_ID', value: '' }
|
|
63
65
|
environment name: 'CHANGE_TARGET', value: 'develop'
|
|
64
|
-
environment name: 'SKIP_TESTS', value: ''
|
|
65
66
|
}
|
|
66
67
|
allOf {
|
|
67
68
|
environment name: 'CHANGE_ID', value: ''
|
|
@@ -69,25 +70,27 @@ pipeline {
|
|
|
69
70
|
not { changelog '.*^Automated release [0-9\\.]+$' }
|
|
70
71
|
branch 'master'
|
|
71
72
|
}
|
|
72
|
-
environment name: 'SKIP_TESTS', value: ''
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
parallel {
|
|
77
|
+
|
|
78
|
+
stage('Volto 17') {
|
|
79
|
+
agent { node { label 'docker-1.13'} }
|
|
80
|
+
stages {
|
|
81
|
+
stage('Build test image') {
|
|
82
|
+
steps {
|
|
83
|
+
sh '''docker build --pull --build-arg="VOLTO_VERSION=$VOLTO" --build-arg="ADDON_NAME=$NAMESPACE/$GIT_NAME" --build-arg="ADDON_PATH=$GIT_NAME" . -t $IMAGE_NAME-frontend'''
|
|
84
|
+
}
|
|
81
85
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
when {
|
|
86
|
+
|
|
87
|
+
stage('Fix code') {
|
|
88
|
+
when {
|
|
86
89
|
environment name: 'CHANGE_ID', value: ''
|
|
87
90
|
not { branch 'master' }
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
}
|
|
92
|
+
steps {
|
|
93
|
+
script {
|
|
91
94
|
fix_result = sh(script: '''docker run --name="$IMAGE_NAME-fix" --entrypoint=make --workdir=/app/src/addons/$GIT_NAME $IMAGE_NAME-frontend ci-fix''', returnStatus: true)
|
|
92
95
|
sh '''docker cp $IMAGE_NAME-fix:/app/src/addons/$GIT_NAME/src .'''
|
|
93
96
|
sh '''docker rm -v $IMAGE_NAME-fix'''
|
|
@@ -105,31 +108,31 @@ pipeline {
|
|
|
105
108
|
sh '''exit 1'''
|
|
106
109
|
}
|
|
107
110
|
}
|
|
111
|
+
}
|
|
108
112
|
}
|
|
109
|
-
}
|
|
110
113
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
+
stage('ES lint') {
|
|
115
|
+
when { environment name: 'SKIP_TESTS', value: '' }
|
|
116
|
+
steps {
|
|
117
|
+
sh '''docker run --rm --name="$IMAGE_NAME-eslint" --entrypoint=make --workdir=/app/src/addons/$GIT_NAME $IMAGE_NAME-frontend lint'''
|
|
118
|
+
}
|
|
114
119
|
}
|
|
115
|
-
}
|
|
116
120
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
121
|
+
stage('Style lint') {
|
|
122
|
+
when { environment name: 'SKIP_TESTS', value: '' }
|
|
123
|
+
steps {
|
|
124
|
+
sh '''docker run --rm --name="$IMAGE_NAME-stylelint" --entrypoint=make --workdir=/app/src/addons/$GIT_NAME $IMAGE_NAME-frontend stylelint'''
|
|
125
|
+
}
|
|
120
126
|
}
|
|
121
|
-
}
|
|
122
127
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
128
|
+
stage('Prettier') {
|
|
129
|
+
when { environment name: 'SKIP_TESTS', value: '' }
|
|
130
|
+
steps {
|
|
131
|
+
sh '''docker run --rm --name="$IMAGE_NAME-prettier" --entrypoint=make --workdir=/app/src/addons/$GIT_NAME $IMAGE_NAME-frontend prettier'''
|
|
132
|
+
}
|
|
126
133
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
stage('Coverage Tests') {
|
|
130
|
-
parallel {
|
|
131
|
-
|
|
132
|
-
stage('Unit tests') {
|
|
134
|
+
stage('Unit tests') {
|
|
135
|
+
when { environment name: 'SKIP_TESTS', value: '' }
|
|
133
136
|
steps {
|
|
134
137
|
script {
|
|
135
138
|
try {
|
|
@@ -155,17 +158,24 @@ pipeline {
|
|
|
155
158
|
}
|
|
156
159
|
}
|
|
157
160
|
}
|
|
158
|
-
|
|
161
|
+
}
|
|
159
162
|
|
|
160
|
-
|
|
163
|
+
stage('Integration tests') {
|
|
164
|
+
when { environment name: 'SKIP_TESTS', value: '' }
|
|
161
165
|
steps {
|
|
162
166
|
script {
|
|
163
167
|
try {
|
|
164
168
|
sh '''docker run --pull always --rm -d --name="$IMAGE_NAME-plone" -e SITE="Plone" -e PROFILES="$BACKEND_PROFILES" -e ADDONS="$BACKEND_ADDONS" eeacms/plone-backend'''
|
|
165
|
-
sh '''docker run -d --shm-size=
|
|
169
|
+
sh '''docker run -d --shm-size=4g --link $IMAGE_NAME-plone:plone --name="$IMAGE_NAME-cypress" -e "RAZZLE_INTERNAL_API_PATH=http://plone:8080/Plone" --entrypoint=make --workdir=/app/src/addons/$GIT_NAME $IMAGE_NAME-frontend start-ci'''
|
|
170
|
+
frontend = sh script:'''docker exec --workdir=/app/src/addons/${GIT_NAME} $IMAGE_NAME-cypress make check-ci''', returnStatus: true
|
|
171
|
+
if ( frontend != 0 ) {
|
|
172
|
+
sh '''docker logs $IMAGE_NAME-cypress; exit 1'''
|
|
173
|
+
}
|
|
174
|
+
|
|
166
175
|
sh '''timeout -s 9 1800 docker exec --workdir=/app/src/addons/${GIT_NAME} $IMAGE_NAME-cypress make cypress-ci'''
|
|
167
176
|
} finally {
|
|
168
177
|
try {
|
|
178
|
+
if ( frontend == 0 ) {
|
|
169
179
|
sh '''rm -rf cypress-videos cypress-results cypress-coverage cypress-screenshots'''
|
|
170
180
|
sh '''mkdir -p cypress-videos cypress-results cypress-coverage cypress-screenshots'''
|
|
171
181
|
videos = sh script: '''docker cp $IMAGE_NAME-cypress:/app/src/addons/$GIT_NAME/cypress/videos cypress-videos/''', returnStatus: true
|
|
@@ -189,6 +199,7 @@ pipeline {
|
|
|
189
199
|
sh '''for file in $(find cypress-results -name *.xml); do if [ $(grep -E 'failures="[1-9].*"' $file | wc -l) -eq 0 ]; then testname=$(grep -E 'file=.*failures="0"' $file | sed 's#.* file=".*\\/\\(.*\\.[jsxt]\\+\\)" time.*#\\1#' ); rm -f cypress-videos/videos/$testname.mp4; fi; done'''
|
|
190
200
|
archiveArtifacts artifacts: 'cypress-videos/**/*.mp4', fingerprint: true, allowEmptyArchive: true
|
|
191
201
|
}
|
|
202
|
+
}
|
|
192
203
|
} finally {
|
|
193
204
|
catchError(buildResult: 'SUCCESS', stageResult: 'SUCCESS') {
|
|
194
205
|
junit testResults: 'cypress-results/**/*.xml', allowEmptyResults: true
|
|
@@ -204,16 +215,7 @@ pipeline {
|
|
|
204
215
|
}
|
|
205
216
|
}
|
|
206
217
|
}
|
|
207
|
-
}
|
|
208
218
|
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
post {
|
|
212
|
-
always {
|
|
213
|
-
sh script: "docker rmi $IMAGE_NAME-frontend", returnStatus: true
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
219
|
|
|
218
220
|
stage('Report to SonarQube') {
|
|
219
221
|
when {
|
|
@@ -221,9 +223,11 @@ pipeline {
|
|
|
221
223
|
allOf {
|
|
222
224
|
not { environment name: 'CHANGE_ID', value: '' }
|
|
223
225
|
environment name: 'CHANGE_TARGET', value: 'develop'
|
|
226
|
+
environment name: 'SKIP_TESTS', value: ''
|
|
224
227
|
}
|
|
225
228
|
allOf {
|
|
226
229
|
environment name: 'CHANGE_ID', value: ''
|
|
230
|
+
environment name: 'SKIP_TESTS', value: ''
|
|
227
231
|
anyOf {
|
|
228
232
|
allOf {
|
|
229
233
|
branch 'develop'
|
|
@@ -248,14 +252,107 @@ pipeline {
|
|
|
248
252
|
}
|
|
249
253
|
}
|
|
250
254
|
|
|
255
|
+
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
stage('Volto 16') {
|
|
260
|
+
agent { node { label 'integration'} }
|
|
261
|
+
when {
|
|
262
|
+
environment name: 'SKIP_TESTS', value: ''
|
|
263
|
+
not { environment name: 'VOLTO16_BREAKING_CHANGES', value: 'yes' }
|
|
264
|
+
}
|
|
265
|
+
stages {
|
|
266
|
+
stage('Build test image') {
|
|
267
|
+
steps {
|
|
268
|
+
sh '''docker build --pull --build-arg="VOLTO_VERSION=16" --build-arg="ADDON_NAME=$NAMESPACE/$GIT_NAME" --build-arg="ADDON_PATH=$GIT_NAME" . -t $IMAGE_NAME-frontend16'''
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
stage('Unit tests Volto 16') {
|
|
273
|
+
steps {
|
|
274
|
+
script {
|
|
275
|
+
try {
|
|
276
|
+
sh '''docker run --name="$IMAGE_NAME-volto16" --entrypoint=make --workdir=/app/src/addons/$GIT_NAME $IMAGE_NAME-frontend16 test-ci'''
|
|
277
|
+
sh '''rm -rf xunit-reports16'''
|
|
278
|
+
sh '''mkdir -p xunit-reports16'''
|
|
279
|
+
sh '''docker cp $IMAGE_NAME-volto16:/app/junit.xml xunit-reports16/'''
|
|
280
|
+
} finally {
|
|
281
|
+
catchError(buildResult: 'SUCCESS', stageResult: 'SUCCESS') {
|
|
282
|
+
junit testResults: 'xunit-reports16/junit.xml', allowEmptyResults: true
|
|
283
|
+
}
|
|
284
|
+
sh script: '''docker rm -v $IMAGE_NAME-volto16''', returnStatus: true
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
stage('Integration tests Volto 16') {
|
|
291
|
+
steps {
|
|
292
|
+
script {
|
|
293
|
+
try {
|
|
294
|
+
sh '''docker run --pull always --rm -d --name="$IMAGE_NAME-plone16" -e SITE="Plone" -e PROFILES="$BACKEND_PROFILES" -e ADDONS="$BACKEND_ADDONS" eeacms/plone-backend'''
|
|
295
|
+
sh '''docker run -d --shm-size=4g --link $IMAGE_NAME-plone16:plone --name="$IMAGE_NAME-cypress16" -e "RAZZLE_INTERNAL_API_PATH=http://plone:8080/Plone" --entrypoint=make --workdir=/app/src/addons/$GIT_NAME $IMAGE_NAME-frontend16 start-ci'''
|
|
296
|
+
frontend = sh script:'''docker exec --workdir=/app/src/addons/${GIT_NAME} $IMAGE_NAME-cypress16 make check-ci''', returnStatus: true
|
|
297
|
+
if ( frontend != 0 ) {
|
|
298
|
+
sh '''docker logs $IMAGE_NAME-cypress16; exit 1'''
|
|
299
|
+
}
|
|
300
|
+
sh '''timeout -s 9 1800 docker exec --workdir=/app/src/addons/${GIT_NAME} $IMAGE_NAME-cypress16 make cypress-ci'''
|
|
301
|
+
} finally {
|
|
302
|
+
try {
|
|
303
|
+
if ( frontend == 0 ) {
|
|
304
|
+
sh '''rm -rf cypress-videos16 cypress-results16 cypress-coverage16 cypress-screenshots16'''
|
|
305
|
+
sh '''mkdir -p cypress-videos16 cypress-results16 cypress-coverage16 cypress-screenshots16'''
|
|
306
|
+
videos = sh script: '''docker cp $IMAGE_NAME-cypress16:/app/src/addons/$GIT_NAME/cypress/videos cypress-videos16/''', returnStatus: true
|
|
307
|
+
sh '''docker cp $IMAGE_NAME-cypress16:/app/src/addons/$GIT_NAME/cypress/reports cypress-results16/'''
|
|
308
|
+
screenshots = sh script: '''docker cp $IMAGE_NAME-cypress16:/app/src/addons/$GIT_NAME/cypress/screenshots cypress-screenshots16''', returnStatus: true
|
|
309
|
+
|
|
310
|
+
archiveArtifacts artifacts: 'cypress-screenshots16/**', fingerprint: true, allowEmptyArchive: true
|
|
311
|
+
|
|
312
|
+
if ( videos == 0 ) {
|
|
313
|
+
sh '''for file in $(find cypress-results16 -name *.xml); do if [ $(grep -E 'failures="[1-9].*"' $file | wc -l) -eq 0 ]; then testname=$(grep -E 'file=.*failures="0"' $file | sed 's#.* file=".*\\/\\(.*\\.[jsxt]\\+\\)" time.*#\\1#' ); rm -f cypress-videos16/videos/$testname.mp4; fi; done'''
|
|
314
|
+
archiveArtifacts artifacts: 'cypress-videos16/**/*.mp4', fingerprint: true, allowEmptyArchive: true
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
} finally {
|
|
318
|
+
catchError(buildResult: 'SUCCESS', stageResult: 'SUCCESS') {
|
|
319
|
+
junit testResults: 'cypress-results16/**/*.xml', allowEmptyResults: true
|
|
320
|
+
}
|
|
321
|
+
catchError(buildResult: 'SUCCESS', stageResult: 'SUCCESS') {
|
|
322
|
+
sh '''docker logs $IMAGE_NAME-cypress16'''
|
|
323
|
+
}
|
|
324
|
+
sh script: "docker stop $IMAGE_NAME-cypress16", returnStatus: true
|
|
325
|
+
sh script: "docker stop $IMAGE_NAME-plone16", returnStatus: true
|
|
326
|
+
sh script: "docker rm -v $IMAGE_NAME-plone16", returnStatus: true
|
|
327
|
+
sh script: "docker rm -v $IMAGE_NAME-cypress16", returnStatus: true
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
post {
|
|
338
|
+
always {
|
|
339
|
+
sh script: "docker rmi $IMAGE_NAME-frontend", returnStatus: true
|
|
340
|
+
sh script: "docker rmi $IMAGE_NAME-frontend16", returnStatus: true
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
|
|
251
346
|
stage('SonarQube compare to master') {
|
|
252
347
|
when {
|
|
253
348
|
anyOf {
|
|
254
349
|
allOf {
|
|
255
350
|
not { environment name: 'CHANGE_ID', value: '' }
|
|
256
351
|
environment name: 'CHANGE_TARGET', value: 'develop'
|
|
352
|
+
environment name: 'SKIP_TESTS', value: ''
|
|
257
353
|
}
|
|
258
354
|
allOf {
|
|
355
|
+
environment name: 'SKIP_TESTS', value: ''
|
|
259
356
|
environment name: 'CHANGE_ID', value: ''
|
|
260
357
|
branch 'develop'
|
|
261
358
|
not { changelog '.*^Automated release [0-9\\.]+$' }
|
|
@@ -316,3 +413,4 @@ pipeline {
|
|
|
316
413
|
}
|
|
317
414
|
}
|
|
318
415
|
}
|
|
416
|
+
|
package/Makefile
CHANGED
|
@@ -46,7 +46,7 @@ endif
|
|
|
46
46
|
DIR=$(shell basename $$(pwd))
|
|
47
47
|
NODE_MODULES?="../../../node_modules"
|
|
48
48
|
PLONE_VERSION?=6
|
|
49
|
-
VOLTO_VERSION?=
|
|
49
|
+
VOLTO_VERSION?=17
|
|
50
50
|
ADDON_PATH="${DIR}"
|
|
51
51
|
ADDON_NAME="@eeacms/${ADDON_PATH}"
|
|
52
52
|
DOCKER_COMPOSE=PLONE_VERSION=${PLONE_VERSION} VOLTO_VERSION=${VOLTO_VERSION} ADDON_NAME=${ADDON_NAME} ADDON_PATH=${ADDON_PATH} docker compose
|
|
@@ -86,7 +86,7 @@ cypress-open: ## Open cypress integration tests
|
|
|
86
86
|
|
|
87
87
|
.PHONY: cypress-run
|
|
88
88
|
cypress-run: ## Run cypress integration tests
|
|
89
|
-
CYPRESS_API_PATH="${RAZZLE_DEV_PROXY_API_PATH}" NODE_ENV=development $(NODE_MODULES)/cypress/bin/cypress run
|
|
89
|
+
CYPRESS_API_PATH="${RAZZLE_DEV_PROXY_API_PATH}" NODE_ENV=development $(NODE_MODULES)/cypress/bin/cypress run
|
|
90
90
|
|
|
91
91
|
.PHONY: test
|
|
92
92
|
test: ## Run jest tests
|
|
@@ -98,7 +98,7 @@ test-update: ## Update jest tests snapshots
|
|
|
98
98
|
|
|
99
99
|
.PHONY: stylelint
|
|
100
100
|
stylelint: ## Stylelint
|
|
101
|
-
$(NODE_MODULES)
|
|
101
|
+
$(NODE_MODULES)/.bin/stylelint --allow-empty-input 'src/**/*.{css,less}'
|
|
102
102
|
|
|
103
103
|
.PHONY: stylelint-overrides
|
|
104
104
|
stylelint-overrides:
|
|
@@ -106,7 +106,7 @@ stylelint-overrides:
|
|
|
106
106
|
|
|
107
107
|
.PHONY: stylelint-fix
|
|
108
108
|
stylelint-fix: ## Fix stylelint
|
|
109
|
-
$(NODE_MODULES)
|
|
109
|
+
$(NODE_MODULES)/.bin/stylelint --allow-empty-input 'src/**/*.{css,less}' --fix
|
|
110
110
|
$(NODE_MODULES)/.bin/stylelint --custom-syntax less --allow-empty-input 'theme/**/*.overrides' 'src/**/*.overrides' --fix
|
|
111
111
|
|
|
112
112
|
.PHONY: prettier
|
|
@@ -119,11 +119,11 @@ prettier-fix: ## Fix prettier
|
|
|
119
119
|
|
|
120
120
|
.PHONY: lint
|
|
121
121
|
lint: ## ES Lint
|
|
122
|
-
$(NODE_MODULES)
|
|
122
|
+
$(NODE_MODULES)/.bin/eslint --max-warnings=0 'src/**/*.{js,jsx}'
|
|
123
123
|
|
|
124
124
|
.PHONY: lint-fix
|
|
125
125
|
lint-fix: ## Fix ES Lint
|
|
126
|
-
$(NODE_MODULES)
|
|
126
|
+
$(NODE_MODULES)/.bin/eslint --fix 'src/**/*.{js,jsx}'
|
|
127
127
|
|
|
128
128
|
.PHONY: i18n
|
|
129
129
|
i18n: ## i18n
|
|
@@ -155,8 +155,11 @@ start-ci:
|
|
|
155
155
|
cd ../..
|
|
156
156
|
yarn start
|
|
157
157
|
|
|
158
|
+
.PHONY: check-ci
|
|
159
|
+
check-ci:
|
|
160
|
+
$(NODE_MODULES)/.bin/wait-on -t 240000 http://localhost:3000
|
|
161
|
+
|
|
158
162
|
.PHONY: cypress-ci
|
|
159
163
|
cypress-ci:
|
|
160
164
|
$(NODE_MODULES)/.bin/wait-on -t 240000 http://localhost:3000
|
|
161
|
-
NODE_ENV=development
|
|
162
|
-
|
|
165
|
+
CYPRESS_API_PATH="${RAZZLE_DEV_PROXY_API_PATH}" NODE_ENV=development $(NODE_MODULES)/cypress/bin/cypress run --browser chromium
|
package/cypress/support/e2e.js
CHANGED
|
@@ -35,10 +35,6 @@ export const slateBeforeEach = (contentType = 'Document') => {
|
|
|
35
35
|
path: 'cypress',
|
|
36
36
|
});
|
|
37
37
|
cy.visit('/cypress/my-page');
|
|
38
|
-
cy.waitForResourceToLoad('@navigation');
|
|
39
|
-
cy.waitForResourceToLoad('@breadcrumbs');
|
|
40
|
-
cy.waitForResourceToLoad('@actions');
|
|
41
|
-
cy.waitForResourceToLoad('@types');
|
|
42
38
|
cy.waitForResourceToLoad('my-page');
|
|
43
39
|
cy.navigate('/cypress/my-page/edit');
|
|
44
40
|
};
|
package/jest-addon.config.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require('dotenv').config({ path: __dirname + '/.env' })
|
|
2
|
+
|
|
1
3
|
module.exports = {
|
|
2
4
|
testMatch: ['**/src/addons/**/?(*.)+(spec|test).[jt]s?(x)'],
|
|
3
5
|
collectCoverageFrom: [
|
|
@@ -9,18 +11,26 @@ module.exports = {
|
|
|
9
11
|
'@plone/volto/cypress': '<rootDir>/node_modules/@plone/volto/cypress',
|
|
10
12
|
'@plone/volto/babel': '<rootDir>/node_modules/@plone/volto/babel',
|
|
11
13
|
'@plone/volto/(.*)$': '<rootDir>/node_modules/@plone/volto/src/$1',
|
|
12
|
-
'@package/(.*)$': '<rootDir>/src/$1',
|
|
13
|
-
'@root/(.*)$': '<rootDir>/src/$1',
|
|
14
|
+
'@package/(.*)$': '<rootDir>/node_modules/@plone/volto/src/$1',
|
|
15
|
+
'@root/(.*)$': '<rootDir>/node_modules/@plone/volto/src/$1',
|
|
14
16
|
'@plone/volto-quanta/(.*)$': '<rootDir>/src/addons/volto-quanta/src/$1',
|
|
17
|
+
'@eeacms/search/(.*)$': '<rootDir>/src/addons/volto-searchlib/searchlib/$1',
|
|
18
|
+
'@eeacms/search': '<rootDir>/src/addons/volto-searchlib/searchlib',
|
|
15
19
|
'@eeacms/(.*?)/(.*)$': '<rootDir>/node_modules/@eeacms/$1/src/$2',
|
|
16
|
-
'@plone/volto-slate':
|
|
20
|
+
'@plone/volto-slate$':
|
|
17
21
|
'<rootDir>/node_modules/@plone/volto/packages/volto-slate/src',
|
|
22
|
+
'@plone/volto-slate/(.*)$':
|
|
23
|
+
'<rootDir>/node_modules/@plone/volto/packages/volto-slate/src/$1',
|
|
18
24
|
'~/(.*)$': '<rootDir>/src/$1',
|
|
19
25
|
'load-volto-addons':
|
|
20
26
|
'<rootDir>/node_modules/@plone/volto/jest-addons-loader.js',
|
|
21
27
|
},
|
|
28
|
+
transformIgnorePatterns: [
|
|
29
|
+
'/node_modules/(?!(@plone|@root|@package|@eeacms)/).*/',
|
|
30
|
+
],
|
|
22
31
|
transform: {
|
|
23
32
|
'^.+\\.js(x)?$': 'babel-jest',
|
|
33
|
+
'^.+\\.ts(x)?$': 'babel-jest',
|
|
24
34
|
'^.+\\.(png)$': 'jest-file',
|
|
25
35
|
'^.+\\.(jpg)$': 'jest-file',
|
|
26
36
|
'^.+\\.(svg)$': './node_modules/@plone/volto/jest-svgsystem-transform.js',
|
|
@@ -33,4 +43,9 @@ module.exports = {
|
|
|
33
43
|
statements: 5,
|
|
34
44
|
},
|
|
35
45
|
},
|
|
36
|
-
|
|
46
|
+
...(process.env.JEST_USE_SETUP === 'ON' && {
|
|
47
|
+
setupFilesAfterEnv: [
|
|
48
|
+
'<rootDir>/node_modules/@eeacms/volto-slate-footnote/jest.setup.js',
|
|
49
|
+
],
|
|
50
|
+
}),
|
|
51
|
+
}
|
package/jest.setup.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
import configureStore from 'redux-mock-store';
|
|
3
|
+
import thunk from 'redux-thunk';
|
|
4
|
+
import { blocksConfig } from '@plone/volto/config/Blocks';
|
|
5
|
+
import installSlate from '@plone/volto-slate/index';
|
|
6
|
+
|
|
7
|
+
var mockSemanticComponents = jest.requireActual('semantic-ui-react');
|
|
8
|
+
var mockComponents = jest.requireActual('@plone/volto/components');
|
|
9
|
+
var config = jest.requireActual('@plone/volto/registry').default;
|
|
10
|
+
|
|
11
|
+
config.blocks.blocksConfig = {
|
|
12
|
+
...blocksConfig,
|
|
13
|
+
...config.blocks.blocksConfig,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
jest.doMock('semantic-ui-react', () => ({
|
|
17
|
+
__esModule: true,
|
|
18
|
+
...mockSemanticComponents,
|
|
19
|
+
Popup: ({ content, trigger }) => {
|
|
20
|
+
return (
|
|
21
|
+
<div className="popup">
|
|
22
|
+
<div className="trigger">{trigger}</div>
|
|
23
|
+
<div className="content">{content}</div>
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
},
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
jest.doMock('@plone/volto/components', () => {
|
|
30
|
+
return {
|
|
31
|
+
__esModule: true,
|
|
32
|
+
...mockComponents,
|
|
33
|
+
SidebarPortal: ({ children }) => <div id="sidebar">{children}</div>,
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
jest.doMock('@plone/volto/registry', () =>
|
|
38
|
+
[installSlate].reduce((acc, apply) => apply(acc), config),
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const mockStore = configureStore([thunk]);
|
|
42
|
+
|
|
43
|
+
global.fetch = jest.fn(() =>
|
|
44
|
+
Promise.resolve({
|
|
45
|
+
json: () => Promise.resolve({}),
|
|
46
|
+
}),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
global.store = mockStore({
|
|
50
|
+
intl: {
|
|
51
|
+
locale: 'en',
|
|
52
|
+
messages: {},
|
|
53
|
+
formatMessage: jest.fn(),
|
|
54
|
+
},
|
|
55
|
+
content: {
|
|
56
|
+
create: {},
|
|
57
|
+
subrequests: [],
|
|
58
|
+
},
|
|
59
|
+
connected_data_parameters: {},
|
|
60
|
+
screen: {
|
|
61
|
+
page: {
|
|
62
|
+
width: 768,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eeacms/volto-slate-footnote",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0",
|
|
4
4
|
"description": "volto-slate-footnote: Volto add-on",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"author": "European Environment Agency: IDM2 A-Team",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"@cypress/code-coverage": "^3.10.0",
|
|
24
24
|
"@plone/scripts": "*",
|
|
25
25
|
"babel-plugin-transform-class-properties": "^6.24.1",
|
|
26
|
+
"dotenv": "^16.3.2",
|
|
26
27
|
"husky": "^8.0.3",
|
|
27
28
|
"lint-staged": "^14.0.1",
|
|
28
29
|
"md5": "^2.3.0"
|
|
@@ -28,139 +28,139 @@ const messages = defineMessages({
|
|
|
28
28
|
},
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
const MultiSelectSearchWidget = injectLazyLibs('reactSelectAsyncCreateable')(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
31
|
+
const MultiSelectSearchWidget = injectLazyLibs('reactSelectAsyncCreateable')((
|
|
32
|
+
props,
|
|
33
|
+
) => {
|
|
34
|
+
const [selectedOption, setSelectedOption] = useState([]);
|
|
35
|
+
const [defaultOptions, setDefaultOptions] = useState([]);
|
|
36
|
+
const [parentFootnote, setParentFootnote] = useState(props.value);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (props.value) {
|
|
40
|
+
const parentFootnoteCurrent = props.value;
|
|
41
|
+
|
|
42
|
+
const extraValues =
|
|
43
|
+
parentFootnoteCurrent && props.value.extra ? props.value.extra : [];
|
|
44
|
+
const selectedOptionCurrent = parentFootnoteCurrent.value
|
|
45
|
+
? [...[parentFootnoteCurrent], ...extraValues]
|
|
46
|
+
: [];
|
|
47
|
+
setSelectedOption(selectedOptionCurrent);
|
|
48
|
+
|
|
49
|
+
// from choices (list of all footnotes available including current in value) get all not
|
|
50
|
+
// found in current in value
|
|
51
|
+
// consider that new footnotes have value and footnote undefined
|
|
52
|
+
const defaultOptions = (props.choices || []).filter(
|
|
53
|
+
(item) =>
|
|
54
|
+
!selectedOption.find(({ label }) => label === item.label) &&
|
|
55
|
+
item.value,
|
|
56
|
+
);
|
|
57
|
+
setDefaultOptions(defaultOptions);
|
|
58
|
+
setParentFootnote(props.value);
|
|
59
|
+
}
|
|
60
|
+
}, [props]); // eslint-disable-line
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* evaluate on Regex to filter results
|
|
64
|
+
* @param {Object} e - event
|
|
65
|
+
* @param {Object} data
|
|
66
|
+
*/
|
|
67
|
+
const loadOptions = (search) => {
|
|
68
|
+
const re = new RegExp(escapeRegExp(search), 'i');
|
|
69
|
+
const isMatch = (result) => re.test(result.value);
|
|
70
|
+
const resultsFiltered = filter(props.choices, isMatch);
|
|
71
|
+
|
|
72
|
+
return Promise.resolve(resultsFiltered);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* If the list is empty or the first is not parent, return true
|
|
77
|
+
* @param {Object[]} optionsList list of objects - footnotes
|
|
78
|
+
* @returns {boolean}
|
|
79
|
+
*/
|
|
80
|
+
const isParetFootnoteRemoved = (optionsList) =>
|
|
81
|
+
!optionsList[0] || optionsList[0].value !== parentFootnote.value;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* replace all parentFootnote data except uid, with the first from the list
|
|
85
|
+
* @param {Object[]} optionsList list of objects - footnotes
|
|
86
|
+
* @returns {Object}
|
|
87
|
+
*/
|
|
88
|
+
const setParentFootnoteFromExtra = (optionsList) => {
|
|
89
|
+
const { footnote, label, value } = optionsList[0] || [];
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
...parentFootnote,
|
|
93
|
+
footnote: footnote || optionsList[0]?.value,
|
|
94
|
+
label,
|
|
95
|
+
value,
|
|
96
|
+
extra: optionsList.slice(1),
|
|
72
97
|
};
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
*/
|
|
87
|
-
const setParentFootnoteFromExtra = (optionsList) => {
|
|
88
|
-
const { footnote, label, value } = optionsList[0] || [];
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
...parentFootnote,
|
|
92
|
-
footnote: footnote || optionsList[0]?.value,
|
|
93
|
-
label,
|
|
94
|
-
value,
|
|
95
|
-
extra: optionsList.slice(1),
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Will make the footnotes object, that will be saved as first from optionsList
|
|
102
|
+
* the rest will be added to extra
|
|
103
|
+
* @param {Object[]} optionsList
|
|
104
|
+
* @returns
|
|
105
|
+
*/
|
|
106
|
+
const setFootnoteFromSelection = (optionsList) => {
|
|
107
|
+
const extra = optionsList.slice(1).map((item) => {
|
|
108
|
+
const obj = {
|
|
109
|
+
...item,
|
|
110
|
+
footnote: item.value,
|
|
96
111
|
};
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
styles={customSelectStyles}
|
|
151
|
-
theme={selectTheme}
|
|
152
|
-
components={{ DropdownIndicator, Option }}
|
|
153
|
-
isMulti
|
|
154
|
-
options={defaultOptions}
|
|
155
|
-
value={selectedOption || []}
|
|
156
|
-
loadOptions={loadOptions}
|
|
157
|
-
onChange={handleChange}
|
|
158
|
-
placeholder={props.intl.formatMessage(messages.select)}
|
|
159
|
-
noOptionsMessage={() => props.intl.formatMessage(messages.no_options)}
|
|
160
|
-
/>
|
|
161
|
-
</FormFieldWrapper>
|
|
162
|
-
);
|
|
163
|
-
},
|
|
164
|
-
);
|
|
112
|
+
const { __isNew__: remove, extra, ...rest } = obj;
|
|
113
|
+
return rest;
|
|
114
|
+
});
|
|
115
|
+
return { ...parentFootnote, extra };
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Handle the field change, will remake the result based on the new selected list
|
|
120
|
+
* @method handleChange
|
|
121
|
+
* @param {array} optionsList The selected options (already aggregated).
|
|
122
|
+
* @returns {undefined}
|
|
123
|
+
*/
|
|
124
|
+
const handleChange = (optionsList) => {
|
|
125
|
+
const formattedSelectedOptions = optionsList.map((option) => ({
|
|
126
|
+
footnoteId: nanoid(5), // to be overwritten if already exists (keep as a reference to same text)
|
|
127
|
+
...option,
|
|
128
|
+
uid: nanoid(5), // overwrite existing, thus creating new record for the same text
|
|
129
|
+
footnote: option.value,
|
|
130
|
+
}));
|
|
131
|
+
setSelectedOption(formattedSelectedOptions);
|
|
132
|
+
|
|
133
|
+
// manage case if parent footnotes (first from the options) was removed
|
|
134
|
+
const resultSelected = isParetFootnoteRemoved(formattedSelectedOptions)
|
|
135
|
+
? setParentFootnoteFromExtra(formattedSelectedOptions)
|
|
136
|
+
: setFootnoteFromSelection(formattedSelectedOptions);
|
|
137
|
+
|
|
138
|
+
props.onChange({
|
|
139
|
+
footnote: resultSelected,
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const AsyncCreatableSelect = props.reactSelectAsyncCreateable.default;
|
|
144
|
+
return (
|
|
145
|
+
<FormFieldWrapper {...props}>
|
|
146
|
+
<AsyncCreatableSelect
|
|
147
|
+
isDisabled={props.isDisabled}
|
|
148
|
+
className="react-select-container"
|
|
149
|
+
classNamePrefix="react-select"
|
|
150
|
+
defaultOptions={defaultOptions}
|
|
151
|
+
styles={customSelectStyles}
|
|
152
|
+
theme={selectTheme}
|
|
153
|
+
components={{ DropdownIndicator, Option }}
|
|
154
|
+
isMulti
|
|
155
|
+
options={defaultOptions}
|
|
156
|
+
value={selectedOption || []}
|
|
157
|
+
loadOptions={loadOptions}
|
|
158
|
+
onChange={handleChange}
|
|
159
|
+
placeholder={props.intl.formatMessage(messages.select)}
|
|
160
|
+
noOptionsMessage={() => props.intl.formatMessage(messages.no_options)}
|
|
161
|
+
/>
|
|
162
|
+
</FormFieldWrapper>
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
165
|
|
|
166
166
|
export default MultiSelectSearchWidget;
|
|
@@ -1,10 +1,70 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import
|
|
2
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
3
3
|
import SearchWidget from './SearchWidget';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
import '@testing-library/jest-dom/extend-expect';
|
|
5
|
+
|
|
6
|
+
describe('SearchWidget', () => {
|
|
7
|
+
const choices = [
|
|
8
|
+
{ footnote: 'Citation 1' },
|
|
9
|
+
{ footnote: 'Citation 2' },
|
|
10
|
+
{ footnote: 'Citation 3' },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
it('renders the search input and displays the choices', () => {
|
|
14
|
+
const onChange = jest.fn();
|
|
15
|
+
render(<SearchWidget choices={choices} onChange={onChange} value="" />);
|
|
16
|
+
|
|
17
|
+
expect(screen.getByRole('textbox')).toBeInTheDocument();
|
|
18
|
+
expect(screen.getByText('Citation')).toBeInTheDocument();
|
|
19
|
+
expect(screen.queryAllByRole('option')).toHaveLength(0);
|
|
20
|
+
expect(screen.getByText('No results found.')).toBeInTheDocument();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('filters the choices based on the search input', async () => {
|
|
24
|
+
const onChange = jest.fn();
|
|
25
|
+
const { container } = render(
|
|
26
|
+
<SearchWidget choices={choices} onChange={onChange} value="" />,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const searchInput = screen.getByRole('textbox');
|
|
30
|
+
fireEvent.change(searchInput, { target: { value: 'Citation 2' } });
|
|
31
|
+
|
|
32
|
+
await waitFor(() => {
|
|
33
|
+
expect(container.querySelectorAll('.result')).toHaveLength(1);
|
|
34
|
+
expect(
|
|
35
|
+
container.querySelector('div[footnote="Citation 2"]'),
|
|
36
|
+
).toBeInTheDocument();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('calls the onChange callback when a choice is selected', () => {
|
|
41
|
+
const onChange = jest.fn();
|
|
42
|
+
render(<SearchWidget choices={choices} onChange={onChange} value="" />);
|
|
43
|
+
|
|
44
|
+
const searchInput = screen.getByRole('textbox');
|
|
45
|
+
fireEvent.change(searchInput, { target: { value: 'Citation 2' } });
|
|
46
|
+
|
|
47
|
+
waitFor(() => {
|
|
48
|
+
const option = screen.getByText('Citation 2');
|
|
49
|
+
fireEvent.click(option);
|
|
50
|
+
expect(onChange).toHaveBeenCalledWith({ footnote: 'Citation 2' });
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('updates the value prop when the search input changes', () => {
|
|
55
|
+
const onChange = jest.fn();
|
|
56
|
+
render(
|
|
57
|
+
<SearchWidget
|
|
58
|
+
choices={choices}
|
|
59
|
+
onChange={onChange}
|
|
60
|
+
value="Initial value"
|
|
61
|
+
/>,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const searchInput = screen.getByRole('textbox');
|
|
65
|
+
expect(searchInput).toHaveValue('Initial value');
|
|
66
|
+
|
|
67
|
+
fireEvent.change(searchInput, { target: { value: 'New value' } });
|
|
68
|
+
expect(onChange).toHaveBeenCalledWith({ footnote: 'New value' });
|
|
9
69
|
});
|
|
10
70
|
});
|
package/src/editor/styles.less
CHANGED
package/src/editor/utils.test.js
CHANGED
|
@@ -61,7 +61,7 @@ describe('getAllBlocksAndSlateFields', () => {
|
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
it('handles metadataSection correctly', () => {
|
|
64
|
-
const properties = {
|
|
64
|
+
const properties = { 1: ['test'] };
|
|
65
65
|
const blocks = [
|
|
66
66
|
{
|
|
67
67
|
'@type': 'metadataSection',
|
|
@@ -98,7 +98,7 @@ describe('getAllBlocksAndSlateFields', () => {
|
|
|
98
98
|
});
|
|
99
99
|
|
|
100
100
|
it('handles metadata correctly', () => {
|
|
101
|
-
const properties = {
|
|
101
|
+
const properties = { 1: ['metadata test'] };
|
|
102
102
|
const blocks = [
|
|
103
103
|
{
|
|
104
104
|
'@type': 'metadata',
|
package/.project.eslintrc.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
|
|
4
|
-
const projectRootPath = fs.existsSync('./project')
|
|
5
|
-
? fs.realpathSync('./project')
|
|
6
|
-
: fs.realpathSync('./../../../');
|
|
7
|
-
const packageJson = require(path.join(projectRootPath, 'package.json'));
|
|
8
|
-
const jsConfig = require(path.join(projectRootPath, 'jsconfig.json')).compilerOptions;
|
|
9
|
-
|
|
10
|
-
const pathsConfig = jsConfig.paths;
|
|
11
|
-
|
|
12
|
-
let voltoPath = path.join(projectRootPath, 'node_modules/@plone/volto');
|
|
13
|
-
|
|
14
|
-
Object.keys(pathsConfig).forEach(pkg => {
|
|
15
|
-
if (pkg === '@plone/volto') {
|
|
16
|
-
voltoPath = `./${jsConfig.baseUrl}/${pathsConfig[pkg][0]}`;
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
const AddonConfigurationRegistry = require(`${voltoPath}/addon-registry.js`);
|
|
20
|
-
const reg = new AddonConfigurationRegistry(projectRootPath);
|
|
21
|
-
|
|
22
|
-
// Extends ESlint configuration for adding the aliases to `src` directories in Volto addons
|
|
23
|
-
const addonAliases = Object.keys(reg.packages).map(o => [
|
|
24
|
-
o,
|
|
25
|
-
reg.packages[o].modulePath,
|
|
26
|
-
]);
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
module.exports = {
|
|
30
|
-
extends: `${projectRootPath}/node_modules/@plone/volto/.eslintrc`,
|
|
31
|
-
settings: {
|
|
32
|
-
'import/resolver': {
|
|
33
|
-
alias: {
|
|
34
|
-
map: [
|
|
35
|
-
['@plone/volto', '@plone/volto/src'],
|
|
36
|
-
...addonAliases,
|
|
37
|
-
['@package', `${__dirname}/src`],
|
|
38
|
-
['~', `${__dirname}/src`],
|
|
39
|
-
],
|
|
40
|
-
extensions: ['.js', '.jsx', '.json'],
|
|
41
|
-
},
|
|
42
|
-
'babel-plugin-root-import': {
|
|
43
|
-
rootPathSuffix: 'src',
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
-
|
|
3
|
-
exports[`Test SearchWidget check html content 1`] = `
|
|
4
|
-
<div
|
|
5
|
-
id="blockform-fieldset-default"
|
|
6
|
-
>
|
|
7
|
-
<div
|
|
8
|
-
className="ui segment attached"
|
|
9
|
-
fluid={true}
|
|
10
|
-
>
|
|
11
|
-
<div
|
|
12
|
-
className="ui fluid card"
|
|
13
|
-
onClick={[Function]}
|
|
14
|
-
>
|
|
15
|
-
<div
|
|
16
|
-
className="content"
|
|
17
|
-
fluid={true}
|
|
18
|
-
>
|
|
19
|
-
<div
|
|
20
|
-
className="header"
|
|
21
|
-
>
|
|
22
|
-
Citation
|
|
23
|
-
</div>
|
|
24
|
-
<div
|
|
25
|
-
className="description"
|
|
26
|
-
fluid={true}
|
|
27
|
-
>
|
|
28
|
-
<div
|
|
29
|
-
className="ui fluid search"
|
|
30
|
-
onBlur={[Function]}
|
|
31
|
-
onFocus={[Function]}
|
|
32
|
-
onMouseDown={[Function]}
|
|
33
|
-
>
|
|
34
|
-
<div
|
|
35
|
-
className="ui fluid right icon input"
|
|
36
|
-
>
|
|
37
|
-
<input
|
|
38
|
-
autoComplete="off"
|
|
39
|
-
className="prompt"
|
|
40
|
-
id="field-footnote"
|
|
41
|
-
onChange={[Function]}
|
|
42
|
-
onClick={[Function]}
|
|
43
|
-
tabIndex="0"
|
|
44
|
-
type="text"
|
|
45
|
-
value=""
|
|
46
|
-
/>
|
|
47
|
-
<i
|
|
48
|
-
aria-hidden="true"
|
|
49
|
-
className="search icon"
|
|
50
|
-
onClick={[Function]}
|
|
51
|
-
/>
|
|
52
|
-
</div>
|
|
53
|
-
<div
|
|
54
|
-
className="results transition"
|
|
55
|
-
>
|
|
56
|
-
<div
|
|
57
|
-
className="message empty"
|
|
58
|
-
>
|
|
59
|
-
<div
|
|
60
|
-
className="header"
|
|
61
|
-
>
|
|
62
|
-
No results found.
|
|
63
|
-
</div>
|
|
64
|
-
</div>
|
|
65
|
-
</div>
|
|
66
|
-
</div>
|
|
67
|
-
</div>
|
|
68
|
-
</div>
|
|
69
|
-
</div>
|
|
70
|
-
</div>
|
|
71
|
-
</div>
|
|
72
|
-
`;
|