diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml
index e6b4989a24..62c8c735eb 100644
--- a/.github/workflows/acceptance.yml
+++ b/.github/workflows/acceptance.yml
@@ -11,7 +11,7 @@ jobs:
matrix:
node-version: [18.x, 20.x]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
# node setup
- name: Use Node.js ${{ matrix.node-version }}
@@ -19,7 +19,7 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
- - uses: pnpm/action-setup@v2
+ - uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 8
@@ -32,7 +32,7 @@ jobs:
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
@@ -42,7 +42,7 @@ jobs:
- name: Cache Cypress Binary
id: cache-cypress-binary
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ~/.cache/Cypress
key: binary-${{ matrix.node-version }}-${{ hashFiles('package-lock.json') }}
@@ -95,15 +95,15 @@ jobs:
matrix:
node-version: [18.x, 20.x]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
# node setup
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- - uses: pnpm/action-setup@v2
+ - uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 8
@@ -114,7 +114,7 @@ jobs:
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
@@ -124,7 +124,7 @@ jobs:
- name: Cache Cypress Binary
id: cache-cypress-binary
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ~/.cache/Cypress
key: binary-${{ matrix.node-version }}-${{ hashFiles('package-lock.json') }}
@@ -177,15 +177,15 @@ jobs:
matrix:
node-version: [18.x, 20.x]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
# node setup
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- - uses: pnpm/action-setup@v2
+ - uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 8
@@ -196,7 +196,7 @@ jobs:
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
@@ -206,7 +206,7 @@ jobs:
- name: Cache Cypress Binary
id: cache-cypress-binary
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ~/.cache/Cypress
key: binary-${{ matrix.node-version }}-${{ hashFiles('package-lock.json') }}
@@ -259,15 +259,15 @@ jobs:
matrix:
node-version: [18.x, 20.x]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
# node setup
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- - uses: pnpm/action-setup@v2
+ - uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 8
@@ -278,7 +278,7 @@ jobs:
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
@@ -288,7 +288,7 @@ jobs:
- name: Cache Cypress Binary
id: cache-cypress-binary
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ~/.cache/Cypress
key: binary-${{ matrix.node-version }}-${{ hashFiles('package-lock.json') }}
@@ -341,15 +341,15 @@ jobs:
matrix:
node-version: [18.x, 20.x]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
# node setup
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- - uses: pnpm/action-setup@v2
+ - uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 8
@@ -360,7 +360,7 @@ jobs:
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
@@ -370,7 +370,7 @@ jobs:
- name: Cache Cypress Binary
id: cache-cypress-binary
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ~/.cache/Cypress
key: binary-${{ matrix.node-version }}-${{ hashFiles('package-lock.json') }}
@@ -423,15 +423,15 @@ jobs:
matrix:
node-version: [18.x, 20.x]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
# node setup
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- - uses: pnpm/action-setup@v2
+ - uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 8
@@ -442,7 +442,7 @@ jobs:
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
@@ -452,7 +452,7 @@ jobs:
- name: Cache Cypress Binary
id: cache-cypress-binary
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ~/.cache/Cypress
key: binary-${{ matrix.node-version }}-${{ hashFiles('package-lock.json') }}
@@ -504,15 +504,15 @@ jobs:
matrix:
node-version: [18.x]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
# node setup
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- - uses: pnpm/action-setup@v2
+ - uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 8
@@ -523,7 +523,7 @@ jobs:
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
@@ -533,7 +533,7 @@ jobs:
- name: Cache Cypress Binary
id: cache-cypress-binary
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ~/.cache/Cypress
key: binary-${{ matrix.node-version }}-${{ hashFiles('package-lock.json') }}
@@ -586,15 +586,15 @@ jobs:
matrix:
node-version: [18.x]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
# node setup
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- - uses: pnpm/action-setup@v2
+ - uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 8
@@ -605,7 +605,7 @@ jobs:
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
@@ -615,7 +615,7 @@ jobs:
- name: Cache Cypress Binary
id: cache-cypress-binary
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ~/.cache/Cypress
key: binary-${{ matrix.node-version }}-${{ hashFiles('package-lock.json') }}
@@ -668,15 +668,15 @@ jobs:
# matrix:
# node-version: [18.x]
# steps:
- # - uses: actions/checkout@v3
+ # - uses: actions/checkout@v4
# # node setup
# - name: Use Node.js ${{ matrix.node-version }}
- # uses: actions/setup-node@v3
+ # uses: actions/setup-node@v4
# with:
# node-version: ${{ matrix.node-version }}
- # - uses: pnpm/action-setup@v2
+ # - uses: pnpm/action-setup@v3
# name: Install pnpm
# with:
# version: 8
@@ -687,7 +687,7 @@ jobs:
# run: |
# echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- # - uses: actions/cache@v3
+ # - uses: actions/cache@v4
# name: Setup pnpm cache
# with:
# path: ${{ env.STORE_PATH }}
@@ -739,15 +739,15 @@ jobs:
matrix:
node-version: [18.x]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
# node setup
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- - uses: pnpm/action-setup@v2
+ - uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 8
@@ -758,7 +758,7 @@ jobs:
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
@@ -768,7 +768,7 @@ jobs:
- name: Cache Cypress Binary
id: cache-cypress-binary
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ~/.cache/Cypress
key: binary-${{ matrix.node-version }}-${{ hashFiles('package-lock.json') }}
@@ -822,15 +822,15 @@ jobs:
node-version: [18.x]
# python-version: [3.7]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
# node setup
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- - uses: pnpm/action-setup@v2
+ - uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 8
@@ -841,7 +841,7 @@ jobs:
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
@@ -851,7 +851,7 @@ jobs:
- name: Cache Cypress Binary
id: cache-cypress-binary
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ~/.cache/Cypress
key: binary-${{ matrix.node-version }}-${{ hashFiles('package-lock.json') }}
@@ -932,15 +932,15 @@ jobs:
project-directory: ./my-volto-app
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
# node setup
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- - uses: pnpm/action-setup@v2
+ - uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 8
@@ -951,7 +951,7 @@ jobs:
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
@@ -961,7 +961,7 @@ jobs:
- name: Cache Cypress Binary
id: cache-cypress-binary
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ~/.cache/Cypress
key: binary-${{ matrix.node-version }}-${{ hashFiles('package-lock.json') }}
@@ -1069,15 +1069,15 @@ jobs:
matrix:
node-version: [18.x]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
# node setup
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- - uses: pnpm/action-setup@v2
+ - uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 8
@@ -1088,7 +1088,7 @@ jobs:
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
@@ -1098,7 +1098,7 @@ jobs:
- name: Cache Cypress Binary
id: cache-cypress-binary
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ~/.cache/Cypress
key: binary-${{ matrix.node-version }}-${{ hashFiles('package-lock.json') }}
@@ -1140,7 +1140,7 @@ jobs:
- uses: actions/upload-artifact@v1
if: failure()
with:
- name: cypress-videos
+ name: cypress-videos-seamless
path: packages/volto/cypress/videos
multilingualseamless:
@@ -1153,15 +1153,15 @@ jobs:
matrix:
node-version: [18.x]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
# node setup
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- - uses: pnpm/action-setup@v2
+ - uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 8
@@ -1172,7 +1172,7 @@ jobs:
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
@@ -1182,7 +1182,7 @@ jobs:
- name: Cache Cypress Binary
id: cache-cypress-binary
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ~/.cache/Cypress
key: binary-${{ matrix.node-version }}-${{ hashFiles('package-lock.json') }}
diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml
index c6a16acd47..04b8739752 100644
--- a/.github/workflows/changelog.yml
+++ b/.github/workflows/changelog.yml
@@ -12,7 +12,7 @@ jobs:
towncrier:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
# Fetch all history
fetch-depth: '0'
diff --git a/.github/workflows/code-analysis.yml b/.github/workflows/code-analysis.yml
index cdba0307d1..a4dad86bde 100644
--- a/.github/workflows/code-analysis.yml
+++ b/.github/workflows/code-analysis.yml
@@ -10,15 +10,15 @@ jobs:
name: Prettier
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
# node setup
- name: Use Node.js ${{ env.node-version }}
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ env.node-version }}
- - uses: pnpm/action-setup@v2
+ - uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 8
@@ -29,7 +29,7 @@ jobs:
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
@@ -47,15 +47,15 @@ jobs:
name: ESlint
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
# node setup
- name: Use Node.js ${{ env.node-version }}
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ env.node-version }}
- - uses: pnpm/action-setup@v2
+ - uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 8
@@ -66,7 +66,7 @@ jobs:
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
@@ -87,15 +87,15 @@ jobs:
name: Stylelint
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
# node setup
- name: Use Node.js ${{ env.node-version }}
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ env.node-version }}
- - uses: pnpm/action-setup@v2
+ - uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 8
@@ -106,7 +106,7 @@ jobs:
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
@@ -127,15 +127,15 @@ jobs:
name: i18n
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
# node setup
- name: Use Node.js ${{ env.node-version }}
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ env.node-version }}
- - uses: pnpm/action-setup@v2
+ - uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 8
@@ -146,7 +146,7 @@ jobs:
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
diff --git a/.github/workflows/deployment_tests.yml b/.github/workflows/deployment_tests.yml
new file mode 100644
index 0000000000..7324662881
--- /dev/null
+++ b/.github/workflows/deployment_tests.yml
@@ -0,0 +1,197 @@
+name: Deployment Tests
+on: [push, pull_request]
+jobs:
+ vitessr:
+ if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
+ runs-on: ubuntu-latest
+ name: Vite SSR
+ timeout-minutes: 5
+ strategy:
+ fail-fast: false
+ steps:
+ - uses: actions/checkout@v4
+
+ # node setup
+ - name: Use Node.js 20.x
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20.x
+
+ - uses: pnpm/action-setup@v3
+ name: Install pnpm
+ with:
+ version: 8
+ # We don't want to install until later,
+ # when the cache and Cypress are in place
+ run_install: false
+
+ - name: Get pnpm store directory
+ shell: bash
+ run: |
+ echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
+
+ - uses: actions/cache@v4
+ name: Setup pnpm cache
+ with:
+ path: ${{ env.STORE_PATH }}
+ key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
+ restore-keys: |
+ ${{ runner.os }}-pnpm-store-
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Build packages
+ run: pnpm build:deps && pnpm build:components
+
+ - name: Start backend
+ run: make start-backend-docker-detached
+
+ - name: Build
+ run: pnpm --filter plone-vite-ssr build
+
+ - name: Start server
+ run: nohup pnpm --filter plone-vite-ssr start:prod &
+
+ - name: Wait
+ run: packages/scripts/node_modules/.bin/wait-on --httpTimeout 20000 http-get://127.0.0.1:8080/Plone
+
+ - name: Run tests
+ run: curl http://localhost:3000 || true
+
+ - name: Run tests
+ run: curl http://127.0.0.1:3000 || true
+
+ - name: Run tests
+ run: node packages/scripts/check_deployment.js
+
+ - name: Stop backend
+ run: make stop-backend-docker-detached
+
+ nextjs:
+ if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
+ runs-on: ubuntu-latest
+ name: Next.JS
+ timeout-minutes: 5
+ strategy:
+ fail-fast: false
+ steps:
+ - uses: actions/checkout@v4
+
+ # node setup
+ - name: Use Node.js 20.x
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20.x
+
+ - uses: pnpm/action-setup@v3
+ name: Install pnpm
+ with:
+ version: 8
+ # We don't want to install until later,
+ # when the cache and Cypress are in place
+ run_install: false
+
+ - name: Get pnpm store directory
+ shell: bash
+ run: |
+ echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
+
+ - uses: actions/cache@v4
+ name: Setup pnpm cache
+ with:
+ path: ${{ env.STORE_PATH }}
+ key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
+ restore-keys: |
+ ${{ runner.os }}-pnpm-store-
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Build packages
+ run: pnpm build:deps && pnpm build:components
+
+ - name: Start backend
+ run: make start-backend-docker-detached
+
+ - name: Build
+ run: pnpm --filter plone-nextjs build
+
+ - name: Start server
+ run: nohup pnpm --filter plone-nextjs start:prod &
+
+ - name: Wait
+ run: packages/scripts/node_modules/.bin/wait-on --httpTimeout 20000 http-get://127.0.0.1:8080/Plone
+
+ - name: Run tests
+ run: curl http://localhost:3000 || true
+
+ - name: Run tests
+ run: curl http://127.0.0.1:3000 || true
+
+ - name: Run tests
+ run: node packages/scripts/check_deployment.js
+
+ - name: Stop backend
+ run: make stop-backend-docker-detached
+
+ remix:
+ if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
+ runs-on: ubuntu-latest
+ name: Remix
+ timeout-minutes: 5
+ strategy:
+ fail-fast: false
+ steps:
+ - uses: actions/checkout@v4
+
+ # node setup
+ - name: Use Node.js 20.x
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20.x
+
+ - uses: pnpm/action-setup@v3
+ name: Install pnpm
+ with:
+ version: 8
+ # We don't want to install until later,
+ # when the cache and Cypress are in place
+ run_install: false
+
+ - name: Get pnpm store directory
+ shell: bash
+ run: |
+ echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
+
+ - uses: actions/cache@v4
+ name: Setup pnpm cache
+ with:
+ path: ${{ env.STORE_PATH }}
+ key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
+ restore-keys: |
+ ${{ runner.os }}-pnpm-store-
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Build packages
+ run: pnpm build:deps && pnpm build:components
+
+ - name: Start backend
+ run: make start-backend-docker-detached
+
+ - name: Build
+ run: pnpm --filter plone-remix build
+
+ - name: Start server
+ run: nohup pnpm --filter plone-remix start:prod &
+
+ - name: Wait
+ run: packages/scripts/node_modules/.bin/wait-on --httpTimeout 20000 http-get://127.0.0.1:8080/Plone
+
+ - name: Run tests
+ run: node packages/scripts/check_deployment.js
+
+ - name: Stop backend
+ run: make stop-backend-docker-detached
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 08763d2f3a..987ac42c7c 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -19,7 +19,7 @@ jobs:
matrix:
python-version: ['3.10']
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml
index 9f2ec8ec26..01c18ec7f9 100644
--- a/.github/workflows/unit.yml
+++ b/.github/workflows/unit.yml
@@ -14,15 +14,15 @@ jobs:
matrix:
node-version: [18.x, 20.x]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
# node setup
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- - uses: pnpm/action-setup@v2
+ - uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 8
@@ -33,7 +33,7 @@ jobs:
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
@@ -64,15 +64,15 @@ jobs:
matrix:
node-version: [18.x, 20.x]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
# node setup
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- - uses: pnpm/action-setup@v2
+ - uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 8
@@ -83,7 +83,7 @@ jobs:
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
@@ -105,15 +105,15 @@ jobs:
matrix:
node-version: [18.x, 20.x]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
# node setup
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- - uses: pnpm/action-setup@v2
+ - uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 8
@@ -124,7 +124,7 @@ jobs:
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
@@ -141,14 +141,14 @@ jobs:
name: '@plone/client'
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Use Node.js ${{ env.node-version }}
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ env.node-version }}
- - uses: pnpm/action-setup@v2
+ - uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 8
@@ -159,7 +159,7 @@ jobs:
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
diff --git a/.husky/post-checkout b/.husky/post-checkout
index a0df979ef1..8ec99a06f9 100755
--- a/.husky/post-checkout
+++ b/.husky/post-checkout
@@ -1,3 +1,2 @@
[ -n "$CI" ] && exit 0
-. "$(dirname -- "$0")/_/husky.sh"
-pnpm yarnhook
+pnpm lockhook
diff --git a/.husky/post-merge b/.husky/post-merge
index a0df979ef1..8ec99a06f9 100755
--- a/.husky/post-merge
+++ b/.husky/post-merge
@@ -1,3 +1,2 @@
[ -n "$CI" ] && exit 0
-. "$(dirname -- "$0")/_/husky.sh"
-pnpm yarnhook
+pnpm lockhook
diff --git a/.husky/post-rebase b/.husky/post-rebase
index a0df979ef1..8ec99a06f9 100755
--- a/.husky/post-rebase
+++ b/.husky/post-rebase
@@ -1,3 +1,2 @@
[ -n "$CI" ] && exit 0
-. "$(dirname -- "$0")/_/husky.sh"
-pnpm yarnhook
+pnpm lockhook
diff --git a/.husky/pre-commit b/.husky/pre-commit
index ca5e004fa3..17092dc583 100755
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -1,5 +1,2 @@
-#!/usr/bin/env sh
[ -n "$CI" ] && exit 0
-. "$(dirname -- "$0")/_/husky.sh"
-
pnpm lint-staged
diff --git a/Makefile b/Makefile
index e3eb1c7db6..179130e6c6 100644
--- a/Makefile
+++ b/Makefile
@@ -189,6 +189,14 @@ copyreleasenotestodocs:
start-backend-docker:
docker run -it --rm --name=backend -p 8080:8080 -e SITE=Plone -e ADDONS='$(KGS)' $(DOCKER_IMAGE)
+.PHONY: start-backend-docker-detached
+start-backend-docker-detached:
+ docker run -d --rm --name=backend -p 8080:8080 -e SITE=Plone -e ADDONS='$(KGS)' $(DOCKER_IMAGE)
+
+.PHONY: stop-backend-docker-detached
+stop-backend-docker-detached:
+ docker kill backend
+
.PHONY: start-backend-docker-no-cors
start-backend-docker-no-cors:
docker run -it --rm --name=backend -p 8080:8080 -e SITE=Plone -e ADDONS='$(KGS)' -e CORS_=true $(DOCKER_IMAGE)
@@ -385,3 +393,7 @@ start-test-acceptance-server-5: ## Start Test Acceptance Server Main Fixture Plo
.PHONY: start-test-acceptance-server-detached
start-test-acceptance-server-detached: ## Start Test Acceptance Server Main Fixture (docker container) in a detached (daemon) mode
docker run -d --name plone-client-acceptance-server -i --rm -p 55001:55001 $(DOCKER_IMAGE_ACCEPTANCE)
+
+.PHONY: stop-test-acceptance-server-detached
+stop-test-acceptance-server-detached: ## Stop Test Acceptance Server Main Fixture (docker container) in a detached (daemon) mode
+ docker kill plone-client-acceptance-server
diff --git a/apps/nextjs/package.json b/apps/nextjs/package.json
index 33b5b41b3c..6e29f5f31e 100644
--- a/apps/nextjs/package.json
+++ b/apps/nextjs/package.json
@@ -6,19 +6,17 @@
"dev": "next dev",
"check-ts": "tsc --project tsconfig.json",
"build": "next build",
- "start": "next start",
- "lint": "next lint",
- "prettier": "prettier --check 'src/**/*.{js,jsx,ts,tsx}'",
- "prettier:fix": "prettier --write 'src/**/*.{js,jsx,ts,tsx}'"
+ "start:prod": "next start",
+ "lint": "next lint"
},
"dependencies": {
"@plone/client": "workspace: *",
"@plone/components": "workspace: *",
- "@tanstack/react-query": "^5.0.5",
- "@tanstack/react-query-devtools": "^5.4.2",
- "next": "14.0.4",
+ "@tanstack/react-query": "^5.24.6",
+ "@tanstack/react-query-devtools": "^5.24.6",
+ "next": "14.1.1",
"react": "^18",
- "react-aria-components": "^1.0.0-rc.0",
+ "react-aria-components": "^1.1.1",
"react-dom": "^18"
},
"devDependencies": {
@@ -26,10 +24,8 @@
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
- "eslint-config-next": "14.0.4",
+ "eslint-config-next": "14.1.1",
"mrs-developer": "^2.1.1",
- "prettier": "3.0.3",
- "sass": "^1.69.1",
"typescript": "5.2.2"
}
}
diff --git a/apps/nextjs/src/app/content.tsx b/apps/nextjs/src/app/content.tsx
index 3ae8de2a2c..c2c6ad2235 100644
--- a/apps/nextjs/src/app/content.tsx
+++ b/apps/nextjs/src/app/content.tsx
@@ -6,7 +6,7 @@ import Link from 'next/link';
import { flattenToAppURL } from './utils';
import { usePloneClient } from '@plone/client/provider';
import { Breadcrumbs } from '@plone/components';
-import '@plone/components/src/styles/main.scss';
+import '@plone/components/dist/basic.css';
export default function Content() {
const { getContentQuery } = usePloneClient();
diff --git a/apps/plone/package.json b/apps/plone/package.json
index b0c8d50d03..d104cc1a07 100644
--- a/apps/plone/package.json
+++ b/apps/plone/package.json
@@ -212,7 +212,7 @@
"redux": "4.1.0",
"redux-actions": "2.6.5",
"redux-connect": "10.0.0",
- "redux-devtools-extension": "2.13.8",
+ "@redux-devtools/extension": "^3.3.0",
"redux-localstorage-simple": "2.3.1",
"redux-mock-store": "1.5.4",
"redux-thunk": "2.3.0",
@@ -257,14 +257,14 @@
"babel-plugin-react-intl": "5.1.17",
"babel-plugin-root-import": "6.1.0",
"css-loader": "5.2.7",
- "eslint": "8.49.0",
- "eslint-config-prettier": "9.0.0",
+ "eslint": "^8.57.0",
+ "eslint-config-prettier": "9.1.0",
"eslint-config-react-app": "7.0.1",
"eslint-import-resolver-alias": "1.1.2",
"eslint-import-resolver-babel-plugin-root-import": "1.1.1",
- "eslint-plugin-import": "2.28.1",
- "eslint-plugin-jsx-a11y": "6.7.1",
- "eslint-plugin-prettier": "5.0.0",
+ "eslint-plugin-import": "2.29.1",
+ "eslint-plugin-jsx-a11y": "^6.7.1",
+ "eslint-plugin-prettier": "5.1.3",
"eslint-plugin-react": "7.33.2",
"eslint-plugin-react-hooks": "4.6.0",
"glob": "7.1.6",
@@ -279,13 +279,13 @@
"postcss-loader": "7.0.2",
"postcss-overrides": "3.1.4",
"postcss-scss": "4.0.6",
- "prettier": "3.0.3",
+ "prettier": "3.2.5",
"razzle": "4.2.18",
"start-server-and-test": "1.14.0",
"style-loader": "3.3.1",
- "stylelint": "15.10.3",
- "stylelint-config-idiomatic-order": "9.0.0",
- "stylelint-prettier": "4.0.2",
+ "stylelint": "^16.2.1",
+ "stylelint-config-idiomatic-order": "10.0.0",
+ "stylelint-prettier": "5.0.0",
"svg-loader": "0.0.2",
"svgo-loader": "3.0.3",
"ts-jest": "^26.4.2",
diff --git a/apps/remix/package.json b/apps/remix/package.json
index 0b95c9eb28..2da2695bce 100644
--- a/apps/remix/package.json
+++ b/apps/remix/package.json
@@ -7,7 +7,7 @@
"build": "remix build",
"dev": "remix dev --manual",
"lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .",
- "start": "remix-serve ./build/index.js",
+ "start:prod": "remix-serve ./build/index.js",
"typecheck": "tsc"
},
"dependencies": {
@@ -16,8 +16,8 @@
"@remix-run/node": "^2.4.0",
"@remix-run/react": "^2.4.0",
"@remix-run/serve": "^2.4.0",
- "@tanstack/react-query": "5.0.5",
- "@tanstack/react-query-devtools": "*",
+ "@tanstack/react-query": "^5.24.6",
+ "@tanstack/react-query-devtools": "^5.24.6",
"isbot": "^3.6.8",
"react": "^18.2.0",
"react-dom": "^18.2.0"
@@ -33,7 +33,7 @@
"eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
- "typescript": "^5.1.6"
+ "typescript": "^5.2.2"
},
"engines": {
"node": ">=18.0.0"
diff --git a/apps/vite-ssr/package.json b/apps/vite-ssr/package.json
index 650967a5b7..1db33a17e2 100644
--- a/apps/vite-ssr/package.json
+++ b/apps/vite-ssr/package.json
@@ -8,7 +8,7 @@
"build": "npm run build:client && npm run build:server",
"build:client": "vite build --outDir dist/client",
"build:server": "vite build --ssr src/entry-server.tsx --outDir dist/server",
- "serve": "NODE_ENV=production node server",
+ "start:prod": "NODE_ENV=production node server",
"debug": "node --inspect-brk server"
},
"dependencies": {
diff --git a/apps/vite-ssr/server.js b/apps/vite-ssr/server.js
index d073028c17..e5af2f9156 100644
--- a/apps/vite-ssr/server.js
+++ b/apps/vite-ssr/server.js
@@ -1,14 +1,18 @@
import fs from 'node:fs/promises';
import express from 'express';
import getPort, { portNumbers } from 'get-port';
+import dns from 'dns';
const isTest = process.env.NODE_ENV === 'test' || !!process.env.VITE_TEST_BUILD;
+// If DNS returns both ipv4 and ipv6 addresses, prefer ipv4
+
export async function createServer(
root = process.cwd(),
isProd = process.env.NODE_ENV === 'production',
hmrPort,
) {
+ dns.setDefaultResultOrder('ipv4first');
const app = express();
const prodIndexHtml = isProd
@@ -93,8 +97,12 @@ export async function createServer(
if (!isTest) {
createServer().then(async ({ app }) =>
- app.listen(await getPort({ port: portNumbers(3000, 3100) }), () => {
- console.log('Client Server: http://localhost:3000');
- }),
+ app.listen(
+ await getPort({ port: portNumbers(3000, 3100) }),
+ '0.0.0.0',
+ () => {
+ console.log('Client Server: http://localhost:3000');
+ },
+ ),
);
}
diff --git a/docs/source/conf.py b/docs/source/conf.py
index b60d6eabac..6360e35dae 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -91,10 +91,11 @@
# Ignore github.com pages with anchors
r"https://github.com/.*#.*",
# Ignore other specific anchors
- r"https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors#Identifying_the_issue",
- r"https://docs.cypress.io/guides/references/migration-guide#Migrating-to-Cypress-version-10-0",
r"https://chromewebstore.google.com/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi", # TODO retest with latest Sphinx when upgrading theme. chromewebstore recently changed its URL and has "too many redirects".
r"https://chromewebstore.google.com/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd", # TODO retest with latest Sphinx when upgrading theme. chromewebstore recently changed its URL and has "too many redirects".
+ r"https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors#Identifying_the_issue",
+ r"https://docs.cypress.io/guides/references/migration-guide#Migrating-to-Cypress-version-10-0",
+ r"https://stackoverflow.com", # volto and documentation # TODO retest with latest Sphinx.
]
linkcheck_anchors = True
linkcheck_timeout = 10
diff --git a/docs/source/configuration/index.md b/docs/source/configuration/index.md
index fde6916ff3..3375c41f9c 100644
--- a/docs/source/configuration/index.md
+++ b/docs/source/configuration/index.md
@@ -26,4 +26,5 @@ workingcopy
environmentvariables
expanders
locking
+slots
```
diff --git a/docs/source/configuration/slots.md b/docs/source/configuration/slots.md
new file mode 100644
index 0000000000..6c2c9701e5
--- /dev/null
+++ b/docs/source/configuration/slots.md
@@ -0,0 +1,412 @@
+---
+myst:
+ html_meta:
+ "description": "Slots are insertion points in the Volto rendering tree structure."
+ "property=og:description": "Slots are insertion points in the Volto rendering tree structure."
+ "property=og:title": "Slots"
+ "keywords": "Volto, Plone, frontend, React, configuration, slots, viewlets"
+---
+
+# Slots
+
+Slots provide a way for Volto add-ons to insert their own components at predefined locations in the rendered page.
+
+```{note}
+This concept is inspired by the Plone Classic UI {doc}`plone:classic-ui/viewlets`.
+```
+
+
+## Anatomy
+
+Slots have a name, and they contain a list of named slot components.
+
+Volto renders slots using the `SlotRenderer` component.
+You can add slot insertion points in your code, as shown in the following example.
+
+```ts
+
+```
+
+Slot components are registered in the {ref}`configuration registry using a specific API for slots `.
+
+The rendering of a slot component is controlled by the presence or absence of a list of conditions called {term}`predicates`.
+
+You can register multiple slot components with the same name under the same slot, as long as they have different predicates or components.
+
+To illustrate how slots are structured and work, let's register a slot component, where the component is `PageHeader`, and the predicate matches a route that begins with `/de/about`.
+
+```ts
+config.registerSlotComponent({
+ slot: 'aboveContent',
+ name: 'header',
+ component: PageHeader,
+ predicates: [RouteCondition('/de/about')],
+});
+```
+
+The following tree structure diagram illustrates the resultant registration.
+
+```text
+Slot (`name`=`aboveContent`)
+└── SlotComponent
+ ├── `slot`=`aboveContent`
+ ├── `name`=`header`
+ ├── `component`=PageHeader
+ └── predicate of "only appear under `/de/about`"
+```
+
+Next, let's register another slot component in the same slot, with the same name and component, but with a different predicate where the content type matches either `Document` or `News Item`.
+
+```ts
+config.registerSlotComponent({
+ slot: 'aboveContent',
+ name: 'header',
+ component: PageHeader,
+ predicates: [ContentTypeCondition(['Document', 'News Item'])],
+});
+```
+
+The following tree structure diagram illustrates the result of the second registration.
+
+```text
+Slot (`name`=`aboveContent`)
+├── SlotComponent
+│ ├── `slot`=`aboveContent`
+│ ├── `name`=`header`
+│ ├── `component`=PageHeader
+│ └── predicate of "only appear under `/de/about`"
+└── SlotComponent
+ ├── `slot`=`aboveContent`
+ ├── `name`=`header`
+ ├── `component`=PageHeader
+ └── predicate of "only appear when the content type is either a Document or News Item"
+```
+
+Finally, let's register another slot component in the same slot, with the same name, but with a different component and without a predicate.
+
+```ts
+config.registerSlotComponent({
+ slot: 'aboveContent',
+ name: 'header',
+ component: 'DefaultHeader',
+});
+```
+
+The following tree structure diagram illustrates the result of the third registration.
+
+```text
+Slot (`name`=`aboveContent`)
+├── SlotComponent
+│ ├── `slot`=`aboveContent`
+│ ├── `name`=`header`
+│ ├── `component`=PageHeader
+│ └── predicate of "only appear under `/de/about`"
+├── SlotComponent
+│ ├── `slot`=`aboveContent`
+│ ├── `name`=`header`
+│ ├── `component`=PageHeader
+│ └── predicate of "only appear when the content type is either a Document or News Item"
+└── SlotComponent
+ ├── `slot`=`aboveContent`
+ ├── `name`=`header`
+ └── `component`=`DefaultHeader`
+```
+
+The rendering of slot components follows an algorithm:
+
+- The last registered slot component is evaluated first.
+- The first evaluated slot component's predicates to return `true` has its component rendered in the slot.
+- A slot component without predicates becomes the fallback for other slot components with predicates.
+
+Working through the above diagram from bottom to top, let's assume a visitor goes to the route `/de/about` and views an Event content type.
+
+1. The algorithm looks for the third slot component's predicates.
+ Because it has no predicates to be evaluated, and therefore cannot return `true`, its component is a fallback to other slot components.
+
+2. Moving upward, the second slot component's predicates are evaluated.
+ If they are `true`, then its component is rendered in the slot, and evaluation stops.
+ But in this case, the content type is an Event, thus it returns `false`, and evaluation continues upward.
+
+3. The first slot component's predicates are evaluated.
+ In this case, they are true because the visitor is on the route `/de/about`.
+ Evaluation stops, and its component is rendered in the slot.
+
+Within a slot, slot components are grouped by their name.
+The order in which the grouped slot components are evaluated is governed by the order in which they are registered.
+
+Extending our previous example, let's register another slot component with a different name.
+
+```ts
+config.registerSlotComponent({
+ slot: 'aboveContent',
+ name: 'subheader',
+ component: PageSubHeader,
+ predicates: [ContentTypeCondition(['Document', 'News Item'])],
+});
+```
+
+Thus the order of evaluation of the named slot components would be `header`, `subheader`.
+As each group of slot components is evaluated, their predicates will determine what is rendered in their position.
+
+You can change the order of the named slot components for a different slot using the {ref}`slots-reorderSlotComponent-label` API.
+In our example, you can reorder the `subheading` before the `heading`, although it would probably look strange.
+
+```ts
+config.reorderSlotComponent({
+ slot: 'aboveContent',
+ name: 'subheader',
+ action: 'before',
+ target: 'header',
+});
+```
+
+You can even delete the rendering of a registered slot component using the {ref}`slots-unregisterSlotComponent-label` API.
+
+
+## Default slots
+
+Volto comes with the following default slots.
+
+- `aboveContent`
+- `belowContent`
+
+
+(configuration-registry-for-slot-components)=
+
+## Configuration registry for slot components
+
+You can manage slot components using the configuration registry for slot components and its API.
+
+
+### `registerSlotComponent`
+
+`registerSlotComponent` registers a slot component as shown.
+
+```ts
+config.registerSlotComponent({
+ slot: 'aboveContent',
+ name: 'header',
+ component: PageHeader,
+ predicates: [
+ RouteCondition('/de/about'),
+ ContentTypeCondition(['Document', 'News Item'])
+ ],
+});
+```
+
+A slot component must have the following parameters.
+
+`slot`
+: The name of the slot, where the slot components are stored.
+
+`name`
+: The name of the slot component that we are registering.
+
+`component`
+: The component that we want to render in the slot.
+
+`predicates`
+: A list of functions that return a function with this signature.
+
+ ```ts
+ export type SlotPredicate = (args: any) => boolean;
+ ```
+
+
+#### Predicate helpers
+
+There are two predicate helpers available in the Volto helpers.
+You can also create custom predicate helpers.
+
+
+##### `RouteCondition`
+
+```ts
+export function RouteCondition(path: string, exact?: boolean) {
+ return ({ pathname }: { pathname: string }) =>
+ Boolean(matchPath(pathname, { path, exact }));
+}
+```
+
+The `RouteCondition` predicate helper renders a slot if the specified route matches.
+It accepts the following parameters.
+
+`path`
+: String.
+ Required.
+ The route.
+
+`exact`
+: Boolean.
+ Optional.
+ If `true`, then the match will be exact, else matches "begins with", for the given string from `path`.
+
+
+##### `ContentTypeCondition`
+
+```ts
+export function ContentTypeCondition(contentType: string[]) {
+ return ({ content }: { content: Content }) =>
+ contentType.includes(content['@type']);
+}
+```
+
+The `ContentTypeCondition` helper predicate allows you to render a slot when the given content type matches the current content type.
+It accepts a list of possible content types.
+
+
+##### Custom predicates
+
+You can create your own predicate helpers to determine whether your slot component should render.
+The `SlotRenderer` will pass down the current `content` and the `pathname` into your custom predicate helper.
+You can also tailor your own `SlotRenderer`s, or shadow the original `SlotRenderer`, to satisfy your requirements.
+
+
+(slots-getSlot-label)=
+
+### `getSlot`
+
+`getSlot` returns the components to be rendered for the given named slot.
+You should use this method while building you own slot renderer or customizing the existing `SlotRenderer`.
+You can use the implementation of `SlotRenderer` as a template.
+This is the signature:
+
+```ts
+config.getSlot(name: string, args: GetSlotArgs): GetSlotReturn
+```
+
+It has the following parameters.
+
+`name`
+: String.
+ Required.
+ The name of the slot we want to render.
+
+`args`
+: Object.
+ Required.
+ An object containing the arguments to pass to the predicates.
+
+
+(slots-getSlotComponents-label)=
+
+### `getSlotComponents`
+
+`getSlotComponents` returns the list of named slot components registered in a given slot.
+This is useful to debug what is registered and in what order, informing you whether you need to change their order.
+This is the signature:
+
+```ts
+config.getSlotComponents(slot: string): string[]
+```
+
+`slot`
+: String.
+ Required.
+ The name of the slot where the slot components are stored.
+
+
+(slots-getSlotComponent-label)=
+
+### `getSlotComponent`
+
+`getSlotComponent` returns the registered named component's data for the given slot component name.
+This is the signature:
+
+```ts
+config.getSlotComponent(slot: string, name: string): SlotComponent[]
+```
+
+`slot`
+: String.
+ Required.
+ The name of the slot where the slot components are stored.
+
+`name`
+: String.
+ Required.
+ The name of the slot component to retrieve.
+
+
+(slots-reorderSlotComponent-label)=
+
+### `reorderSlotComponent`
+
+`reorderSlotComponent` reorders the list of named slot components registered per slot.
+
+Given a `slot` and the `name` of a slot component, you must either specify the desired `position` or perform an `action` to reposition the slot component in the given slot, but not both.
+
+The available actions are `"after"`, `"before"`, `"first"`, and `"last"`.
+`"first"` and `"last"` do not accept a `target`.
+
+This is the signature:
+
+```ts
+config.reorderSlotComponent({ slot, name, position, action, target }: {
+ slot: string;
+ name: string;
+ position?: number | undefined;
+ action?: "after" | "before" | "first" | "last" | undefined;
+ target?: string | undefined;
+}): void
+```
+
+`slot`
+: String.
+ Required.
+ The name of the slot where the slot components are stored.
+
+`name`
+: String.
+ Required.
+ The name of the slot component to reposition in the list of slot components.
+
+`position`
+: Number.
+ Exactly one of `position` or `action` is required.
+ The destination position in the registered list of slot components.
+ The position is zero-indexed.
+
+`action`
+: Enum: `"after"` | `"before"` | `"first"` | `"last"` | undefined.
+ Exactly one of `position` or `action` is required.
+ The action to perform on `name`.
+
+ When using either the `"after"` or `"before"` values, a `target` is required.
+ The slot component will be repositioned relative to the `target`.
+
+ When using either the `"first"` and `"last"` values, a `target` must not be used.
+ The slot component will be repositioned to either the first or last position.
+
+`target`
+: String.
+ Required when `action` is either `"after"` or `"before"`, else must not be provided.
+ The name of the slot component targeted for the given `action`.
+
+
+(slots-unregisterSlotComponent-label)=
+
+### `unregisterSlotComponent`
+
+`unregisterSlotComponent` removes a registration for a named slot component, given its registration position.
+This is the signature:
+
+```ts
+config.unRegisterSlotComponent(slot: string, name: string, position: number): void
+```
+
+`slot`
+: String.
+ Required.
+ The name of the slot that contains the slot component to unregister.
+
+`name`
+: String.
+ Required.
+ The name of the slot component to unregister inside the component.
+
+`position`
+: Number.
+ Required.
+ The component position to remove in the slot component registration.
+ Use {ref}`slots-getSlotComponent-label` to find the zero-indexed position of the registered component to remove.
diff --git a/docs/source/getting-started/roadmap.md b/docs/source/getting-started/roadmap.md
index cba274e63b..244b11159c 100644
--- a/docs/source/getting-started/roadmap.md
+++ b/docs/source/getting-started/roadmap.md
@@ -89,3 +89,16 @@ Not really advanced but perhaps less common, here's some stuff you can do:
- Customize Volto's Webpack configuration or project loader using Razzle
- Write a Redux middleware
- Start hacking on Volto.
+
+## Add-on first approach
+
+Developing for Plone's frontend means to add code to a Volto project.
+The frontend files of the project are created in the `frontend` folder.
+The generator also creates a default add-on in the `frontend/src/addons` folder.
+The project is already configured to use this add-on.
+Add your code and customizations to this add-on, and Volto will load them on start up or a restart.
+This add-on is configured as a theme add-on, so you are able to customize the look and feel of your site as well.
+
+```{seealso}
+For more information about how to develop a Volto project as an add-on, see {doc}`training:voltoaddons/index`.
+```
diff --git a/docs/source/recipes/developing-a-project.md b/docs/source/recipes/developing-a-project.md
deleted file mode 100644
index c015c9b3f8..0000000000
--- a/docs/source/recipes/developing-a-project.md
+++ /dev/null
@@ -1,32 +0,0 @@
----
-myst:
- html_meta:
- "description": "Developing a Volto project"
- "property=og:description": "Developing a Volto project"
- "property=og:title": "AppExtras component"
- "keywords": "Volto, Plone, frontend, React, app extra component"
----
-
-(frontend-developing-a-project-label)=
-
-# Developing a project
-
-When you start developing a Plone project, it is recommended that you use Plone's cookiecutter boilerplate project generator.
-See {doc}`plone:install/create-project` for more information.
-
-The generator will output the project folder structure.
-It is organized in three folders named `frontend`, `backend`, and `devops`, each of which corresponds to its primary use.
-It also contains the convenience `Makefile` commands to perform all the usual actions while developing.
-
-## Add-on first approach
-
-Developing for Plone's frontend means to add code to a Volto project.
-The frontend files of the project are created in the `frontend` folder.
-The generator also creates a default add-on in the `frontend/src/addons` folder.
-The project is already configured to use this add-on.
-Add your code and customizations to this add-on, and Volto will load them on start up or a restart.
-This add-on is configured as a theme add-on, so you are able to customize the look and feel of your site as well.
-
-```{seealso}
-For more information about how to develop a Volto project as an add-on, see {doc}`training:voltoaddons/index`.
-```
diff --git a/docs/source/recipes/index.md b/docs/source/recipes/index.md
index d75154ba39..97133ec6dd 100644
--- a/docs/source/recipes/index.md
+++ b/docs/source/recipes/index.md
@@ -13,7 +13,6 @@ myst:
:maxdepth: 1
creating-project
-developing-a-project
folder-structure
environment-variables
customizing-components
diff --git a/docs/source/release-notes/index.md b/docs/source/release-notes/index.md
index b148fe9e5c..1273a7286f 100644
--- a/docs/source/release-notes/index.md
+++ b/docs/source/release-notes/index.md
@@ -17,6 +17,50 @@ myst:
+## 18.0.0-alpha.16 (2024-03-02)
+
+### Internal
+
+- Update dependencies
+ Fix prettier due to new version @sneridagh [#5815](https://github.com/plone/volto/issues/5815)
+
+### Documentation
+
+- Linkcheck thinks `README.md` is `http://README.md`. Bad linkcheck, no more 🍺 for you. @stevepiercy [#5816](https://github.com/plone/volto/issues/5816)
+
+## 18.0.0-alpha.15 (2024-03-01)
+
+### Breaking
+
+- Upgrade Volto core to use React 18.2.0 @sneridagh [#3221](https://github.com/plone/volto/issues/3221)
+
+## 18.0.0-alpha.14 (2024-03-01)
+
+### Breaking
+
+- Improved accessibility of logo component. @Molochem [#5776](https://github.com/plone/volto/issues/5776)
+
+### Feature
+
+- Support for slots @sneridagh [#5775](https://github.com/plone/volto/issues/5775)
+
+### Bugfix
+
+- Fixed toolbar menus not closing when clicking again on the toolbar buttons that show menus. @ichim-david
+ Add focus-visible rule to toolbar buttons so that it's visible to the user what button is focused when using tab navigation @ichim-david [#5645](https://github.com/plone/volto/issues/5645)
+- Enhance findBlocks to check for blocks also in data for add-ons such as @eeacms/volto-tabs-block. @ichim-david [#5796](https://github.com/plone/volto/issues/5796)
+- Fixed ArrayWidget sorting items. @giuliaghisini [#5805](https://github.com/plone/volto/issues/5805)
+
+### Internal
+
+- New types declarations with @types/react@18 - make tsc happy @sneridagh [#5814](https://github.com/plone/volto/issues/5814)
+
+### Documentation
+
+- Added Release Management Notes. @sneridagh @stevepiercy [#5358](https://github.com/plone/volto/issues/5358)
+- Delete redundant `developing-a-project.md`. @stevepiercy [#5675](https://github.com/plone/volto/issues/5675)
+- Removed Memori and TwinCreator websites from README.md no longer made using Volto and giving 404 error. @ichim-david [#5802](https://github.com/plone/volto/issues/5802)
+
## 18.0.0-alpha.13 (2024-02-22)
### Bugfix
diff --git a/docs/source/upgrade-guide/index.md b/docs/source/upgrade-guide/index.md
index a89577132b..06427363f5 100644
--- a/docs/source/upgrade-guide/index.md
+++ b/docs/source/upgrade-guide/index.md
@@ -28,6 +28,27 @@ Thus it is safe to run it on top of your project and answer the prompts.
## Upgrading to Volto 18.x.x
+### Volto runs now on React 18.2.0
+
+We have updated Volto to use React 18.
+This has been the latest published stable version since June 2022.
+This aligns Volto with the latests developments in the React ecosystem and opens the door to up to date software and React features, like client side `Suspense` and others:
+
+- Concurrent rendering in client (Suspense)
+- Automatic batching updates
+- Transitions
+- New hooks `useId`, `useTransition`, `useDeferredValue`, `useSyncExternalStore`, and other hooks
+
+### `react-portal` dependency removed
+
+`react-portal` is deprecated and it was removed from Volto.
+The Volto code that relied on it was mainly CMS UI components.
+If your project relies on it, either in your code or the shadowed components you may have, you should update to use the standard React API, `createPortal`.
+You can update your shadows taking the modified components as templates.
+As a last resort, you can install `react-portal` as a dependency of your project.
+However, this is discouraged, because the React 18 rendering could have unexpected side effects.
+It is recommended that you use the React API instead.
+
### ESlint project configuration update
`@plone/registry` and [other packages on which Volto depends](https://github.com/plone/volto/tree/main/packages) are now stand-alone releases in the monorepo structure released in 18.0.0-alpha.4.
diff --git a/package.json b/package.json
index c5b5b04a23..76ffe12480 100644
--- a/package.json
+++ b/package.json
@@ -4,9 +4,10 @@
"scripts": {
"preinstall": "npx only-allow pnpm",
"postinstall": "make setup",
- "watch": "turbo run watch",
- "build:deps": "turbo run build --filter @plone/registry --filter @plone/client",
+ "watch": "pnpm --filter @plone/registry watch",
+ "build:deps": "pnpm --filter @plone/registry --filter @plone/client build",
"build:registry": "pnpm --filter @plone/registry run build",
+ "build:components": "pnpm --filter @plone/components run build",
"build": "pnpm build:deps && pnpm --filter @plone/volto build",
"start": "pnpm build:deps && pnpm --filter @plone/volto start",
"start:project": "pnpm --filter plone run start",
@@ -17,34 +18,36 @@
"i18n:ci": "pnpm --filter @plone/volto i18n:ci",
"prettier": "prettier --check '{apps,packages}/**/*.{js,jsx,ts,tsx}'",
"prettier:fix": "prettier --write '{apps,packages}/**/*.{js,jsx,ts,tsx}' ",
+ "stylelint": "stylelint '{apps,packages}/**/*.{css,scss,less}'",
+ "stylelint:fix": "stylelint '{packages}/**/*.{css,scss,less}' --fix",
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
"lint-staged": "lint-staged",
- "prepare": "husky install",
+ "lockhook": "node packages/scripts/lockhook.js",
+ "prepare": "husky",
"husky:uninstall": "husky uninstall",
"prereleaser": "node packages/scripts/preleaser.js"
},
"devDependencies": {
- "@parcel/packager-ts": "2.11.0",
- "@parcel/transformer-typescript-types": "2.11.0",
+ "@parcel/packager-ts": "^2.12.0",
+ "@parcel/transformer-typescript-types": "^2.12.0",
"@typescript-eslint/eslint-plugin": "^6.8.0",
"@typescript-eslint/parser": "^6.8.0",
- "eslint": "^8.53.0",
- "eslint-config-prettier": "9.0.0",
+ "concurrently": "^8.2.2",
+ "eslint": "^8.57.0",
+ "eslint-config-prettier": "9.1.0",
"eslint-import-resolver-typescript": "^3.6.1",
- "eslint-plugin-import": "2.28.1",
- "eslint-plugin-prettier": "5.0.0",
+ "eslint-plugin-import": "2.29.1",
+ "eslint-plugin-prettier": "5.1.3",
"eslint-plugin-react": "7.33.2",
- "husky": "^8.0.3",
- "lint-staged": "15.0.2",
- "prettier": "3.0.3",
- "stylelint": "^15.11.0",
+ "husky": "9.0.11",
+ "lint-staged": "15.2.2",
+ "prettier": "3.2.5",
+ "stylelint": "^16.2.1",
"tsconfig": "workspace:*",
- "turbo": "latest",
"typescript": "5.2.2",
- "vitest": "^0.34.6",
- "yarnhook": "0.6.1"
+ "vitest": "^1.3.1"
},
- "packageManager": "pnpm@8.9.0",
+ "packageManager": "pnpm@8.15.4",
"pnpm": {
"overrides": {
"webpack": "5.90.1"
diff --git a/packages/blocks/package.json b/packages/blocks/package.json
index d09d8c474d..b098e5c688 100644
--- a/packages/blocks/package.json
+++ b/packages/blocks/package.json
@@ -54,13 +54,13 @@
},
"dependencies": {},
"devDependencies": {
- "@parcel/packager-ts": "2.10.2",
- "@parcel/transformer-typescript-types": "2.10.2",
+ "@parcel/packager-ts": "^2.12.0",
+ "@parcel/transformer-typescript-types": "^2.12.0",
"@plone/registry": "workspace:*",
"@plone/types": "workspace:*",
"@types/react": "^18",
"@types/react-dom": "^18",
- "parcel": "2.10.2",
+ "parcel": "^2.12.0",
"release-it": "17.1.1",
"tsconfig": "workspace:*",
"typescript": "5.2.2",
diff --git a/packages/client/CHANGELOG.md b/packages/client/CHANGELOG.md
index d954167003..112e69e9c8 100644
--- a/packages/client/CHANGELOG.md
+++ b/packages/client/CHANGELOG.md
@@ -8,6 +8,16 @@
+## 1.0.0-alpha.13 (2024-03-02)
+
+### Internal
+
+- Update dependencies @sneridagh [#5815](https://github.com/plone/volto/pull/5815)
+
+### Documentation
+
+- Reorganize `README.md`, merging content into authoritative locations. Add `awesome_bot` to check links in all READMEs. @stevepiercy [#5437](https://github.com/plone/volto/pull/5437)
+
## 1.0.0-alpha.12 (2024-01-02)
### Internal
diff --git a/packages/client/news/5437.documentation b/packages/client/news/5437.documentation
deleted file mode 100644
index 377e651c6b..0000000000
--- a/packages/client/news/5437.documentation
+++ /dev/null
@@ -1 +0,0 @@
-Reorganize `README.md`, merging content into authoritative locations. Add `awesome_bot` to check links in all READMEs. @stevepiercy
diff --git a/packages/client/news/5824.internal b/packages/client/news/5824.internal
new file mode 100644
index 0000000000..c17d9fa550
--- /dev/null
+++ b/packages/client/news/5824.internal
@@ -0,0 +1 @@
+Upgrade TSQ to latest @sneridagh
diff --git a/packages/client/package.json b/packages/client/package.json
index 5f8a09b0f2..7bec23b8e8 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -8,7 +8,7 @@
}
],
"license": "MIT",
- "version": "1.0.0-alpha.12",
+ "version": "1.0.0-alpha.13",
"repository": {
"type": "git",
"url": "git@github.com:plone/volto.git"
@@ -61,10 +61,6 @@
"vitest": "vitest",
"check-ts": "tsc --project tsconfig.json",
"coverage": "vitest run --coverage --no-threads",
- "lint": "eslint 'src/**/*.{js,ts,tsx}' --quiet",
- "lint:fix": "eslint 'src/**/*.{js,ts,tsx}' --quiet --fix",
- "prettier": "prettier --check 'src/**/*.{js,jsx,ts,tsx}'",
- "prettier:fix": "prettier --write 'src/**/*.{js,jsx,ts,tsx}'",
"dry-release": "release-it --dry-run",
"release": "release-it",
"release-major-alpha": "release-it major --preRelease=alpha",
@@ -82,26 +78,26 @@
"@types/react-dom": "18.2.12",
"@types/uuid": "^9.0.2",
"@vitejs/plugin-react": "^4.1.0",
- "@vitest/coverage-c8": "0.28.5",
+ "@vitest/coverage-v8": "^1.3.1",
"glob": "7.1.6",
"jsdom": "^21.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "release-it": "16.2.1",
- "tsup": "^8.0.1",
+ "release-it": "17.1.1",
+ "tsup": "^8.0.2",
"typescript": "5.2.2",
- "uuid": "^9.0.0",
- "vite": "^4.5.1",
- "vite-plugin-dts": "^3.6.0",
- "vitest": "^0.34.6",
- "wait-on": "^7.0.1"
+ "uuid": "^9.0.1",
+ "vite": "^5.1.4",
+ "vite-plugin-dts": "^3.7.3",
+ "vitest": "^1.3.1",
+ "wait-on": "^7.2.0"
},
"dependencies": {
- "@tanstack/react-query": "5.0.5",
- "axios": "^1.5.1",
- "debug": "4.3.2",
- "query-string": "^8.1.0",
- "zod": "^3.21.4"
+ "@tanstack/react-query": "5.24.1",
+ "axios": "^1.6.7",
+ "debug": "4.3.4",
+ "query-string": "^9.0.0",
+ "zod": "^3.22.4"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
diff --git a/packages/client/testRunner.js b/packages/client/testRunner.js
index 86fc6367dc..d3d9870b9c 100644
--- a/packages/client/testRunner.js
+++ b/packages/client/testRunner.js
@@ -46,7 +46,7 @@ directories.forEach((dir) => {
// if there are any matches, run vitest on this directory
if (matches.length > 0) {
console.log(`Running vitest on src/${dir}`);
- execSync(`pnpm vitest run src/restapi/${dir} --no-threads`, {
+ execSync(`pnpm vitest run src/restapi/${dir}`, {
stdio: 'inherit',
});
}
diff --git a/packages/client/vite.config.ts b/packages/client/vite.config.ts
index 9676ff4d51..9c0f8405d4 100644
--- a/packages/client/vite.config.ts
+++ b/packages/client/vite.config.ts
@@ -23,6 +23,7 @@ export default defineConfig({
'@tanstack/react-query': 'reactQuery',
axios: 'axios',
zod: 'zod',
+ 'query-string': 'queryString',
},
},
},
diff --git a/packages/client/vitest.config.ts b/packages/client/vitest.config.ts
index dc525dfa51..02c3465eae 100644
--- a/packages/client/vitest.config.ts
+++ b/packages/client/vitest.config.ts
@@ -5,20 +5,25 @@ export default defineConfig({
globals: true,
environment: 'jsdom',
setupFiles: './setupTesting.ts',
+ poolOptions: {
+ threads: {
+ singleThread: true,
+ },
+ },
// you might want to disable it, if you don't have tests that rely on CSS
// since parsing CSS is slow
css: true,
environmentOptions: {
jsdom: {
/*
- We need to set the url parameter below to the url of the API server
+ We need to set the url parameter below to the url of the API server
to avoid CORS issue.
-
- This is because JSDom environment sets the
- origin header (axios cannot control it on its own, just like in a
- browser) automatically to the url specified here.
-
- If this url is not provided then vitest sets the default value as
+
+ This is because JSDom environment sets the
+ origin header (axios cannot control it on its own, just like in a
+ browser) automatically to the url specified here.
+
+ If this url is not provided then vitest sets the default value as
localhost:3000 which causes CORS error with the test server.
*/
url: 'http://localhost:55001',
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 5106923826..9c696a5e92 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -8,6 +8,28 @@
+## 2.0.0-alpha.4 (2024-03-02)
+
+### Bugfix
+
+- Remove `lodash` dependency.
+ Several packages upgrades and general cleanup. @sneridagh [#5822](https://github.com/plone/volto/issues/5822)
+- Proxy the `PopoverContext` inside the `Select` component, to be able to override it from the outside @sneridagh [#5823](https://github.com/plone/volto/issues/5823)
+
+### Internal
+
+- Update dependencies @sneridagh [#5815](https://github.com/plone/volto/issues/5815)
+
+## 2.0.0-alpha.3 (2024-03-01)
+
+### Breaking
+
+- Use `pathname` instead of the full location as prop in `BlocksRenderer` @sneridagh [#5798](https://github.com/plone/volto/issues/5798)
+
+### Bugfix
+
+- Fix `lodash` imports for bundling @sneridagh [#5798](https://github.com/plone/volto/issues/5798)
+
## 2.0.0-alpha.2 (2024-02-23)
### Bugfix
diff --git a/packages/components/README.md b/packages/components/README.md
index 7f0fc45617..0731a8e49f 100644
--- a/packages/components/README.md
+++ b/packages/components/README.md
@@ -45,7 +45,7 @@ You can use the CSS bundled for all components in a single file, or use the spec
They are distributed along with the components code in the `dist` folder of the library.
```js
-import '@plone/components/basic.css';
+import '@plone/components/dist/basic.css';
```
or selectively:
@@ -75,8 +75,8 @@ You could take Quanta as example to build your own layer of styles over basic st
To use a theme built upon the basic styling, you need to import both the basic and the theme CSS, in this order:
```js
-import '@plone/components/basic.css';
-import '@plone/components/quanta.css';
+import '@plone/components/dist/basic.css';
+import '@plone/components/dist/quanta.css';
```
You have the option of doing it selectively per component, too:
diff --git a/packages/components/news/5798.breaking b/packages/components/news/5798.breaking
deleted file mode 100644
index 263432a2c2..0000000000
--- a/packages/components/news/5798.breaking
+++ /dev/null
@@ -1 +0,0 @@
-Use `pathname` instead of the full location as prop in `BlocksRenderer` @sneridagh
diff --git a/packages/components/news/5798.bugfix b/packages/components/news/5798.bugfix
deleted file mode 100644
index d6e35d6924..0000000000
--- a/packages/components/news/5798.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Fix `lodash` imports for bundling @sneridagh
diff --git a/packages/components/news/5824.bugfix b/packages/components/news/5824.bugfix
new file mode 100644
index 0000000000..c1568b430f
--- /dev/null
+++ b/packages/components/news/5824.bugfix
@@ -0,0 +1 @@
+Fix some case inconsistencies in CSS declarations @sneridagh
diff --git a/packages/components/package.json b/packages/components/package.json
index 3a0bd73e9a..670db5334e 100644
--- a/packages/components/package.json
+++ b/packages/components/package.json
@@ -8,7 +8,7 @@
}
],
"license": "MIT",
- "version": "2.0.0-alpha.2",
+ "version": "2.0.0-alpha.4",
"repository": {
"type": "git",
"url": "http://github.com/plone/components.git"
@@ -45,13 +45,12 @@
"quanta"
],
"scripts": {
- "build": "parcel build --no-cache && pnpm build:srctypes && pnpm build:css",
+ "build": "parcel build --no-cache && pnpm build:css",
"build:css": "pnpm build:basic && pnpm build:quanta",
"build:basic": "lightningcss --browserslist --bundle --sourcemap src/styles/basic/main.css -o basic.css && mv basic.css* dist/.",
"build:quanta": "lightningcss --browserslist --bundle --sourcemap src/styles/quanta/main.css -o quanta.css && mv quanta.css* dist/.",
- "build:srctypes": "mkdir -p dist/src && cp dist/index.d.ts dist/src/index.d.ts",
"check-ts": "tsc --project tsconfig.json",
- "test": "vitest --no-threads --passWithNoTests",
+ "test": "vitest --passWithNoTests",
"coverage": "vitest run --coverage --no-threads",
"lint": "pnpm eslint && pnpm prettier && pnpm stylelint",
"format": "pnpm eslint:fix && pnpm prettier:fix && pnpm stylelint:fix",
@@ -77,62 +76,57 @@
"not dead"
],
"devDependencies": {
- "@parcel/config-default": "2.11.0",
- "@parcel/core": "^2.11.0",
- "@parcel/packager-ts": "^2.11.0",
- "@parcel/transformer-js": "^2.11.0",
- "@parcel/transformer-react-refresh-wrap": "^2.11.0",
- "@parcel/transformer-typescript-types": "^2.11.0",
+ "@parcel/config-default": "^2.12.0",
+ "@parcel/core": "^2.12.0",
+ "@parcel/packager-ts": "^2.12.0",
+ "@parcel/transformer-js": "^2.12.0",
+ "@parcel/transformer-react-refresh-wrap": "^2.12.0",
+ "@parcel/transformer-typescript-types": "^2.12.0",
"@plone/types": "workspace: *",
"@react-types/shared": "^3.22.0",
- "@storybook/addon-essentials": "^7.5.1",
- "@storybook/addon-interactions": "^7.5.1",
- "@storybook/addon-links": "^7.5.1",
- "@storybook/addon-mdx-gfm": "^7.5.1",
- "@storybook/blocks": "^7.5.1",
+ "@storybook/addon-essentials": "^7.6.17",
+ "@storybook/addon-interactions": "^7.6.17",
+ "@storybook/addon-links": "^7.6.17",
+ "@storybook/blocks": "^7.6.17",
"@storybook/manager-api": "^7.6.17",
- "@storybook/react": "^7.5.1",
- "@storybook/react-vite": "^7.5.1",
+ "@storybook/react": "^7.6.17",
+ "@storybook/react-vite": "^7.6.17",
"@storybook/testing-library": "^0.2.2",
"@storybook/theming": "^7.6.17",
- "@testing-library/jest-dom": "6.1.4",
- "@testing-library/react": "14.0.0",
+ "@testing-library/jest-dom": "6.4.2",
+ "@testing-library/react": "14.2.1",
"@types/jest-axe": "^3.5.7",
- "@types/lodash": "^4.14.201",
"@types/react": "^18",
"@types/react-dom": "^18",
"@typescript-eslint/eslint-plugin": "^6.8.0",
"@typescript-eslint/parser": "^6.8.0",
"@vitejs/plugin-react": "^4.1.0",
- "@vitest/coverage-c8": "0.33.0",
+ "@vitest/coverage-v8": "^1.3.1",
"browserslist": "^4.23.0",
- "eslint": "^8.53.0",
- "eslint-plugin-storybook": "^0.6.15",
- "globby": "^14.0.0",
+ "eslint": "^8.57.0",
+ "eslint-plugin-storybook": "^0.8.0",
"jest-axe": "^8.0.0",
"jsdom": "^22.1.0",
- "lightningcss": "^1.23.0",
- "lightningcss-cli": "^1.23.0",
- "parcel": "^2.11.0",
+ "lightningcss": "^1.24.0",
+ "lightningcss-cli": "^1.24.0",
+ "parcel": "^2.12.0",
"parcel-optimizer-react-client": "workspace:^",
- "prettier": "3.0.3",
- "release-it": "16.2.1",
- "storybook": "^7.5.1",
- "stylelint": "15.11.0",
- "stylelint-config-idiomatic-order": "9.0.0",
+ "prettier": "3.2.5",
+ "release-it": "17.1.1",
+ "storybook": "^7.6.17",
+ "stylelint": "16.2.1",
+ "stylelint-config-idiomatic-order": "10.0.0",
"stylelint-config-prettier": "9.0.5",
- "stylelint-prettier": "4.0.2",
+ "stylelint-prettier": "5.0.0",
"typescript": "5.2.2",
- "vite": "^4.5.0",
- "vitest": "^0.34.6",
+ "vite": "^5.1.4",
+ "vitest": "^1.3.1",
"vitest-axe": "^0.1.0"
},
"dependencies": {
"@react-aria/utils": "^3.22.0",
"@react-spectrum/utils": "^3.11.1",
- "classnames": "^2.3.2",
"clsx": "^2.0.0",
- "lodash": "^4.17.21",
"react-aria-components": "^1.1.1"
},
"peerDependencies": {
diff --git a/packages/components/src/components/Container/Container.tsx b/packages/components/src/components/Container/Container.tsx
index 622c97d052..1f157792ed 100644
--- a/packages/components/src/components/Container/Container.tsx
+++ b/packages/components/src/components/Container/Container.tsx
@@ -1,6 +1,6 @@
import React, { ReactNode } from 'react';
import { getElementType } from '../helpers';
-import cx from 'classnames';
+import cx from 'clsx';
type ContainerProps = {
/** Primary content. */
diff --git a/packages/components/src/components/Select/Select.tsx b/packages/components/src/components/Select/Select.tsx
index cbe5c31c42..fdbe84c784 100644
--- a/packages/components/src/components/Select/Select.tsx
+++ b/packages/components/src/components/Select/Select.tsx
@@ -7,10 +7,12 @@ import {
ListBoxItem,
ListBoxItemProps,
Popover,
+ PopoverContext,
Select as RACSelect,
SelectProps as RACSelectProps,
SelectValue,
Text,
+ useContextProps,
ValidationResult,
} from 'react-aria-components';
@@ -49,6 +51,9 @@ export function Select({
items,
...props
}: SelectProps) {
+ // In case that we want to customize the Popover, we proxy the PopoverContext props down
+ const [popoverProps] = useContextProps({}, null, PopoverContext);
+
return (
{({ isOpen }) => (
@@ -63,7 +68,7 @@ export function Select({
{description && {description}}
{errorMessage}
-
+
{children}
>
diff --git a/packages/components/src/components/quanta/Select/Select.tsx b/packages/components/src/components/quanta/Select/Select.tsx
index 664121627c..6e43494084 100644
--- a/packages/components/src/components/quanta/Select/Select.tsx
+++ b/packages/components/src/components/quanta/Select/Select.tsx
@@ -1,12 +1,14 @@
import React from 'react';
-import { TextFieldContext } from 'react-aria-components';
+import { SelectContext, PopoverContext } from 'react-aria-components';
import { Select, SelectItem, SelectProps } from '../../Select/Select';
export function QuantaSelect(props: SelectProps) {
return (
-
-
-
+
+
+
+
+
);
}
diff --git a/packages/components/src/helpers/blocks.ts b/packages/components/src/helpers/blocks.ts
index 73accdfbf9..34a10f4ba2 100644
--- a/packages/components/src/helpers/blocks.ts
+++ b/packages/components/src/helpers/blocks.ts
@@ -1,13 +1,9 @@
import type { Content } from '@plone/types';
-import find from 'lodash/find';
-import keys from 'lodash/keys';
-import endsWith from 'lodash/endsWith';
export function hasBlocksData(content: Content) {
return (
- find(
- keys(content),
- (key) => key !== 'volto.blocks' && endsWith(key, 'blocks'),
+ Object.keys(content).find(
+ (key) => key !== 'volto.blocks' && key.endsWith('blocks'),
) !== undefined
);
}
diff --git a/packages/components/src/stories/Introduction.stories.mdx b/packages/components/src/stories/Introduction.stories.mdx
index 1743ccc57c..41ecc9f5d6 100644
--- a/packages/components/src/stories/Introduction.stories.mdx
+++ b/packages/components/src/stories/Introduction.stories.mdx
@@ -33,3 +33,47 @@ const MyComponent = (props) => {
export default MyComponent;
```
+## Styling
+
+This package provide a basic set of CSS rules.
+You should add them to your project build to make the components properly styled.
+
+You can bring your own styles, but the CSS you provide should replace what the basic stylying does and style the bare components from scratch.
+
+You can use the CSS bundled for all components in a single file, or use the specific files for your components.
+They are distributed along with the components code in the `dist` folder of the library.
+
+```js
+import '@plone/components/dist/basic.css';
+```
+
+or selectively:
+
+```js
+import '@plone/components/src/styles/basic/TextField.css';
+```
+
+
+## Quanta
+
+This package also features the Quanta components.
+The Quanta theme is an example of it.
+These components use the basic styling as a baseline, not only in styling, but also in the component side, reusing the CSS and custom CSS properties in it.
+
+Quanta is built upon the basic styles in an additive way.
+The use of the Quanta CSS implies using it upon basic styling.
+You could take Quanta as example to build your own layer of styles over basic styling for your theme.
+
+To use a theme built upon the basic styling, you need to import both the basic and the theme CSS, in this order:
+
+```js
+import '@plone/components/dist/basic.css';
+import '@plone/components/dist/quanta.css';
+```
+
+You have the option of doing it selectively per component, too:
+
+```js
+import '@plone/components/src/styles/basic/TextField.css';
+import '@plone/components/src/styles/quanta/TextField.css';
+```
diff --git a/packages/components/src/styles/basic/main.css b/packages/components/src/styles/basic/main.css
index 09a850cf68..fa7999e24f 100644
--- a/packages/components/src/styles/basic/main.css
+++ b/packages/components/src/styles/basic/main.css
@@ -4,7 +4,7 @@
@import './Label.css';
@import './Checkbox.css';
@import './CheckboxGroup.css';
-@import './Combobox.css';
+@import './ComboBox.css';
@import './NumberField.css';
@import './RadioGroup.css';
@import './Switch.css';
@@ -22,8 +22,8 @@
@import './TextField.css';
@import './Menu.css';
-@import './Listbox.css';
-@import './Gridlist.css';
+@import './ListBox.css';
+@import './GridList.css';
@import './Form.css';
@import './Table.css';
diff --git a/packages/components/src/styles/quanta/Select.css b/packages/components/src/styles/quanta/Select.css
index 99855ac40a..dda92780e5 100644
--- a/packages/components/src/styles/quanta/Select.css
+++ b/packages/components/src/styles/quanta/Select.css
@@ -1,6 +1,6 @@
/* Quanta does not have to redefine colors, but it has to override the original ones coming
from the basic styles. Then, if people override the basic ones, then they won't get overridden in Quanta. */
-.react-aria-Select {
+.q.react-aria-Select {
.react-aria-Label {
color: var(--quanta-sapphire);
}
@@ -30,7 +30,7 @@ from the basic styles. Then, if people override the basic ones, then they won't
}
}
-.react-aria-Popover[data-trigger='Select'] {
+.q.react-aria-Popover[data-trigger='Select'] {
.react-aria-ListBoxItem {
--highlight-background: var(--basic-300);
--highlight-foreground: var(--text-color);
diff --git a/packages/components/src/views/RenderBlocks/RenderBlocks.tsx b/packages/components/src/views/RenderBlocks/RenderBlocks.tsx
index 2bde61d6f6..e0489596b8 100644
--- a/packages/components/src/views/RenderBlocks/RenderBlocks.tsx
+++ b/packages/components/src/views/RenderBlocks/RenderBlocks.tsx
@@ -1,6 +1,4 @@
import React, { Fragment } from 'react';
-// import { defineMessages, useIntl } from 'react-intl';
-
import { hasBlocksData } from '../../helpers/blocks';
import { DefaultBlockView } from './DefaultBlockView';
import type { Content } from '@plone/types';
diff --git a/packages/components/tsconfig.node.json b/packages/components/tsconfig.node.json
index b5376a7db7..473bb13b8a 100644
--- a/packages/components/tsconfig.node.json
+++ b/packages/components/tsconfig.node.json
@@ -5,5 +5,5 @@
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
- "include": ["vitest.config.ts"]
+ "include": ["vitest.config.mts"]
}
diff --git a/packages/components/vite.config.ts b/packages/components/vite.config.mts
similarity index 94%
rename from packages/components/vite.config.ts
rename to packages/components/vite.config.mts
index 0bf55cebb7..e79241277d 100644
--- a/packages/components/vite.config.ts
+++ b/packages/components/vite.config.mts
@@ -18,8 +18,6 @@ export default defineConfig({
'react-aria',
'react-aria-components',
'@react-spectrum/utils',
- 'classnames',
- 'lodash',
],
output: {
globals: {
diff --git a/packages/components/vitest.config.ts b/packages/components/vitest.config.mts
similarity index 100%
rename from packages/components/vitest.config.ts
rename to packages/components/vitest.config.mts
diff --git a/packages/coresandbox/package.json b/packages/coresandbox/package.json
index e90e2e480c..aaf007882e 100644
--- a/packages/coresandbox/package.json
+++ b/packages/coresandbox/package.json
@@ -26,17 +26,17 @@
"react"
],
"peerDependencies": {
- "react": "17.0.2",
- "react-dom": "17.0.2",
+ "react": "18.2.0",
+ "react-dom": "18.2.0",
"react-intl": "3.8.0",
"react-redux": "7.2.4",
"semantic-ui-react": "2.0.3"
},
"devDependencies": {
"@plone/types": "workspace:*",
+ "@types/react": "^18",
+ "@types/react-dom": "^18",
"@plone/registry": "workspace:*",
- "@types/react": "^17.0.52",
- "@types/react-dom": "^17",
"@types/react-redux": "^7.1.33"
}
}
diff --git a/packages/coresandbox/src/components/Slots/SlotTest.tsx b/packages/coresandbox/src/components/Slots/SlotTest.tsx
new file mode 100644
index 0000000000..66f095c552
--- /dev/null
+++ b/packages/coresandbox/src/components/Slots/SlotTest.tsx
@@ -0,0 +1,10 @@
+const SlotComponentTest = () => {
+ return (
+
+
This is a test slot component
+
It should appear above the Content
+
+ );
+};
+
+export default SlotComponentTest;
diff --git a/packages/coresandbox/src/index.ts b/packages/coresandbox/src/index.ts
index 1f4527a27c..b5c04d775b 100644
--- a/packages/coresandbox/src/index.ts
+++ b/packages/coresandbox/src/index.ts
@@ -9,6 +9,9 @@ import { conditionalVariationsSchemaEnhancer } from './components/Blocks/schemaE
import codeSVG from '@plone/volto/icons/code.svg';
import type { BlockConfigBase } from '@plone/types';
import type { ConfigType } from '@plone/registry';
+import SlotComponentTest from './components/Slots/SlotTest';
+import { ContentTypeCondition } from '@plone/volto/helpers';
+import { RouteCondition } from '@plone/volto/helpers/Slots';
const testBlock: BlockConfigBase = {
id: 'testBlock',
@@ -171,6 +174,13 @@ const applyConfig = (config: ConfigType) => {
config.blocks.blocksConfig.listing = listing(config);
config.views.contentTypesViews.Folder = NewsAndEvents;
+ config.registerSlotComponent({
+ slot: 'aboveContent',
+ name: 'testSlotComponent',
+ component: SlotComponentTest,
+ predicates: [ContentTypeCondition(['Document']), RouteCondition('/hello')],
+ });
+
return config;
};
diff --git a/packages/generator-volto/CHANGELOG.md b/packages/generator-volto/CHANGELOG.md
index 9a115b56da..1389dfe0d4 100644
--- a/packages/generator-volto/CHANGELOG.md
+++ b/packages/generator-volto/CHANGELOG.md
@@ -8,6 +8,18 @@
+## 9.0.0-alpha.8 (2024-03-02)
+
+### Internal
+
+- Update dependencies @sneridagh [#5815](https://github.com/plone/volto/issues/5815)
+
+## 9.0.0-alpha.7 (2024-03-01)
+
+### Bugfix
+
+- Switch peerDependencies to Volto 18 for generator-volto alpha's/latest. [#5780](https://github.com/plone/volto/issues/5780)
+
## 9.0.0-alpha.6 (2024-02-18)
### Bugfix
diff --git a/packages/generator-volto/generators/app/templates/package.json.tpl b/packages/generator-volto/generators/app/templates/package.json.tpl
index d19c2ca7c3..fb4195f4a1 100644
--- a/packages/generator-volto/generators/app/templates/package.json.tpl
+++ b/packages/generator-volto/generators/app/templates/package.json.tpl
@@ -151,11 +151,11 @@
"jest-junit": "8.0.0",
"mrs-developer": "^2.1.1",
"postcss": "8.4.13",
- "prettier": "3.0.3",
+ "prettier": "3.2.5",
"razzle": "4.2.18",
- "stylelint": "15.10.3",
- "stylelint-config-idiomatic-order": "9.0.0",
- "stylelint-prettier": "4.0.2",
+ "stylelint": "^16.2.1",
+ "stylelint-config-idiomatic-order": "10.0.0",
+ "stylelint-prettier": "5.0.0",
"ts-jest": "^26.4.2",
"ts-loader": "9.4.4",
"typescript": "5.2.2"
diff --git a/packages/generator-volto/news/5780.bugfix b/packages/generator-volto/news/5780.bugfix
deleted file mode 100644
index 996ccc7007..0000000000
--- a/packages/generator-volto/news/5780.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Switch peerDependencies to Volto 18 for generator-volto alpha's/latest.
\ No newline at end of file
diff --git a/packages/generator-volto/package.json b/packages/generator-volto/package.json
index a214df45dd..254e70b19f 100644
--- a/packages/generator-volto/package.json
+++ b/packages/generator-volto/package.json
@@ -10,7 +10,7 @@
}
],
"license": "MIT",
- "version": "9.0.0-alpha.6",
+ "version": "9.0.0-alpha.8",
"repository": {
"type": "git",
"url": "git+https://github.com/plone/generator-volto.git"
diff --git a/packages/parcel-optimizer-react-client/package.json b/packages/parcel-optimizer-react-client/package.json
index edb609816f..a9533e7207 100644
--- a/packages/parcel-optimizer-react-client/package.json
+++ b/packages/parcel-optimizer-react-client/package.json
@@ -4,10 +4,10 @@
"private": true,
"main": "ReactClientOptimizer.js",
"engines": {
- "parcel": "^2.10.2"
+ "parcel": "^2.12.0"
},
"dependencies": {
- "@parcel/plugin": "^2.10.2",
- "@parcel/utils": "^2.10.2"
+ "@parcel/plugin": "^2.12.0",
+ "@parcel/utils": "^2.12.0"
}
}
diff --git a/packages/registry/CHANGELOG.md b/packages/registry/CHANGELOG.md
index cdfd8c700e..b3468a12e4 100644
--- a/packages/registry/CHANGELOG.md
+++ b/packages/registry/CHANGELOG.md
@@ -8,6 +8,24 @@
+## 1.5.1 (2024-03-02)
+
+### Bugfix
+
+- Upgrade parcel @sneridagh [#5822](https://github.com/plone/volto/issues/5822)
+
+## 1.5.0 (2024-03-01)
+
+### Feature
+
+- Upgrade Volto core to use React 18.2.0 @sneridagh [#3221](https://github.com/plone/volto/issues/3221)
+
+## 1.4.0 (2024-03-01)
+
+### Feature
+
+- Support for slots @tiberiuichim @sneridagh [#5775](https://github.com/plone/volto/issues/5775)
+
## 1.3.1 (2024-02-20)
### Bugfix
diff --git a/packages/registry/package.json b/packages/registry/package.json
index c8a2fa1c1f..8a4b376f6a 100644
--- a/packages/registry/package.json
+++ b/packages/registry/package.json
@@ -9,7 +9,7 @@
],
"funding": "https://github.com/sponsors/plone",
"license": "MIT",
- "version": "1.3.1",
+ "version": "1.5.1",
"repository": {
"type": "git",
"url": "https://github.com/plone/volto.git"
@@ -50,7 +50,7 @@
},
"scripts": {
"watch": "parcel watch",
- "build": "parcel build",
+ "build": "parcel build --no-cache",
"test": "vitest",
"dry-release": "release-it --dry-run",
"release": "release-it",
@@ -73,12 +73,14 @@
"glob": "7.1.6"
},
"devDependencies": {
- "@parcel/packager-ts": "2.10.2",
- "@parcel/transformer-typescript-types": "2.10.2",
+ "@parcel/packager-ts": "^2.12.0",
+ "@parcel/transformer-typescript-types": "^2.12.0",
"@plone/types": "workspace:*",
- "@types/react": "^17.0.52",
- "@types/react-dom": "^17",
- "parcel": "2.10.2",
+ "@types/react": "^18",
+ "@types/react-dom": "^18",
+ "parcel": "^2.12.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
"release-it": "16.2.1",
"tsconfig": "workspace:*",
"typescript": "5.2.2",
diff --git a/packages/registry/src/index.ts b/packages/registry/src/index.ts
index dc81917a15..56740160ed 100644
--- a/packages/registry/src/index.ts
+++ b/packages/registry/src/index.ts
@@ -5,6 +5,10 @@ import type {
ComponentsConfig,
ExperimentalConfig,
SettingsConfig,
+ GetSlotArgs,
+ GetSlotReturn,
+ SlotComponent,
+ SlotPredicate,
SlotsConfig,
ViewsConfig,
WidgetsConfig,
@@ -125,7 +129,7 @@ class Config {
}
getComponent(
- options: { name: string; dependencies: string[] | string } | string,
+ options: { name: string; dependencies?: string[] | string } | string,
): GetComponentResult {
if (typeof options === 'object') {
const { name, dependencies = '' } = options;
@@ -147,7 +151,7 @@ class Config {
registerComponent(options: {
name: string;
- dependencies: string[] | string;
+ dependencies?: string[] | string;
component: React.ComponentType;
}) {
const { name, component, dependencies = '' } = options;
@@ -181,6 +185,214 @@ class Config {
}
}
}
+
+ getSlot(name: string, args: GetSlotArgs): GetSlotReturn {
+ if (!this._data.slots[name]) {
+ return;
+ }
+ const { slots, data } = this._data.slots[name];
+ const slotComponents = [];
+ // For all enabled slots
+ for (const slotName of slots) {
+ // For all registered components for that slot, inversed, since the last one registered wins
+ // TODO: Cover ZCA use case, where if more predicates, more specificity wins if all true.
+ // Let's keep it simple here and stick to the registered order.
+ let noPredicateComponent: SlotComponent | undefined;
+ const reversedSlotComponents = data[slotName].slice().reverse(); // Immutable reversed copy
+ for (const slotComponent of reversedSlotComponents) {
+ let isPredicateTrueFound: boolean = false;
+ if (slotComponent.predicates) {
+ isPredicateTrueFound = slotComponent.predicates.every((predicate) =>
+ predicate(args),
+ );
+ } else {
+ // We mark the one with no predicates
+ noPredicateComponent = slotComponent;
+ }
+
+ // If all the predicates are truthy
+ if (isPredicateTrueFound) {
+ slotComponents.push({
+ component: slotComponent.component,
+ name: slotName,
+ });
+ // We "reset" the marker, we already found a candidate
+ noPredicateComponent = undefined;
+ break;
+ }
+ }
+
+ if (noPredicateComponent) {
+ slotComponents.push({
+ component: noPredicateComponent.component,
+ name: slotName,
+ });
+ }
+ }
+
+ return slotComponents;
+ }
+
+ registerSlotComponent(options: {
+ slot: string;
+ name: string;
+ predicates?: SlotPredicate[];
+ component: SlotComponent['component'];
+ }): void {
+ const { name, component, predicates, slot } = options;
+
+ if (!component) {
+ throw new Error('No component provided');
+ }
+ if (!predicates) {
+ // Test if there's already one registered, we only support one
+ const hasRegisteredNoPredicatesComponent = this._data.slots?.[
+ slot
+ ]?.data?.[name]?.find(({ predicates }) => !predicates);
+ if (hasRegisteredNoPredicatesComponent) {
+ throw new Error(
+ `There is already registered a component ${name} for the slot ${slot}. You can only register one slot component with no predicates per slot.`,
+ );
+ }
+ }
+
+ let currentSlot = this._data.slots[slot];
+ if (!currentSlot) {
+ this._data.slots[slot] = {
+ slots: [],
+ data: {},
+ };
+ currentSlot = this._data.slots[slot];
+ }
+ if (!currentSlot.data[name]) {
+ currentSlot.data[name] = [];
+ }
+
+ const currentSlotComponents = currentSlot.data[name];
+ if (!currentSlot.slots.includes(name)) {
+ currentSlot.slots.push(name);
+ }
+ const slotComponentData = {
+ component,
+ predicates,
+ };
+
+ // Try to set a displayName (useful for React dev tools) for the registered component
+ // Only if it's a function and it's not set previously
+ try {
+ const displayName = slotComponentData.component.displayName;
+
+ if (!displayName && typeof slotComponentData?.component === 'function') {
+ slotComponentData.component.displayName = name;
+ }
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.warn(
+ `Not setting the slot component displayName because ${error}`,
+ );
+ }
+
+ currentSlotComponents.push(slotComponentData);
+ }
+
+ getSlotComponent(slot: string, name: string) {
+ const currentSlot = this._data.slots[slot];
+ if (!slot || !currentSlot) {
+ throw new Error(`No slot ${slot} found`);
+ }
+ const currentSlotComponents = currentSlot.data[name];
+ if (!currentSlotComponents) {
+ throw new Error(`No slot component ${name} in slot ${slot} found`);
+ }
+ return currentSlotComponents;
+ }
+
+ getSlotComponents(slot: string) {
+ const currentSlot = this._data.slots[slot];
+ if (!slot || !currentSlot) {
+ throw new Error(`No slot ${slot} found`);
+ }
+ return currentSlot.slots;
+ }
+
+ reorderSlotComponent({
+ slot,
+ name,
+ position,
+ action,
+ target,
+ }: {
+ slot: string;
+ name: string;
+ position?: number;
+ action?: 'after' | 'before' | 'first' | 'last';
+ target?: string;
+ }) {
+ if (!position && !action) {
+ throw new Error(`At least a position or action is required as argument`);
+ }
+ if (position && action) {
+ throw new Error(
+ `You should provide only one of position or action as arguments`,
+ );
+ }
+ if ((action == 'after' || action == 'before') && !target) {
+ throw new Error(
+ `No action target set. You should provide the name of a slot component as target when action is 'after' or 'before'.`,
+ );
+ }
+
+ const currentSlot = this._data.slots[slot];
+ if (!slot || !currentSlot) {
+ throw new Error(`No slot ${slot} found`);
+ }
+ const origin = currentSlot.slots.indexOf(name);
+ const result = Array.from(currentSlot.slots);
+ const [removed] = result.splice(origin, 1);
+
+ if (action) {
+ let targetIdx = 0;
+ if (target) {
+ targetIdx = currentSlot.slots.indexOf(target);
+ }
+ switch (action) {
+ case 'after':
+ result.splice(targetIdx, 0, removed);
+ break;
+ case 'before':
+ result.splice(targetIdx - 1, 0, removed);
+ break;
+ case 'last':
+ result.push(removed);
+ break;
+ case 'first':
+ result.unshift(removed);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (position) {
+ result.splice(position, 0, removed);
+ }
+
+ currentSlot.slots = result;
+ }
+
+ unRegisterSlotComponent(slot: string, name: string, position: number) {
+ const currentSlot = this._data.slots[slot];
+ if (!slot || !currentSlot) {
+ throw new Error(`No slot ${slot} found`);
+ }
+ const currentSlotComponents = currentSlot.data[name];
+ if (!currentSlotComponents) {
+ throw new Error(`No slot component ${name} in slot ${slot} found`);
+ }
+ const result = currentSlotComponents.slice();
+ currentSlot.data[name] = result.splice(position, 1);
+ }
}
const instance = new Config();
diff --git a/packages/registry/src/registry.test.jsx b/packages/registry/src/registry.test.jsx
deleted file mode 100644
index 861d26701f..0000000000
--- a/packages/registry/src/registry.test.jsx
+++ /dev/null
@@ -1,112 +0,0 @@
-import config from './index';
-import { describe, expect, it } from 'vitest';
-
-config.set('components', {
- Toolbar: { component: 'this is the Toolbar component' },
- 'Toolbar.Types': { component: 'this is the Types component' },
- 'Teaser|News Item': { component: 'This is the News Item Teaser component' },
-});
-
-describe('registry', () => {
- it('get components', () => {
- expect(config.getComponent('Toolbar').component).toEqual(
- 'this is the Toolbar component',
- );
- });
- it('get components with context', () => {
- expect(
- config.getComponent({ name: 'Teaser', dependencies: 'News Item' })
- .component,
- ).toEqual('This is the News Item Teaser component');
- });
- it('get components with dots (as an object)', () => {
- expect(config.getComponent({ name: 'Toolbar.Types' }).component).toEqual(
- 'this is the Types component',
- );
- });
- it('get components with | and spaces (as a string)', () => {
- expect(config.getComponent('Teaser|News Item').component).toEqual(
- 'This is the News Item Teaser component',
- );
- });
- it('resolves unexistent component (as a string)', () => {
- expect(config.getComponent('Toolbar.Doh').component).toEqual(undefined);
- expect(config.getComponent('Toolbar.Doh')).toEqual({});
- });
- it('registers and gets a component by name (as string)', () => {
- config.registerComponent({
- name: 'Toolbar.Bar',
- component: 'this is a Bar component',
- });
- expect(config.getComponent('Toolbar.Bar').component).toEqual(
- 'this is a Bar component',
- );
- });
- it('registers and gets a component by name (as an object)', () => {
- config.registerComponent({
- name: 'Toolbar.Bar',
- component: 'this is a Bar component',
- });
- expect(config.getComponent({ name: 'Toolbar.Bar' }).component).toEqual(
- 'this is a Bar component',
- );
- });
- it('registers and gets a component by name (as an object) - check displayName', () => {
- config.registerComponent({
- name: 'Toolbar.Bar',
- component: () => Hello
,
- });
- expect(config.getComponent('Toolbar.Bar').component.displayName).toEqual(
- 'Toolbar.Bar',
- );
- });
- it('registers and gets a component by name (as an object) - check displayName if it has already one, it does not overwrite it', () => {
- const TestComponent = () => Hello
;
- TestComponent.displayName = 'DisplayNameAlreadySet';
- config.registerComponent({
- name: 'Toolbar.Bar',
- component: TestComponent,
- });
-
- expect(config.getComponent('Toolbar.Bar').component.displayName).toEqual(
- 'DisplayNameAlreadySet',
- );
- });
- it('registers and gets a component by name (as an object) - check displayName - do not break if it is a normal function', () => {
- function myFunction() {
- return 'true';
- }
-
- config.registerComponent({
- name: 'Toolbar.Bar',
- component: myFunction,
- });
- expect(config.getComponent('Toolbar.Bar').component.displayName).toEqual(
- 'Toolbar.Bar',
- );
- });
- it('registers a component by name with dependencies', () => {
- config.registerComponent({
- name: 'Toolbar.Bar',
- component: 'this is a Bar component',
- dependencies: 'News Item',
- });
- expect(
- config.getComponent({ name: 'Toolbar.Bar', dependencies: 'News Item' })
- .component,
- ).toEqual('this is a Bar component');
- });
- it('registers a component by name with dependencies array', () => {
- config.registerComponent({
- name: 'Toolbar.Bar',
- component: 'this is a Bar component',
- dependencies: ['News Item', 'StringFieldWidget'],
- });
- expect(
- config.getComponent({
- name: 'Toolbar.Bar',
- dependencies: ['News Item', 'StringFieldWidget'],
- }).component,
- ).toEqual('this is a Bar component');
- });
-});
diff --git a/packages/registry/src/registry.test.tsx b/packages/registry/src/registry.test.tsx
new file mode 100644
index 0000000000..f5879bebe6
--- /dev/null
+++ b/packages/registry/src/registry.test.tsx
@@ -0,0 +1,736 @@
+import config from './index';
+import { describe, expect, it, afterEach } from 'vitest';
+
+config.set('components', {
+ Toolbar: { component: 'this is the Toolbar component' },
+ 'Toolbar.Types': { component: 'this is the Types component' },
+ 'Teaser|News Item': { component: 'This is the News Item Teaser component' },
+});
+
+config.set('slots', {});
+
+describe('Component registry', () => {
+ it('get components', () => {
+ expect(config.getComponent('Toolbar').component).toEqual(
+ 'this is the Toolbar component',
+ );
+ });
+ it('get components with context', () => {
+ expect(
+ config.getComponent({ name: 'Teaser', dependencies: 'News Item' })
+ .component,
+ ).toEqual('This is the News Item Teaser component');
+ });
+ it('get components with dots (as an object)', () => {
+ expect(config.getComponent({ name: 'Toolbar.Types' }).component).toEqual(
+ 'this is the Types component',
+ );
+ });
+ it('get components with | and spaces (as a string)', () => {
+ expect(config.getComponent('Teaser|News Item').component).toEqual(
+ 'This is the News Item Teaser component',
+ );
+ });
+ it('resolves unexistent component (as a string)', () => {
+ expect(config.getComponent('Toolbar.Doh').component).toEqual(undefined);
+ expect(config.getComponent('Toolbar.Doh')).toEqual({});
+ });
+ it('registers and gets a component by name (as string)', () => {
+ config.registerComponent({
+ name: 'Toolbar.Bar',
+ component: 'this is a Bar component',
+ });
+ expect(config.getComponent('Toolbar.Bar').component).toEqual(
+ 'this is a Bar component',
+ );
+ });
+ it('registers and gets a component by name (as an object)', () => {
+ config.registerComponent({
+ name: 'Toolbar.Bar',
+ component: 'this is a Bar component',
+ });
+ expect(config.getComponent({ name: 'Toolbar.Bar' }).component).toEqual(
+ 'this is a Bar component',
+ );
+ });
+ it('registers and gets a component by name (as an object) - check displayName', () => {
+ config.registerComponent({
+ name: 'Toolbar.Bar',
+ component: () => Hello
,
+ });
+ expect(config.getComponent('Toolbar.Bar').component.displayName).toEqual(
+ 'Toolbar.Bar',
+ );
+ });
+ it('registers and gets a component by name (as an object) - check displayName if it has already one, it does not overwrite it', () => {
+ const TestComponent = () => Hello
;
+ TestComponent.displayName = 'DisplayNameAlreadySet';
+ config.registerComponent({
+ name: 'Toolbar.Bar',
+ component: TestComponent,
+ });
+
+ expect(config.getComponent('Toolbar.Bar').component.displayName).toEqual(
+ 'DisplayNameAlreadySet',
+ );
+ });
+ it('registers and gets a component by name (as an object) - check displayName - do not break if it is a normal function', () => {
+ function myFunction() {
+ return 'true';
+ }
+
+ config.registerComponent({
+ name: 'Toolbar.Bar',
+ component: myFunction,
+ });
+ expect(config.getComponent('Toolbar.Bar').component.displayName).toEqual(
+ 'Toolbar.Bar',
+ );
+ });
+ it('registers a component by name with dependencies', () => {
+ config.registerComponent({
+ name: 'Toolbar.Bar',
+ component: 'this is a Bar component',
+ dependencies: 'News Item',
+ });
+ expect(
+ config.getComponent({ name: 'Toolbar.Bar', dependencies: 'News Item' })
+ .component,
+ ).toEqual('this is a Bar component');
+ });
+ it('registers a component by name with dependencies array', () => {
+ config.registerComponent({
+ name: 'Toolbar.Bar',
+ component: 'this is a Bar component',
+ dependencies: ['News Item', 'StringFieldWidget'],
+ });
+ expect(
+ config.getComponent({
+ name: 'Toolbar.Bar',
+ dependencies: ['News Item', 'StringFieldWidget'],
+ }).component,
+ ).toEqual('this is a Bar component');
+ });
+});
+
+describe('Slots registry', () => {
+ afterEach(() => {
+ config.set('slots', {});
+ });
+
+ // type Predicate = (predicateValues: unknown) = (predicateValues, args) => boolean
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const RouteConditionTrue = (route: string) => () => true;
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const RouteConditionFalse = (route: string) => () => false;
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const ContentTypeConditionTrue = (contentType: string[]) => () => true;
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const ContentTypeConditionFalse = (contentType: string[]) => () => false;
+
+ it('registers a single slot component with no predicate', () => {
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: 'this is a toolbar component with no predicate',
+ });
+
+ expect(config.getSlot('toolbar', {})![0].component).toEqual(
+ 'this is a toolbar component with no predicate',
+ );
+ });
+
+ it('registers two slot components with predicates - registered components order is respected', () => {
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component:
+ 'this is a toolbar component with only a truth-ish route condition',
+ predicates: [RouteConditionTrue('/de')],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: 'this is a Bar component with a false predicate and one true',
+ predicates: [
+ RouteConditionFalse('/folder/path'),
+ ContentTypeConditionTrue(['News Item']),
+ ],
+ });
+
+ expect(config.getSlot('toolbar', {})![0].component).toEqual(
+ 'this is a toolbar component with only a truth-ish route condition',
+ );
+ });
+
+ it('registers two slot components with predicates - All registered components predicates are truthy, the last one registered wins', () => {
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component:
+ 'this is a toolbar component with only a truth-ish route condition',
+ predicates: [RouteConditionTrue('/de')],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: 'this is a toolbar component with two truth-ish predicates',
+ predicates: [
+ RouteConditionTrue('/folder/path'),
+ ContentTypeConditionTrue(['News Item']),
+ ],
+ });
+
+ expect(config.getSlot('toolbar', {})![0].component).toEqual(
+ 'this is a toolbar component with two truth-ish predicates',
+ );
+ });
+
+ it('registers two slot components with predicates - No registered component have a truthy predicate', () => {
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: 'this is a toolbar component with a false predicate',
+ predicates: [RouteConditionFalse('/de')],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: 'this is a toolbar component with two false predicate',
+ predicates: [
+ RouteConditionFalse('/folder/path'),
+ ContentTypeConditionFalse(['News Item']),
+ ],
+ });
+
+ expect(config.getSlot('toolbar', {})).toEqual([]);
+ });
+
+ it('registers two slot components one without predicates - registered component with predicates are truthy, the last one registered wins', () => {
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: 'this is a toolbar component with no predicate',
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: 'this is a toolbar component with two truth-ish predicates',
+ predicates: [
+ RouteConditionTrue('/folder/path'),
+ ContentTypeConditionTrue(['News Item']),
+ ],
+ });
+
+ expect(config.getSlot('toolbar', {})![0].component).toEqual(
+ 'this is a toolbar component with two truth-ish predicates',
+ );
+ });
+
+ it('registers two slot components one without predicates - registered components predicates are falsy, the one with no predicates wins', () => {
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: 'this is a toolbar component with no predicate',
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: 'this is a toolbar component with two truth-ish predicates',
+ predicates: [
+ RouteConditionFalse('/folder/path'),
+ ContentTypeConditionTrue(['News Item']),
+ ],
+ });
+
+ expect(config.getSlot('toolbar', {})![0].component).toEqual(
+ 'this is a toolbar component with no predicate',
+ );
+ });
+
+ it('registers two slot components one without predicates - registered components predicates are truthy, the one with predicates wins', () => {
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: 'this is a toolbar component with two truth-ish predicates',
+ predicates: [
+ RouteConditionTrue('/folder/path'),
+ ContentTypeConditionTrue(['News Item']),
+ ],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: 'this is a toolbar component with no predicate',
+ });
+
+ expect(config.getSlot('toolbar', {})![0].component).toEqual(
+ 'this is a toolbar component with two truth-ish predicates',
+ );
+ });
+
+ it('registers 2 + 2 slot components with predicates - No registered component have a truthy predicate', () => {
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: 'this is a toolbar component with a false predicate',
+ predicates: [RouteConditionFalse('/de')],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: 'this is a toolbar component with two false predicate',
+ predicates: [
+ RouteConditionFalse('/folder/path'),
+ ContentTypeConditionFalse(['News Item']),
+ ],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'edit',
+ component: 'this is a toolbar component with a false predicate',
+ predicates: [RouteConditionFalse('/de')],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'edit',
+ component: 'this is a toolbar component with two false predicate',
+ predicates: [
+ RouteConditionFalse('/folder/path'),
+ ContentTypeConditionFalse(['News Item']),
+ ],
+ });
+ expect(config.getSlot('toolbar', {})).toEqual([]);
+ });
+
+ it('registers 2 + 2 slot components with predicates - One truthy predicate per set', () => {
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: 'this is a toolbar save component with a true predicate',
+ predicates: [RouteConditionTrue('/de')],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: 'this is a toolbar component with two false predicate',
+ predicates: [
+ RouteConditionFalse('/folder/path'),
+ ContentTypeConditionFalse(['News Item']),
+ ],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'edit',
+ component: 'this is a toolbar component with a false predicate',
+ predicates: [RouteConditionFalse('/de')],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'edit',
+ component: 'this is a toolbar edit component with true predicate',
+ predicates: [
+ RouteConditionTrue('/folder/path'),
+ ContentTypeConditionTrue(['News Item']),
+ ],
+ });
+ expect(config.getSlot('toolbar', {})![0].component).toEqual(
+ 'this is a toolbar save component with a true predicate',
+ );
+ expect(config.getSlot('toolbar', {})![1].component).toEqual(
+ 'this is a toolbar edit component with true predicate',
+ );
+ });
+
+ it('getSlotComponents - registers 2 + 2 slot components with predicates', () => {
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: 'this is a toolbar save component with a true predicate',
+ predicates: [RouteConditionTrue('/de')],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: 'this is a toolbar component with two false predicate',
+ predicates: [
+ RouteConditionFalse('/folder/path'),
+ ContentTypeConditionFalse(['News Item']),
+ ],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'edit',
+ component: 'this is a toolbar component with a false predicate',
+ predicates: [RouteConditionFalse('/de')],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'edit',
+ component: 'this is a toolbar edit component with true predicate',
+ predicates: [
+ RouteConditionTrue('/folder/path'),
+ ContentTypeConditionTrue(['News Item']),
+ ],
+ });
+ expect(config.getSlotComponents('toolbar').length).toEqual(2);
+ expect(config.getSlotComponents('toolbar')).toEqual(['save', 'edit']);
+ });
+
+ it('getSlotComponent - registers 2 + 2 slot components with predicates', () => {
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: 'this is a toolbar save component with a true predicate',
+ predicates: [RouteConditionTrue('/de')],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: 'this is a toolbar component with two false predicate',
+ predicates: [
+ RouteConditionFalse('/folder/path'),
+ ContentTypeConditionFalse(['News Item']),
+ ],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'edit',
+ component: 'this is a toolbar component with a false predicate',
+ predicates: [RouteConditionFalse('/de')],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'edit',
+ component: 'this is a toolbar edit component with true predicate',
+ predicates: [
+ RouteConditionTrue('/folder/path'),
+ ContentTypeConditionTrue(['News Item']),
+ ],
+ });
+ expect(config.getSlotComponent('toolbar', 'save').length).toEqual(2);
+ expect(config.getSlotComponent('toolbar', 'save')[0].component).toEqual(
+ 'this is a toolbar save component with a true predicate',
+ );
+ });
+
+ it('reorderSlotComponent - position', () => {
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: 'this is a toolbar save component with a true predicate',
+ predicates: [RouteConditionTrue('/de')],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: 'this is a toolbar component with two false predicate',
+ predicates: [
+ RouteConditionFalse('/folder/path'),
+ ContentTypeConditionFalse(['News Item']),
+ ],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'edit',
+ component: 'this is a toolbar component with a false predicate',
+ predicates: [RouteConditionFalse('/de')],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'edit',
+ component: 'this is a toolbar edit component with true predicate',
+ predicates: [
+ RouteConditionTrue('/folder/path'),
+ ContentTypeConditionTrue(['News Item']),
+ ],
+ });
+ expect(config.getSlotComponent('toolbar', 'save').length).toEqual(2);
+ expect(config.getSlotComponent('toolbar', 'save')[0].component).toEqual(
+ 'this is a toolbar save component with a true predicate',
+ );
+ config.reorderSlotComponent({ slot: 'toolbar', name: 'save', position: 1 });
+ expect(config.getSlotComponents('toolbar')).toEqual(['edit', 'save']);
+ });
+
+ it('reorderSlotComponent - after', () => {
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: 'this is a toolbar save component with a true predicate',
+ predicates: [RouteConditionTrue('/de')],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'edit',
+ component: 'this is a toolbar component with a false predicate',
+ predicates: [RouteConditionFalse('/de')],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'cancel',
+ component: 'this is a toolbar edit component with true predicate',
+ predicates: [
+ RouteConditionTrue('/folder/path'),
+ ContentTypeConditionTrue(['News Item']),
+ ],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'bold',
+ component: 'this is a toolbar edit component with true predicate',
+ predicates: [
+ RouteConditionTrue('/folder/path'),
+ ContentTypeConditionTrue(['News Item']),
+ ],
+ });
+
+ expect(config.getSlotComponents('toolbar')).toEqual([
+ 'save',
+ 'edit',
+ 'cancel',
+ 'bold',
+ ]);
+ config.reorderSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ action: 'after',
+ target: 'cancel',
+ });
+ expect(config.getSlotComponents('toolbar')).toEqual([
+ 'edit',
+ 'cancel',
+ 'save',
+ 'bold',
+ ]);
+ });
+
+ it('reorderSlotComponent - before', () => {
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: 'this is a toolbar save component with a true predicate',
+ predicates: [RouteConditionTrue('/de')],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'edit',
+ component: 'this is a toolbar component with a false predicate',
+ predicates: [RouteConditionFalse('/de')],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'cancel',
+ component: 'this is a toolbar edit component with true predicate',
+ predicates: [
+ RouteConditionTrue('/folder/path'),
+ ContentTypeConditionTrue(['News Item']),
+ ],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'bold',
+ component: 'this is a toolbar edit component with true predicate',
+ predicates: [
+ RouteConditionTrue('/folder/path'),
+ ContentTypeConditionTrue(['News Item']),
+ ],
+ });
+
+ expect(config.getSlotComponents('toolbar')).toEqual([
+ 'save',
+ 'edit',
+ 'cancel',
+ 'bold',
+ ]);
+ config.reorderSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ action: 'before',
+ target: 'cancel',
+ });
+ expect(config.getSlotComponents('toolbar')).toEqual([
+ 'edit',
+ 'save',
+ 'cancel',
+ 'bold',
+ ]);
+ });
+
+ it('reorderSlotComponent - last', () => {
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: 'this is a toolbar save component with a true predicate',
+ predicates: [RouteConditionTrue('/de')],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'edit',
+ component: 'this is a toolbar component with a false predicate',
+ predicates: [RouteConditionFalse('/de')],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'cancel',
+ component: 'this is a toolbar edit component with true predicate',
+ predicates: [
+ RouteConditionTrue('/folder/path'),
+ ContentTypeConditionTrue(['News Item']),
+ ],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'bold',
+ component: 'this is a toolbar edit component with true predicate',
+ predicates: [
+ RouteConditionTrue('/folder/path'),
+ ContentTypeConditionTrue(['News Item']),
+ ],
+ });
+
+ expect(config.getSlotComponents('toolbar')).toEqual([
+ 'save',
+ 'edit',
+ 'cancel',
+ 'bold',
+ ]);
+ config.reorderSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ action: 'last',
+ });
+ expect(config.getSlotComponents('toolbar')).toEqual([
+ 'edit',
+ 'cancel',
+ 'bold',
+ 'save',
+ ]);
+ });
+
+ it('reorderSlotComponent - first', () => {
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: 'this is a toolbar save component with a true predicate',
+ predicates: [RouteConditionTrue('/de')],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'edit',
+ component: 'this is a toolbar component with a false predicate',
+ predicates: [RouteConditionFalse('/de')],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'cancel',
+ component: 'this is a toolbar edit component with true predicate',
+ predicates: [
+ RouteConditionTrue('/folder/path'),
+ ContentTypeConditionTrue(['News Item']),
+ ],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'bold',
+ component: 'this is a toolbar edit component with true predicate',
+ predicates: [
+ RouteConditionTrue('/folder/path'),
+ ContentTypeConditionTrue(['News Item']),
+ ],
+ });
+
+ expect(config.getSlotComponents('toolbar')).toEqual([
+ 'save',
+ 'edit',
+ 'cancel',
+ 'bold',
+ ]);
+ config.reorderSlotComponent({
+ slot: 'toolbar',
+ name: 'bold',
+ action: 'first',
+ });
+ expect(config.getSlotComponents('toolbar')).toEqual([
+ 'bold',
+ 'save',
+ 'edit',
+ 'cancel',
+ ]);
+ });
+
+ it('unRegisterSlotComponent - registers 2 + 2 slot components with predicates', () => {
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: 'this is a toolbar save component with a true predicate',
+ predicates: [RouteConditionTrue('/de')],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: 'this is a toolbar component with two false predicate',
+ predicates: [
+ RouteConditionFalse('/folder/path'),
+ ContentTypeConditionFalse(['News Item']),
+ ],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'edit',
+ component: 'this is a toolbar component with a false predicate',
+ predicates: [RouteConditionFalse('/de')],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'edit',
+ component: 'this is a toolbar edit component with true predicate',
+ predicates: [
+ RouteConditionTrue('/folder/path'),
+ ContentTypeConditionTrue(['News Item']),
+ ],
+ });
+ expect(config.getSlotComponent('toolbar', 'save').length).toEqual(2);
+ expect(config.getSlotComponent('toolbar', 'save')[0].component).toEqual(
+ 'this is a toolbar save component with a true predicate',
+ );
+ config.unRegisterSlotComponent('toolbar', 'save', 1);
+ expect(config.getSlotComponent('toolbar', 'save').length).toEqual(1);
+ });
+});
diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md
index 3e0ae44a23..e731d3c4bf 100644
--- a/packages/scripts/CHANGELOG.md
+++ b/packages/scripts/CHANGELOG.md
@@ -8,6 +8,16 @@
+## 3.4.0 (2024-03-02)
+
+### Feature
+
+- New `lockhook.js` script for replacing `yarnhook` @sneridagh [#5815](https://github.com/plone/volto/issues/5815)
+
+### Internal
+
+- Update dependencies @sneridagh [#5815](https://github.com/plone/volto/issues/5815)
+
## 3.3.2 (2024-01-26)
### Bugfix
diff --git a/packages/scripts/check_deployment.js b/packages/scripts/check_deployment.js
new file mode 100644
index 0000000000..feb3ea0092
--- /dev/null
+++ b/packages/scripts/check_deployment.js
@@ -0,0 +1,41 @@
+import http from 'http';
+import waitOn from 'wait-on';
+
+const url = new URL('http://localhost:3000'); // replace with your service URL
+const searchString = 'Welcome to Plone'; // replace with the string you want to search for
+
+// Wait for the service to be available
+waitOn({ resources: [url.href] })
+ .then(() => {
+ console.log('Service is available');
+
+ // Fetch the HTML
+ http
+ .get(url.href, (res) => {
+ let data = '';
+
+ // A chunk of data has been received.
+ res.on('data', (chunk) => {
+ data += chunk;
+ });
+
+ // The whole response has been received.
+ res.on('end', () => {
+ // Check if the HTML contains the specific string
+ if (data.includes(searchString)) {
+ console.log(`Found "${searchString}" in the HTML`);
+ } else {
+ console.error(`Did not find "${searchString}" in the HTML`);
+ process.exit(1);
+ }
+ });
+ })
+ .on('error', (err) => {
+ console.error('Error: ' + err.message);
+ process.exit(1);
+ });
+ })
+ .catch((err) => {
+ console.error('An error occurred:', err);
+ process.exit(1);
+ });
diff --git a/packages/scripts/lockhook.js b/packages/scripts/lockhook.js
new file mode 100644
index 0000000000..3574ad8014
--- /dev/null
+++ b/packages/scripts/lockhook.js
@@ -0,0 +1,138 @@
+#!/usr/bin/env node
+
+// @flow
+import findParentDir from 'find-parent-dir';
+import execa from 'execa';
+import { join } from 'path';
+import fs from 'fs';
+
+// environment variables
+const {
+ LOCKHOOK_BYPASS = false,
+ LOCKHOOK_DEBUG = false,
+ LOCKHOOK_DRYRUN = false,
+} = process.env;
+
+// supported package managers and lockfile names
+const lockfileSpecs = [
+ {
+ checkfile: 'bun.lockb',
+ lockfile: 'bun.lockb',
+ command: 'bun',
+ version: '1',
+ arguments: ['install', '--frozen-lockfile '],
+ },
+ {
+ checkfile: '.yarnrc.yml',
+ lockfile: 'yarn.lock',
+ command: 'yarn',
+ version: '2',
+ arguments: ['install', '--immutable'],
+ },
+ {
+ checkfile: 'yarn.lock',
+ lockfile: 'yarn.lock',
+ command: 'yarn',
+ version: '1',
+ arguments: ['install', '--prefer-offline', '--pure-lockfile'],
+ },
+ {
+ checkfile: 'package-lock.json',
+ lockfile: 'package-lock.json',
+ command: 'npm',
+ version: '>=5',
+ arguments: ['install', '--prefer-offline', '--no-audit', '--no-save'],
+ },
+ {
+ checkfile: 'npm-shrinkwrap.json',
+ lockfile: 'npm-shrinkwrap.json',
+ command: 'npm',
+ version: '<5',
+ arguments: ['install', '--prefer-offline', '--no-audit', '--no-save'],
+ },
+ {
+ checkfile: 'pnpm-lock.yaml',
+ lockfile: 'pnpm-lock.yaml',
+ command: 'pnpm',
+ version: '>=3',
+ arguments: [
+ 'install',
+ '--prefer-offline',
+ '--frozen-lockfile',
+ '--no-verify-store-integrity',
+ ],
+ },
+ {
+ checkfile: 'shrinkwrap.yaml',
+ lockfile: 'shrinkwrap.yaml',
+ command: 'pnpm',
+ version: '<3',
+ arguments: ['install', '--prefer-offline', '--prefer-frozen-shrinkwrap'],
+ },
+];
+
+function getLockfileSpec(currentDir) {
+ for (let lockfileSpec of lockfileSpecs) {
+ const checkfilePath = join(currentDir, lockfileSpec.checkfile);
+ if (fs.existsSync(checkfilePath)) {
+ return lockfileSpec;
+ }
+ }
+
+ return null;
+}
+
+if (!LOCKHOOK_BYPASS) {
+ // find directories
+ const currentDir = process.cwd();
+ const gitDir = findParentDir.sync(currentDir, '.git');
+
+ // check for lockfiles
+ const lockfileSpec = getLockfileSpec(currentDir);
+
+ if (LOCKHOOK_DEBUG) {
+ console.log('currentDir:', currentDir);
+ console.log('gitDir:', gitDir);
+ console.log('lockfile:', lockfileSpec);
+ }
+
+ if (lockfileSpec !== null) {
+ // get the command, arguments and lockfile path
+ const { lockfile, command, arguments: commandargs } = lockfileSpec;
+ const lockfilePath = join(currentDir, lockfile);
+
+ // run a git diff on the lockfile
+ const { stdout: output } = execa.sync(
+ 'git',
+ ['diff', 'HEAD@{1}..HEAD@{0}', '--', lockfilePath],
+ { cwd: gitDir },
+ );
+
+ if (LOCKHOOK_DEBUG) {
+ console.log(output);
+ }
+
+ // if diff exists, update dependencies
+ if (output.length > 0) {
+ if (LOCKHOOK_DRYRUN) {
+ console.log(
+ `Changes to lockfile found, you should run \`${command} install\` if you want to have up-to-date dependencies.`,
+ );
+ } else {
+ console.log(
+ `Changes to lockfile found, running \`${command} install\``,
+ );
+ try {
+ execa.sync(command, commandargs, { stdio: 'inherit' });
+ } catch (err) {
+ console.warn(`Running ${command} ${commandargs.join(' ')} failed`);
+ }
+ }
+ }
+ } else {
+ const lockfiles = lockfileSpecs.map((spec) => spec.lockfile).join(', ');
+ console.warn(
+ `I can't find a lockfile. Currently supported lockfiles are: ${lockfiles}.`,
+ );
+ }
+}
diff --git a/packages/scripts/news/5824.feature b/packages/scripts/news/5824.feature
new file mode 100644
index 0000000000..10d0e18d00
--- /dev/null
+++ b/packages/scripts/news/5824.feature
@@ -0,0 +1 @@
+Add `check_deployments.js` script - Test simple deployments setups when Cypress is too much @sneridagh
diff --git a/packages/scripts/package.json b/packages/scripts/package.json
index 9fd6c869c8..3cef14df40 100644
--- a/packages/scripts/package.json
+++ b/packages/scripts/package.json
@@ -9,7 +9,7 @@
}
],
"license": "MIT",
- "version": "3.3.2",
+ "version": "3.4.0",
"repository": {
"type": "git",
"url": "git@github.com:plone/volto.git"
@@ -48,12 +48,15 @@
"chalk": "4",
"commander": "8.2.0",
"comment-json": "^4.2.3",
+ "execa": "0.6.3",
+ "find-parent-dir": "^0.3.1",
"fs-extra": "10.1.0",
"git-url-parse": "^13.1.0",
"glob": "7.1.6",
"lodash": "4.17.21",
"mrs-developer": "^2.1.1",
- "pofile": "1.0.10"
+ "pofile": "1.0.10",
+ "wait-on": "^7.2.0"
},
"devDependencies": {
"release-it": "^16.1.3"
diff --git a/packages/scripts/prepublish.js b/packages/scripts/prepublish.js
index c467b92390..f22f8b99dd 100644
--- a/packages/scripts/prepublish.js
+++ b/packages/scripts/prepublish.js
@@ -12,7 +12,7 @@ class PrePublishReleaseItPlugin extends Plugin {
type: 'confirm',
message: (context) =>
`Are you sure you want to publish ${context.npm.name}${
- context.isPreRelease ? `@${context.preReleaseId}` : 'latest'
+ context.isPreRelease ? `@${context.preReleaseId}` : '@latest'
} to npm?`,
default: true,
},
diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md
index 2ad0136b0d..d6edab582b 100644
--- a/packages/types/CHANGELOG.md
+++ b/packages/types/CHANGELOG.md
@@ -8,6 +8,18 @@
+## 1.0.0-alpha.5 (2024-03-01)
+
+### Feature
+
+- Upgrade Volto core to use React 18.2.0 @sneridagh [#3221](https://github.com/plone/volto/issues/3221)
+
+## 1.0.0-alpha.4 (2024-03-01)
+
+### Feature
+
+- Support for slots @sneridagh [#5775](https://github.com/plone/volto/issues/5775)
+
## 1.0.0-alpha.3 (2024-02-02)
### Bugfix
diff --git a/packages/types/package.json b/packages/types/package.json
index c8ff7d96d5..bc99040d69 100644
--- a/packages/types/package.json
+++ b/packages/types/package.json
@@ -9,7 +9,7 @@
],
"funding": "https://github.com/sponsors/plone",
"license": "MIT",
- "version": "1.0.0-alpha.3",
+ "version": "1.0.0-alpha.5",
"repository": {
"type": "git",
"url": "https://github.com/plone/volto.git"
@@ -50,10 +50,12 @@
"devDependencies": {
"@parcel/packager-ts": "2.10.2",
"@parcel/transformer-typescript-types": "2.10.2",
- "@types/react": "^17.0.52",
- "@types/react-dom": "^17",
+ "@types/react": "^18",
+ "@types/react-dom": "^18",
"history": "^5.3.0",
"parcel": "2.10.2",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
"react-intl": "3.8.0",
"release-it": "16.2.1",
"tsconfig": "workspace:*",
diff --git a/packages/types/src/config/Slots.d.ts b/packages/types/src/config/Slots.d.ts
new file mode 100644
index 0000000000..87952c3f7d
--- /dev/null
+++ b/packages/types/src/config/Slots.d.ts
@@ -0,0 +1,25 @@
+import type { Content } from '../content';
+
+export type SlotPredicate = (args: any) => boolean;
+
+export type GetSlotArgs = {
+ content: Content;
+ pathname: string;
+ navRoot?: Content;
+};
+
+export type GetSlotReturn =
+ | { component: SlotComponent['component']; name: string }[]
+ | undefined;
+
+export type SlotComponent = {
+ component: React.ComponentType;
+ predicates?: SlotPredicate[];
+};
+
+export type SlotManager = {
+ slots: string[];
+ data: Record;
+};
+
+export type SlotsConfig = Record;
diff --git a/packages/types/src/config/index.d.ts b/packages/types/src/config/index.d.ts
index 02df192e63..a62e18c96d 100644
--- a/packages/types/src/config/index.d.ts
+++ b/packages/types/src/config/index.d.ts
@@ -2,6 +2,7 @@ import type { SettingsConfig } from './Settings';
import type { BlocksConfig } from './Blocks';
import type { ViewsConfig } from './Views';
import type { WidgetsConfig } from './Widgets';
+import type { SlotsConfig } from './Slots';
export type AddonReducersConfig = Record;
@@ -11,8 +12,6 @@ export type AddonRoutesConfig = {
component: React.ComponentType;
}[];
-export type SlotsConfig = Record;
-
export type ComponentsConfig = Record<
string,
{ component: React.ComponentType }
@@ -34,3 +33,4 @@ export type ConfigData = {
export { SettingsConfig, BlocksConfig, ViewsConfig, WidgetsConfig };
export * from './Blocks';
+export * from './Slots';
diff --git a/packages/types/src/content/common.d.ts b/packages/types/src/content/common.d.ts
index b1b91edf84..de8922c49f 100644
--- a/packages/types/src/content/common.d.ts
+++ b/packages/types/src/content/common.d.ts
@@ -2,6 +2,7 @@ import type { BreadcrumbsResponse } from '../services/breadcrumbs';
import type { NavigationResponse } from '../services/navigation';
import type { ActionsResponse } from '../services/actions';
import type { GetTypesResponse } from '../services/types';
+import type { GetNavrootResponse } from '../services/navroot';
import type { GetAliasesResponse } from '../services/aliases';
import type { ContextNavigationResponse } from '../services/contextnavigation';
import type { WorkflowResponse } from '../services/workflow';
@@ -13,6 +14,7 @@ export interface Expanders {
breadcrumbs: BreadcrumbsResponse;
contextnavigation: ContextNavigationResponse;
navigation: NavigationResponse;
+ navroot: GetNavrootResponse;
types: GetTypesResponse;
workflow: WorkflowResponse;
}
diff --git a/packages/volto-slate/CHANGELOG.md b/packages/volto-slate/CHANGELOG.md
index 756fd90751..62324926d2 100644
--- a/packages/volto-slate/CHANGELOG.md
+++ b/packages/volto-slate/CHANGELOG.md
@@ -8,6 +8,18 @@
+## 18.0.0-alpha.9 (2024-03-02)
+
+### Internal
+
+- Update dependencies @sneridagh [#5815](https://github.com/plone/volto/issues/5815)
+
+## 18.0.0-alpha.8 (2024-03-01)
+
+### Feature
+
+- Upgrade Volto core to use React 18.2.0 @sneridagh [#3221](https://github.com/plone/volto/issues/3221)
+
## 18.0.0-alpha.7 (2024-02-22)
### Bugfix
diff --git a/packages/volto-slate/package.json b/packages/volto-slate/package.json
index 8eadc11fee..1d4311f244 100644
--- a/packages/volto-slate/package.json
+++ b/packages/volto-slate/package.json
@@ -1,6 +1,6 @@
{
"name": "@plone/volto-slate",
- "version": "18.0.0-alpha.7",
+ "version": "18.0.0-alpha.9",
"description": "Slate.js integration with Volto",
"main": "src/index.js",
"author": "European Environment Agency: IDM2 A-Team",
@@ -21,9 +21,9 @@
"image-extensions": "1.1.0",
"is-url": "1.2.4",
"jsdom": "^16.6.0",
- "react": "17.0.2",
+ "react": "18.2.0",
"react-intersection-observer": "9.1.0",
- "react-dom": "17.0.2",
+ "react-dom": "18.2.0",
"slate": "0.100.0",
"slate-history": "0.100.0",
"slate-hyperscript": "0.100.0",
diff --git a/packages/volto-slate/src/blocks/Table/TableBlockView.jsx b/packages/volto-slate/src/blocks/Table/TableBlockView.jsx
index 8ea9c210aa..4e10c84dcc 100644
--- a/packages/volto-slate/src/blocks/Table/TableBlockView.jsx
+++ b/packages/volto-slate/src/blocks/Table/TableBlockView.jsx
@@ -101,8 +101,8 @@ const View = ({ data }) => {
state.column !== index
? 'ascending'
: state.direction === 'ascending'
- ? 'descending'
- : 'ascending',
+ ? 'descending'
+ : 'ascending',
});
}}
>
diff --git a/packages/volto-slate/src/blocks/Text/PluginSidebar.jsx b/packages/volto-slate/src/blocks/Text/PluginSidebar.jsx
index 3858e9448f..a5a3185a05 100644
--- a/packages/volto-slate/src/blocks/Text/PluginSidebar.jsx
+++ b/packages/volto-slate/src/blocks/Text/PluginSidebar.jsx
@@ -1,16 +1,15 @@
import React from 'react';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
const PluginSidebar = ({ children, selected }) => {
return (
<>
- {selected && (
-
- {children}
-
- )}
+ {selected &&
+ __CLIENT__ &&
+ createPortal(
+ <>{children}>,
+ document.getElementById('slate-plugin-sidebar'),
+ )}
>
);
};
diff --git a/packages/volto-slate/src/blocks/Text/SlashMenu.jsx b/packages/volto-slate/src/blocks/Text/SlashMenu.jsx
index de312e3d89..6fd5ed2f30 100644
--- a/packages/volto-slate/src/blocks/Text/SlashMenu.jsx
+++ b/packages/volto-slate/src/blocks/Text/SlashMenu.jsx
@@ -122,8 +122,8 @@ const PersistentSlashMenu = ({ editor }) => {
hasAllowedBlocks
? allowedBlocks.includes(item.id)
: typeof item.restricted === 'function'
- ? !item.restricted({ properties, block: item })
- : !item.restricted,
+ ? !item.restricted({ properties, block: item })
+ : !item.restricted,
)
.filter((block) => Boolean(block.title && block.id))
.filter((block) => {
diff --git a/packages/volto-slate/src/blocks/Text/extensions/breakList.js b/packages/volto-slate/src/blocks/Text/extensions/breakList.js
index c02e74ec13..b4117a0d8b 100644
--- a/packages/volto-slate/src/blocks/Text/extensions/breakList.js
+++ b/packages/volto-slate/src/blocks/Text/extensions/breakList.js
@@ -77,8 +77,8 @@ export const breakList = (editor) => {
Editor.deleteBackward(editor, { unit: 'line' });
// also account for empty nodes [{text: ''}]
if (Editor.isEmpty(editor, parent)) {
- Transforms.removeNodes(editor, { at: ref.current });
createAndSelectNewBlockAfter(editor, [createEmptyParagraph()]);
+ Transforms.removeNodes(editor, { at: ref.current });
return true;
}
diff --git a/packages/volto-slate/src/blocks/Text/extensions/insertBreak.js b/packages/volto-slate/src/blocks/Text/extensions/insertBreak.js
index 258aa7d382..d767c30ab6 100644
--- a/packages/volto-slate/src/blocks/Text/extensions/insertBreak.js
+++ b/packages/volto-slate/src/blocks/Text/extensions/insertBreak.js
@@ -42,9 +42,9 @@ export const withSplitBlocksOnBreak = (editor) => {
// deconstructToVoltoBlocks
ReactDOM.unstable_batchedUpdates(() => {
const [top, bottom] = splitEditorInTwoFragments(editor);
- setEditorContent(editor, top);
// ReactEditor.blur(editor);
createAndSelectNewBlockAfter(editor, bottom);
+ setEditorContent(editor, top);
});
}
return;
diff --git a/packages/volto-slate/src/blocks/Text/index.js b/packages/volto-slate/src/blocks/Text/index.js
index 562cc58341..37b93ecd02 100644
--- a/packages/volto-slate/src/blocks/Text/index.js
+++ b/packages/volto-slate/src/blocks/Text/index.js
@@ -128,8 +128,8 @@ export default function applyConfig(config) {
return override_toc && level
? [parseInt(level.slice(1)), entry_text]
: config.settings.slate.topLevelTargetElements.includes(type)
- ? [parseInt(type.slice(1)), plaintext]
- : null;
+ ? [parseInt(type.slice(1)), plaintext]
+ : null;
},
};
diff --git a/packages/volto-slate/src/blocks/Text/keyboard/indentListItems.js b/packages/volto-slate/src/blocks/Text/keyboard/indentListItems.js
index 7a17ecd26f..4f9f81cba2 100644
--- a/packages/volto-slate/src/blocks/Text/keyboard/indentListItems.js
+++ b/packages/volto-slate/src/blocks/Text/keyboard/indentListItems.js
@@ -55,8 +55,8 @@ export function indentListItems({ editor, event }) {
? decreaseMultipleItemsDepth(editor, event)
: decreaseItemDepth(editor, event)
: event.ctrlKey
- ? increaseMultipleItemDepth(editor, event)
- : increaseItemDepth(editor, event);
+ ? increaseMultipleItemDepth(editor, event)
+ : increaseItemDepth(editor, event);
}
}
diff --git a/packages/volto-slate/src/editor/SlateEditor.jsx b/packages/volto-slate/src/editor/SlateEditor.jsx
index d59e8b3f4a..fe8457b191 100644
--- a/packages/volto-slate/src/editor/SlateEditor.jsx
+++ b/packages/volto-slate/src/editor/SlateEditor.jsx
@@ -166,7 +166,14 @@ class SlateEditor extends Component {
ReactEditor.focus(editor);
Transforms.select(editor, selection);
} else {
- Transforms.select(editor, Editor.end(editor, []));
+ try {
+ Transforms.select(editor, Editor.end(editor, []));
+ } catch (error) {
+ // Weird error only happening in Cypress
+ // Adding a try/catch
+ // eslint-disable-next-line no-console
+ console.log(error);
+ }
}
this.setState({
diff --git a/packages/volto-slate/src/editor/ui/PositionedToolbar.jsx b/packages/volto-slate/src/editor/ui/PositionedToolbar.jsx
index 93ab6d27d2..1ea7ab68ce 100644
--- a/packages/volto-slate/src/editor/ui/PositionedToolbar.jsx
+++ b/packages/volto-slate/src/editor/ui/PositionedToolbar.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import BasicToolbar from './BasicToolbar';
@@ -20,12 +20,11 @@ const PositionedToolbar = ({ toggleButton, className, children, position }) => {
el.style.left = left;
});
- return (
-
-
- {children}
-
-
+ return createPortal(
+
+ {children}
+ ,
+ document.body,
);
};
diff --git a/packages/volto-slate/src/editor/ui/Toolbar.jsx b/packages/volto-slate/src/editor/ui/Toolbar.jsx
index 2d7406e12f..4e7afc51de 100644
--- a/packages/volto-slate/src/editor/ui/Toolbar.jsx
+++ b/packages/volto-slate/src/editor/ui/Toolbar.jsx
@@ -1,11 +1,11 @@
import cx from 'classnames';
import React, { useRef, useEffect } from 'react';
-import { Portal } from 'react-portal';
import { useSlate } from 'slate-react';
import Separator from './Separator';
import BasicToolbar from './BasicToolbar';
import { Editor, Node } from 'slate';
import { ReactEditor } from 'slate-react';
+import { createPortal } from 'react-dom';
const Toolbar = ({
elementType,
@@ -82,21 +82,20 @@ const Toolbar = ({
)}px`;
});
- return (
-
-
- {children}
- {enableExpando && toggleButton && (
- <>
-
- {toggleButton}
- >
- )}
-
-
+ return createPortal(
+
+ {children}
+ {enableExpando && toggleButton && (
+ <>
+
+ {toggleButton}
+ >
+ )}
+ ,
+ document.body,
);
};
diff --git a/packages/volto/CHANGELOG.md b/packages/volto/CHANGELOG.md
index b148fe9e5c..1273a7286f 100644
--- a/packages/volto/CHANGELOG.md
+++ b/packages/volto/CHANGELOG.md
@@ -17,6 +17,50 @@ myst:
+## 18.0.0-alpha.16 (2024-03-02)
+
+### Internal
+
+- Update dependencies
+ Fix prettier due to new version @sneridagh [#5815](https://github.com/plone/volto/issues/5815)
+
+### Documentation
+
+- Linkcheck thinks `README.md` is `http://README.md`. Bad linkcheck, no more 🍺 for you. @stevepiercy [#5816](https://github.com/plone/volto/issues/5816)
+
+## 18.0.0-alpha.15 (2024-03-01)
+
+### Breaking
+
+- Upgrade Volto core to use React 18.2.0 @sneridagh [#3221](https://github.com/plone/volto/issues/3221)
+
+## 18.0.0-alpha.14 (2024-03-01)
+
+### Breaking
+
+- Improved accessibility of logo component. @Molochem [#5776](https://github.com/plone/volto/issues/5776)
+
+### Feature
+
+- Support for slots @sneridagh [#5775](https://github.com/plone/volto/issues/5775)
+
+### Bugfix
+
+- Fixed toolbar menus not closing when clicking again on the toolbar buttons that show menus. @ichim-david
+ Add focus-visible rule to toolbar buttons so that it's visible to the user what button is focused when using tab navigation @ichim-david [#5645](https://github.com/plone/volto/issues/5645)
+- Enhance findBlocks to check for blocks also in data for add-ons such as @eeacms/volto-tabs-block. @ichim-david [#5796](https://github.com/plone/volto/issues/5796)
+- Fixed ArrayWidget sorting items. @giuliaghisini [#5805](https://github.com/plone/volto/issues/5805)
+
+### Internal
+
+- New types declarations with @types/react@18 - make tsc happy @sneridagh [#5814](https://github.com/plone/volto/issues/5814)
+
+### Documentation
+
+- Added Release Management Notes. @sneridagh @stevepiercy [#5358](https://github.com/plone/volto/issues/5358)
+- Delete redundant `developing-a-project.md`. @stevepiercy [#5675](https://github.com/plone/volto/issues/5675)
+- Removed Memori and TwinCreator websites from README.md no longer made using Volto and giving 404 error. @ichim-david [#5802](https://github.com/plone/volto/issues/5802)
+
## 18.0.0-alpha.13 (2024-02-22)
### Bugfix
diff --git a/packages/volto/Makefile b/packages/volto/Makefile
index fbb5a192fe..37a3b274ea 100644
--- a/packages/volto/Makefile
+++ b/packages/volto/Makefile
@@ -67,7 +67,10 @@ cypress-install:
.PHONY: build-deps
build-deps:
- if [ ! -d $$(pwd)/../registry/dist ]; then (cd ../../ && pnpm build:deps); fi
+ @if [ ! -d $$(pwd)/../registry/dist ] || [ $$(find $$(pwd)/../registry -newer $$(pwd)/../registry/dist -print -quit) ]; then \
+ (cd ../../ && pnpm build:deps); \
+ fi
+
##### Release (it runs the one inside)
diff --git a/packages/volto/cypress/tests/core/basic/folder-contents.js b/packages/volto/cypress/tests/core/basic/folder-contents.js
index 71dcaf9306..6efb50fe7e 100644
--- a/packages/volto/cypress/tests/core/basic/folder-contents.js
+++ b/packages/volto/cypress/tests/core/basic/folder-contents.js
@@ -130,25 +130,23 @@ describe('Folder Contents Tests', () => {
});
it('Move items to top of folder and bottom of folder', () => {
+ cy.intercept('GET', `/**/@search*`).as('search');
+ cy.intercept('PATCH', `/**/my-folder`).as('reorder');
// creating a Document
- cy.createContent({
- contentType: 'Document',
- contentId: 'child',
- contentTitle: 'My Child',
- path: 'my-folder',
- });
-
- // doing copy paste for dummy data
- cy.get('svg[class="icon unchecked"]').click();
- cy.get('svg[class="icon copy"]').click();
var genArr = Array.from({ length: 56 }, (v, k) => k + 1);
- cy.wrap(genArr).each((index) => {
- cy.get('svg[class="icon paste"]').click({ force: true });
+ genArr.forEach((item) => {
+ cy.createContent({
+ contentType: 'Document',
+ contentTitle: 'My Child',
+ path: 'my-folder',
+ });
});
- cy.wait(2000); // just for clearing of toast
// after adding 56 page I need to add a final page to move around.
// when I add a page
+ cy.visit('/my-folder');
+ cy.wait('@content');
+
cy.get('#toolbar-add').click();
cy.get('#toolbar-add-document').click();
cy.getSlateTitle()
@@ -159,6 +157,7 @@ describe('Folder Contents Tests', () => {
// then a new page has been created
cy.get('#toolbar-save').click();
+ cy.wait('@content');
cy.url().should(
'eq',
Cypress.config().baseUrl + '/my-folder/last-and-first-page',
@@ -171,7 +170,7 @@ describe('Folder Contents Tests', () => {
).click();
cy.findByText('Move to top of folder').click();
cy.url().should('eq', Cypress.config().baseUrl + '/my-folder/contents');
- cy.wait(1000); // waiting for settling of odering or search return
+ cy.wait('@reorder');
// Checking if move to top of folder works or not.
cy.get('table tbody tr:first-child a span').findByText(
@@ -181,6 +180,7 @@ describe('Folder Contents Tests', () => {
'tr[aria-label="/my-folder/last-and-first-page"] svg[class="icon dropdown-popup-trigger"]',
).click();
cy.findByText('Move to bottom of folder').click();
+ cy.wait('@reorder');
// Checking whether moving to bottom of folder works or not.
cy.get('.contents-pagination .menu').findByText('2').click();
@@ -195,7 +195,8 @@ describe('Folder Contents Tests', () => {
cy.get(
'tr[aria-label="/my-folder/last-and-first-page"] svg[class="icon dropdown-popup-trigger"]',
).click();
- // cy.intercept('GET', '/plone/++api++/my-folder/@search').as('getSearch'); // I don't know proper way to wait
+ cy.wait('@search');
+
cy.findByText('Move to top of folder').click();
cy.get('.search.item button').click();
@@ -207,6 +208,8 @@ describe('Folder Contents Tests', () => {
'tr[aria-label="/my-folder/last-and-first-page"] svg[class="icon dropdown-popup-trigger"]',
).click();
cy.findByText('Move to bottom of folder').click();
+ cy.wait('@reorder');
+
cy.get('.contents-pagination .menu').findByText('2').click();
// Checking whether moving to bottom of folder works or not.
diff --git a/packages/volto/cypress/tests/core/blocks/blocks-autofocus.js b/packages/volto/cypress/tests/core/blocks/blocks-autofocus.js
index fed851ad51..ef48d8f8bb 100644
--- a/packages/volto/cypress/tests/core/blocks/blocks-autofocus.js
+++ b/packages/volto/cypress/tests/core/blocks/blocks-autofocus.js
@@ -1,7 +1,7 @@
describe('New Block Auto Focus Tests', () => {
beforeEach(() => {
- cy.intercept('GET', `/**/*?expand*`).as('content');
cy.intercept('GET', '/**/my-page').as('content');
+ cy.intercept('GET', '/**/Document').as('schema');
cy.intercept('PATCH', '*').as('save');
// given a logged in editor and a page in edit mode
@@ -15,12 +15,17 @@ describe('New Block Auto Focus Tests', () => {
cy.visit('/my-page');
cy.wait('@content');
+ cy.wait(500);
+
cy.navigate('/my-page/edit');
+ cy.wait('@schema');
});
it('Press Enter on a description block adds new autofocused default block', () => {
cy.addNewBlock('description');
cy.get('.documentDescription').first().click().type('{enter}');
+ cy.get('.block-editor-description + .block-editor-slate');
+ cy.wait(500);
cy.get('*[class^="block-editor"]')
.eq(2)
.within(() => {
@@ -34,6 +39,8 @@ describe('New Block Auto Focus Tests', () => {
cy.get('.blocks-chooser .title').contains('Text').click({ force: true });
cy.get('.blocks-chooser .text').contains('Text').click({ force: true });
cy.get('.text-slate-editor-inner').first().click().type('{enter}');
+ cy.wait(500);
+ cy.get('.block-editor-slate + .block-editor-slate');
cy.get('*[class^="block-editor"]')
.eq(2)
.within(() => {
@@ -43,7 +50,15 @@ describe('New Block Auto Focus Tests', () => {
it('Press Enter on a image block adds new autofocused default block', () => {
cy.addNewBlock('image');
- cy.get('.block-editor-image').first().click().type('{enter}');
+ // Timing issues ahead :(
+ cy.get('.block-editor-image .no-image-wrapper img')
+ .should('be.visible')
+ .and(($img) => {
+ // "naturalWidth" and "naturalHeight" are set when the image loads
+ expect($img[0].naturalWidth).to.be.greaterThan(0);
+ });
+ cy.get('.block-editor-image').wait(500).click('topLeft').type('{enter}');
+ cy.wait(500);
cy.get('*[class^="block-editor"]')
.eq(2)
.within(() => {
@@ -53,7 +68,15 @@ describe('New Block Auto Focus Tests', () => {
it('Press Enter on a video block adds new autofocused default block', () => {
cy.addNewBlock('video');
+ cy.get('.block-editor-video .message img')
+ .should('be.visible')
+ .and(($img) => {
+ // "naturalWidth" and "naturalHeight" are set when the image loads
+ expect($img[0].naturalWidth).to.be.greaterThan(0);
+ });
cy.get('.block-editor-video').first().click().type('{enter}');
+ cy.get('.block-editor-slate + .block-editor-slate');
+ cy.wait(500);
cy.get('*[class^="block-editor"]')
.eq(2)
.within(() => {
@@ -64,6 +87,7 @@ describe('New Block Auto Focus Tests', () => {
it('Press Enter on a listing block adds new autofocused default block', () => {
cy.addNewBlock('listing');
cy.get('.block-editor-listing').first().click().type('{enter}');
+ cy.wait(500);
cy.get('*[class^="block-editor"]')
.eq(2)
.within(() => {
@@ -74,6 +98,7 @@ describe('New Block Auto Focus Tests', () => {
it('Press Enter on a table of contents block adds new autofocused default block', () => {
cy.addNewBlock('contents');
cy.get('.block-editor-toc').first().click().type('{enter}');
+ cy.wait(500);
cy.get('*[class^="block-editor"]')
.eq(2)
.within(() => {
@@ -83,7 +108,14 @@ describe('New Block Auto Focus Tests', () => {
it('Press Enter on a maps block adds new autofocused default block', () => {
cy.addNewBlock('maps');
+ cy.get('.block-editor-maps .message img')
+ .should('be.visible')
+ .and(($img) => {
+ // "naturalWidth" and "naturalHeight" are set when the image loads
+ expect($img[0].naturalWidth).to.be.greaterThan(0);
+ });
cy.get('.block-editor-maps').first().click().type('{enter}');
+ cy.wait(500);
cy.get('*[class^="block-editor"]')
.eq(2)
.within(() => {
@@ -94,6 +126,7 @@ describe('New Block Auto Focus Tests', () => {
it('Press Enter on a html block adds new autofocused default block', () => {
cy.addNewBlock('html');
cy.get('.block-editor-html').first().click().type('{enter}');
+ cy.wait(500);
cy.get('*[class^="block-editor"]')
.eq(2)
.within(() => {
@@ -104,6 +137,7 @@ describe('New Block Auto Focus Tests', () => {
it('Press Enter on a search block adds new autofocused default block', () => {
cy.addNewBlock('search');
cy.get('.block-editor-search').first().click().type('{enter}');
+ cy.wait(500);
cy.get('*[class^="block-editor"]')
.eq(2)
.within(() => {
diff --git a/packages/volto/cypress/tests/core/blocks/blocks-image.js b/packages/volto/cypress/tests/core/blocks/blocks-image.js
index e96bb1dd21..9b76682372 100644
--- a/packages/volto/cypress/tests/core/blocks/blocks-image.js
+++ b/packages/volto/cypress/tests/core/blocks/blocks-image.js
@@ -15,6 +15,8 @@ describe('Blocks Tests', () => {
cy.visit('/my-page');
cy.wait('@content');
+ cy.wait(500);
+
cy.navigate('/my-page/edit');
cy.wait('@schema');
});
@@ -156,7 +158,7 @@ describe('Blocks Tests', () => {
});
});
- it('Create an image block and initially alt attr is empty', () => {
+ it.only('Create an image block and initially alt attr is empty', () => {
// when I add an image block via upload
cy.get('.content-area .slate-editor [contenteditable=true]', {
timeout: 10000,
diff --git a/packages/volto/cypress/tests/core/blocks/listing/blocks-listing.js b/packages/volto/cypress/tests/core/blocks/listing/blocks-listing.js
index cc4f5e13d8..ab7c82bfcf 100644
--- a/packages/volto/cypress/tests/core/blocks/listing/blocks-listing.js
+++ b/packages/volto/cypress/tests/core/blocks/listing/blocks-listing.js
@@ -445,7 +445,9 @@ describe('Listing Block Tests', () => {
//add listing block
cy.scrollTo('bottom');
- cy.addNewBlock('listing', true);
+ cy.getSlate().click();
+ cy.get('.ui.basic.icon.button.block-add-button').click();
+ cy.get('.ui.basic.icon.button.listing').contains('Listing').click();
//******** add Page Type criteria filter
cy.configureListingWith('Page');
diff --git a/packages/volto/cypress/tests/core/controlpanels/dexterity-controlpanel-layout.js b/packages/volto/cypress/tests/core/controlpanels/dexterity-controlpanel-layout.js
index 35a24d1b65..20b1a097e6 100644
--- a/packages/volto/cypress/tests/core/controlpanels/dexterity-controlpanel-layout.js
+++ b/packages/volto/cypress/tests/core/controlpanels/dexterity-controlpanel-layout.js
@@ -21,7 +21,7 @@ describe('ControlPanel: Dexterity Content-Types Layout', () => {
'Book',
);
- cy.visit('/controlpanel/dexterity-types/book/layout');
+ cy.navigate('/controlpanel/dexterity-types/book/layout');
cy.get('#page-controlpanel-layout').contains(
'Can not edit Layout for Book',
);
diff --git a/packages/volto/cypress/tests/coresandbox/slots.js b/packages/volto/cypress/tests/coresandbox/slots.js
new file mode 100644
index 0000000000..4d8abe6e48
--- /dev/null
+++ b/packages/volto/cypress/tests/coresandbox/slots.js
@@ -0,0 +1,43 @@
+context('Slots', () => {
+ describe('Block Default View / Edit', () => {
+ beforeEach(() => {
+ cy.intercept('GET', `/**/*?expand*`).as('content');
+ cy.intercept('GET', '/**/Document').as('schema');
+ // given a logged in editor and a page in edit mode
+ cy.autologin();
+ cy.createContent({
+ contentType: 'Document',
+ contentId: 'document',
+ contentTitle: 'Test document',
+ });
+ cy.createContent({
+ contentType: 'Document',
+ contentId: 'hello',
+ contentTitle: 'Test document Hello',
+ });
+
+ cy.visit('/');
+ cy.wait('@content');
+ });
+
+ it('[ContentTypeCondition(["Document"]), RouteCondition("/hello")] only renders when the predicates are true', function () {
+ cy.get('body').should(
+ 'not.include.text',
+ 'This is a test slot component',
+ );
+
+ cy.navigate('/document');
+ cy.wait('@content');
+
+ cy.get('body').should(
+ 'not.include.text',
+ 'This is a test slot component',
+ );
+
+ cy.navigate('/hello');
+ cy.wait('@content');
+
+ cy.get('body').should('include.text', 'This is a test slot component');
+ });
+ });
+});
diff --git a/packages/volto/jest-setup-afterenv.js b/packages/volto/jest-setup-afterenv.js
new file mode 100644
index 0000000000..8c79262657
--- /dev/null
+++ b/packages/volto/jest-setup-afterenv.js
@@ -0,0 +1,2 @@
+// Jest-crap setup after env T_T
+import '@testing-library/jest-dom';
diff --git a/packages/volto/locales/ca/LC_MESSAGES/volto.po b/packages/volto/locales/ca/LC_MESSAGES/volto.po
index 124ebbcd01..517af40068 100644
--- a/packages/volto/locales/ca/LC_MESSAGES/volto.po
+++ b/packages/volto/locales/ca/LC_MESSAGES/volto.po
@@ -3656,6 +3656,12 @@ msgstr ""
msgid "There are no users with the searched criteria"
msgstr ""
+#. Default: "There are some errors."
+#: components/manage/Add/Add
+#: components/manage/Edit/Edit
+msgid "There are some errors."
+msgstr ""
+
#. Default: "There is a configuration problem on the backend"
#: components/theme/CorsError/CorsError
msgid "There is a configuration problem on the backend"
diff --git a/packages/volto/locales/de/LC_MESSAGES/volto.po b/packages/volto/locales/de/LC_MESSAGES/volto.po
index 6011f4fed2..daebb01e75 100644
--- a/packages/volto/locales/de/LC_MESSAGES/volto.po
+++ b/packages/volto/locales/de/LC_MESSAGES/volto.po
@@ -3655,6 +3655,12 @@ msgstr ""
msgid "There are no users with the searched criteria"
msgstr ""
+#. Default: "There are some errors."
+#: components/manage/Add/Add
+#: components/manage/Edit/Edit
+msgid "There are some errors."
+msgstr ""
+
#. Default: "There is a configuration problem on the backend"
#: components/theme/CorsError/CorsError
msgid "There is a configuration problem on the backend"
diff --git a/packages/volto/locales/en/LC_MESSAGES/volto.po b/packages/volto/locales/en/LC_MESSAGES/volto.po
index 19b258fecb..d54686ff84 100644
--- a/packages/volto/locales/en/LC_MESSAGES/volto.po
+++ b/packages/volto/locales/en/LC_MESSAGES/volto.po
@@ -3650,6 +3650,12 @@ msgstr ""
msgid "There are no users with the searched criteria"
msgstr ""
+#. Default: "There are some errors."
+#: components/manage/Add/Add
+#: components/manage/Edit/Edit
+msgid "There are some errors."
+msgstr ""
+
#. Default: "There is a configuration problem on the backend"
#: components/theme/CorsError/CorsError
msgid "There is a configuration problem on the backend"
diff --git a/packages/volto/locales/es/LC_MESSAGES/volto.po b/packages/volto/locales/es/LC_MESSAGES/volto.po
index f9f065a686..0a9b7f60cd 100644
--- a/packages/volto/locales/es/LC_MESSAGES/volto.po
+++ b/packages/volto/locales/es/LC_MESSAGES/volto.po
@@ -3657,6 +3657,12 @@ msgstr "No hay grupos con los criterios buscados."
msgid "There are no users with the searched criteria"
msgstr "No hay usuarios con los criterios buscados"
+#. Default: "There are some errors."
+#: components/manage/Add/Add
+#: components/manage/Edit/Edit
+msgid "There are some errors."
+msgstr ""
+
#. Default: "There is a configuration problem on the backend"
#: components/theme/CorsError/CorsError
msgid "There is a configuration problem on the backend"
diff --git a/packages/volto/locales/eu/LC_MESSAGES/volto.po b/packages/volto/locales/eu/LC_MESSAGES/volto.po
index 7064a7b56c..2afcc9f192 100644
--- a/packages/volto/locales/eu/LC_MESSAGES/volto.po
+++ b/packages/volto/locales/eu/LC_MESSAGES/volto.po
@@ -3657,6 +3657,12 @@ msgstr ""
msgid "There are no users with the searched criteria"
msgstr ""
+#. Default: "There are some errors."
+#: components/manage/Add/Add
+#: components/manage/Edit/Edit
+msgid "There are some errors."
+msgstr ""
+
#. Default: "There is a configuration problem on the backend"
#: components/theme/CorsError/CorsError
msgid "There is a configuration problem on the backend"
diff --git a/packages/volto/locales/fi/LC_MESSAGES/volto.po b/packages/volto/locales/fi/LC_MESSAGES/volto.po
index 03b2780a8c..ff5107a887 100644
--- a/packages/volto/locales/fi/LC_MESSAGES/volto.po
+++ b/packages/volto/locales/fi/LC_MESSAGES/volto.po
@@ -3655,6 +3655,12 @@ msgstr ""
msgid "There are no users with the searched criteria"
msgstr ""
+#. Default: "There are some errors."
+#: components/manage/Add/Add
+#: components/manage/Edit/Edit
+msgid "There are some errors."
+msgstr ""
+
#. Default: "There is a configuration problem on the backend"
#: components/theme/CorsError/CorsError
msgid "There is a configuration problem on the backend"
diff --git a/packages/volto/locales/fr/LC_MESSAGES/volto.po b/packages/volto/locales/fr/LC_MESSAGES/volto.po
index 79c1ee9372..fd79824475 100644
--- a/packages/volto/locales/fr/LC_MESSAGES/volto.po
+++ b/packages/volto/locales/fr/LC_MESSAGES/volto.po
@@ -3657,6 +3657,12 @@ msgstr ""
msgid "There are no users with the searched criteria"
msgstr ""
+#. Default: "There are some errors."
+#: components/manage/Add/Add
+#: components/manage/Edit/Edit
+msgid "There are some errors."
+msgstr ""
+
#. Default: "There is a configuration problem on the backend"
#: components/theme/CorsError/CorsError
msgid "There is a configuration problem on the backend"
diff --git a/packages/volto/locales/it/LC_MESSAGES/volto.po b/packages/volto/locales/it/LC_MESSAGES/volto.po
index ee27a0e94c..6353bffafb 100644
--- a/packages/volto/locales/it/LC_MESSAGES/volto.po
+++ b/packages/volto/locales/it/LC_MESSAGES/volto.po
@@ -3650,6 +3650,12 @@ msgstr ""
msgid "There are no users with the searched criteria"
msgstr ""
+#. Default: "There are some errors."
+#: components/manage/Add/Add
+#: components/manage/Edit/Edit
+msgid "There are some errors."
+msgstr ""
+
#. Default: "There is a configuration problem on the backend"
#: components/theme/CorsError/CorsError
msgid "There is a configuration problem on the backend"
diff --git a/packages/volto/locales/ja/LC_MESSAGES/volto.po b/packages/volto/locales/ja/LC_MESSAGES/volto.po
index 23c0849a3c..200948bd4a 100644
--- a/packages/volto/locales/ja/LC_MESSAGES/volto.po
+++ b/packages/volto/locales/ja/LC_MESSAGES/volto.po
@@ -3655,6 +3655,12 @@ msgstr ""
msgid "There are no users with the searched criteria"
msgstr ""
+#. Default: "There are some errors."
+#: components/manage/Add/Add
+#: components/manage/Edit/Edit
+msgid "There are some errors."
+msgstr ""
+
#. Default: "There is a configuration problem on the backend"
#: components/theme/CorsError/CorsError
msgid "There is a configuration problem on the backend"
diff --git a/packages/volto/locales/nl/LC_MESSAGES/volto.po b/packages/volto/locales/nl/LC_MESSAGES/volto.po
index fe21ea8d91..e5bb5478f5 100644
--- a/packages/volto/locales/nl/LC_MESSAGES/volto.po
+++ b/packages/volto/locales/nl/LC_MESSAGES/volto.po
@@ -3654,6 +3654,12 @@ msgstr ""
msgid "There are no users with the searched criteria"
msgstr ""
+#. Default: "There are some errors."
+#: components/manage/Add/Add
+#: components/manage/Edit/Edit
+msgid "There are some errors."
+msgstr ""
+
#. Default: "There is a configuration problem on the backend"
#: components/theme/CorsError/CorsError
msgid "There is a configuration problem on the backend"
diff --git a/packages/volto/locales/pt/LC_MESSAGES/volto.po b/packages/volto/locales/pt/LC_MESSAGES/volto.po
index c341c5f237..2239501dc2 100644
--- a/packages/volto/locales/pt/LC_MESSAGES/volto.po
+++ b/packages/volto/locales/pt/LC_MESSAGES/volto.po
@@ -3655,6 +3655,12 @@ msgstr ""
msgid "There are no users with the searched criteria"
msgstr ""
+#. Default: "There are some errors."
+#: components/manage/Add/Add
+#: components/manage/Edit/Edit
+msgid "There are some errors."
+msgstr ""
+
#. Default: "There is a configuration problem on the backend"
#: components/theme/CorsError/CorsError
msgid "There is a configuration problem on the backend"
diff --git a/packages/volto/locales/pt_BR/LC_MESSAGES/volto.po b/packages/volto/locales/pt_BR/LC_MESSAGES/volto.po
index 663e5e5ab4..fb355baa90 100644
--- a/packages/volto/locales/pt_BR/LC_MESSAGES/volto.po
+++ b/packages/volto/locales/pt_BR/LC_MESSAGES/volto.po
@@ -3656,6 +3656,12 @@ msgstr ""
msgid "There are no users with the searched criteria"
msgstr ""
+#. Default: "There are some errors."
+#: components/manage/Add/Add
+#: components/manage/Edit/Edit
+msgid "There are some errors."
+msgstr "Existem alguns erros."
+
#. Default: "There is a configuration problem on the backend"
#: components/theme/CorsError/CorsError
msgid "There is a configuration problem on the backend"
diff --git a/packages/volto/locales/ro/LC_MESSAGES/volto.po b/packages/volto/locales/ro/LC_MESSAGES/volto.po
index 2c75e1aa99..0f5512ad88 100644
--- a/packages/volto/locales/ro/LC_MESSAGES/volto.po
+++ b/packages/volto/locales/ro/LC_MESSAGES/volto.po
@@ -3650,6 +3650,12 @@ msgstr ""
msgid "There are no users with the searched criteria"
msgstr ""
+#. Default: "There are some errors."
+#: components/manage/Add/Add
+#: components/manage/Edit/Edit
+msgid "There are some errors."
+msgstr ""
+
#. Default: "There is a configuration problem on the backend"
#: components/theme/CorsError/CorsError
msgid "There is a configuration problem on the backend"
diff --git a/packages/volto/locales/volto.pot b/packages/volto/locales/volto.pot
index 414d63d872..2791c609c7 100644
--- a/packages/volto/locales/volto.pot
+++ b/packages/volto/locales/volto.pot
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: Plone\n"
-"POT-Creation-Date: 2024-02-26T09:23:35.781Z\n"
+"POT-Creation-Date: 2024-02-26T19:56:41.001Z\n"
"Last-Translator: Plone i18n \n"
"Language-Team: Plone i18n \n"
"Content-Type: text/plain; charset=utf-8\n"
@@ -3652,6 +3652,12 @@ msgstr ""
msgid "There are no users with the searched criteria"
msgstr ""
+#. Default: "There are some errors."
+#: components/manage/Add/Add
+#: components/manage/Edit/Edit
+msgid "There are some errors."
+msgstr ""
+
#. Default: "There is a configuration problem on the backend"
#: components/theme/CorsError/CorsError
msgid "There is a configuration problem on the backend"
diff --git a/packages/volto/locales/zh_CN/LC_MESSAGES/volto.po b/packages/volto/locales/zh_CN/LC_MESSAGES/volto.po
index 105ead2efc..eb6b8a7ade 100644
--- a/packages/volto/locales/zh_CN/LC_MESSAGES/volto.po
+++ b/packages/volto/locales/zh_CN/LC_MESSAGES/volto.po
@@ -3656,6 +3656,12 @@ msgstr ""
msgid "There are no users with the searched criteria"
msgstr ""
+#. Default: "There are some errors."
+#: components/manage/Add/Add
+#: components/manage/Edit/Edit
+msgid "There are some errors."
+msgstr ""
+
#. Default: "There is a configuration problem on the backend"
#: components/theme/CorsError/CorsError
msgid "There is a configuration problem on the backend"
diff --git a/packages/volto/news/1868.bugfix b/packages/volto/news/1868.bugfix
new file mode 100644
index 0000000000..f807b6ba1f
--- /dev/null
+++ b/packages/volto/news/1868.bugfix
@@ -0,0 +1 @@
+Show validation error message as string instead of list. @wesleybl
diff --git a/packages/volto/news/5358.documentation b/packages/volto/news/5358.documentation
deleted file mode 100644
index c954effb8c..0000000000
--- a/packages/volto/news/5358.documentation
+++ /dev/null
@@ -1 +0,0 @@
-Added Release Management Notes. @sneridagh @stevepiercy
diff --git a/packages/volto/news/5645.bugfix b/packages/volto/news/5645.bugfix
deleted file mode 100644
index e7c1940261..0000000000
--- a/packages/volto/news/5645.bugfix
+++ /dev/null
@@ -1,2 +0,0 @@
-Fixed toolbar menus not closing when clicking again on the toolbar buttons that show menus. @ichim-david
-Add focus-visible rule to toolbar buttons so that it's visible to the user what button is focused when using tab navigation @ichim-david
\ No newline at end of file
diff --git a/packages/volto/news/5776.breaking b/packages/volto/news/5776.breaking
deleted file mode 100644
index 0c25069810..0000000000
--- a/packages/volto/news/5776.breaking
+++ /dev/null
@@ -1 +0,0 @@
-Improved accessibility of logo component. @Molochem
\ No newline at end of file
diff --git a/packages/volto/news/5796.bugfix b/packages/volto/news/5796.bugfix
deleted file mode 100644
index e5975989d2..0000000000
--- a/packages/volto/news/5796.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Enhance findBlocks to check for blocks also in data for add-ons such as @eeacms/volto-tabs-block. @ichim-david
\ No newline at end of file
diff --git a/packages/volto/news/5802.documentation b/packages/volto/news/5802.documentation
deleted file mode 100644
index 8d3c2f16d3..0000000000
--- a/packages/volto/news/5802.documentation
+++ /dev/null
@@ -1 +0,0 @@
-Removed Memori and TwinCreator websites from README.md no longer made using Volto and giving 404 error. @ichim-david
\ No newline at end of file
diff --git a/packages/volto/news/5805.bugfix b/packages/volto/news/5805.bugfix
deleted file mode 100644
index 68088432fd..0000000000
--- a/packages/volto/news/5805.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Fixed ArrayWidget sorting items. @giuliaghisini
diff --git a/packages/volto/news/5825.bugfix b/packages/volto/news/5825.bugfix
new file mode 100644
index 0000000000..844a5247b8
--- /dev/null
+++ b/packages/volto/news/5825.bugfix
@@ -0,0 +1 @@
+Modified build-deps make command to check if registry files are newer than dist to force rebuild. @ichim-david
\ No newline at end of file
diff --git a/packages/volto/package.json b/packages/volto/package.json
index c202aaf0c6..0b1d9e518b 100644
--- a/packages/volto/package.json
+++ b/packages/volto/package.json
@@ -9,7 +9,7 @@
}
],
"license": "MIT",
- "version": "18.0.0-alpha.13",
+ "version": "18.0.0-alpha.16",
"repository": {
"type": "git",
"url": "git@github.com:plone/volto.git"
@@ -110,6 +110,9 @@
"./test-setup-config.js"
],
"globalSetup": "./global-test-setup.js",
+ "setupFilesAfterEnv": [
+ "/jest-setup-afterenv.js"
+ ],
"globals": {
"__DEV__": true
},
@@ -189,6 +192,9 @@
"@plone/registry": "workspace:*",
"@plone/scripts": "workspace:*",
"@plone/volto-slate": "workspace:*",
+ "@redux-devtools/extension": "^3.3.0",
+ "@types/react": "^18.2.57",
+ "@types/react-dom": "^18.2.19",
"autoprefixer": "10.4.8",
"axe-core": "4.4.2",
"babel-plugin-add-module-exports": "0.2.1",
@@ -217,13 +223,13 @@
"draft-js-plugins-utils": "2.0.3",
"draftjs-filters": "2.3.0",
"eslint": "8.49.0",
- "eslint-config-prettier": "9.0.0",
+ "eslint-config-prettier": "9.1.0",
"eslint-config-react-app": "7.0.1",
"eslint-import-resolver-alias": "1.1.2",
"eslint-import-resolver-babel-plugin-root-import": "1.1.1",
- "eslint-plugin-import": "2.28.1",
+ "eslint-plugin-import": "2.29.1",
"eslint-plugin-jsx-a11y": "^6.7.1",
- "eslint-plugin-prettier": "5.0.0",
+ "eslint-plugin-prettier": "5.1.3",
"eslint-plugin-react": "7.33.2",
"eslint-plugin-react-hooks": "4.6.0",
"express": "4.17.3",
@@ -263,7 +269,7 @@
"postcss-overrides": "3.1.4",
"postcss-scss": "4.0.6",
"prepend-http": "2",
- "prettier": "3.0.3",
+ "prettier": "3.2.5",
"pretty-bytes": "5.3.0",
"prismjs": "1.27.0",
"process": "^0.11.10",
@@ -274,7 +280,7 @@
"razzle-dev-utils": "4.2.18",
"razzle-plugin-scss": "4.2.18",
"rc-time-picker": "3.7.3",
- "react": "17.0.2",
+ "react": "18.2.0",
"react-anchor-link-smooth-scroll": "1.0.12",
"react-animate-height": "2.0.17",
"react-beautiful-dnd": "13.0.0",
@@ -283,17 +289,16 @@
"react-detect-click-outside": "1.1.1",
"react-dnd": "5.0.0",
"react-dnd-html5-backend": "5.0.1",
- "react-dom": "17.0.2",
+ "react-dom": "18.2.0",
"react-dropzone": "11.1.0",
"react-fast-compare": "2.0.4",
"react-image-gallery": "1.2.7",
"react-intersection-observer": "9.1.0",
"react-intl": "3.8.0",
- "react-intl-redux": "2.2.0",
+ "react-intl-redux": "2.3.0",
"react-medium-image-zoom": "3.0.15",
"react-popper": "^2.3.0",
- "react-portal": "4.2.1",
- "react-redux": "7.2.4",
+ "react-redux": "8.1.2",
"react-router": "5.2.0",
"react-router-config": "5.1.1",
"react-router-dom": "5.2.0",
@@ -301,21 +306,20 @@
"react-select": "4.3.1",
"react-select-async-paginate": "0.5.3",
"react-share": "2.3.1",
- "react-side-effect": "2.1.0",
+ "react-side-effect": "2.1.2",
"react-simple-code-editor": "0.7.1",
"react-sortable-hoc": "2.0.0",
- "react-test-renderer": "17.0.2",
+ "react-test-renderer": "18.2.0",
"react-toastify": "5.4.1",
"react-transition-group": "4.4.5",
"react-virtualized": "9.22.3",
"redraft": "0.10.2",
- "redux": "4.1.0",
- "redux-actions": "2.6.5",
+ "redux": "4.2.1",
+ "redux-actions": "3.0.0",
"redux-connect": "10.0.0",
- "redux-devtools-extension": "2.13.8",
- "redux-localstorage-simple": "2.3.1",
+ "redux-localstorage-simple": "2.5.1",
"redux-mock-store": "1.5.4",
- "redux-thunk": "2.3.0",
+ "redux-thunk": "2.4.2",
"rrule": "2.7.1",
"semantic-ui-less": "2.4.1",
"semantic-ui-react": "2.1.5",
@@ -325,9 +329,9 @@
"slate-react": "0.98.4",
"start-server-and-test": "1.14.0",
"style-loader": "3.3.1",
- "stylelint": "15.10.3",
- "stylelint-config-idiomatic-order": "9.0.0",
- "stylelint-prettier": "4.0.2",
+ "stylelint": "16.2.1",
+ "stylelint-config-idiomatic-order": "10.0.0",
+ "stylelint-prettier": "5.0.0",
"superagent": "3.8.2",
"svg-loader": "0.0.2",
"svgo-loader": "3.0.3",
@@ -343,8 +347,7 @@
"webpack-bundle-analyzer": "4.10.1",
"webpack-dev-server": "4.11.1",
"webpack-node-externals": "3.0.0",
- "xmlrpc": "1.3.2",
- "yarnhook": "0.5.1"
+ "xmlrpc": "1.3.2"
},
"devDependencies": {
"@jest/globals": "^29.7.0",
@@ -360,14 +363,15 @@
"@storybook/manager-webpack5": "^6.5.15",
"@storybook/react": "^6.5.15",
"@testing-library/cypress": "9.0.0",
- "@testing-library/jest-dom": "5.16.4",
- "@testing-library/react": "12.1.5",
+ "@testing-library/jest-dom": "6.4.1",
+ "@testing-library/react": "14.2.0",
"@testing-library/react-hooks": "8.0.1",
"@types/jest": "^29.5.8",
"@types/lodash": "^4.14.201",
- "@types/react": "^17.0.52",
- "@types/react-dom": "^17",
- "@types/react-test-renderer": "18.0.1",
+ "@types/react": "^18",
+ "@types/react-dom": "^18",
+ "@types/react-router-dom": "^5.3.3",
+ "@types/react-test-renderer": "18.0.7",
"@types/uuid": "^9.0.2",
"@typescript-eslint/eslint-plugin": "6.7.0",
"@typescript-eslint/parser": "6.7.0",
@@ -384,7 +388,7 @@
"jsonwebtoken": "9.0.0",
"react-docgen-typescript-plugin": "^1.0.5",
"react-error-overlay": "6.0.9",
- "react-is": "^16.13.1",
+ "react-is": "^18.2.0",
"release-it": "^16.2.1",
"semver": "^7.5.4",
"tmp": "0.2.1",
diff --git a/packages/volto/src/components/index.js b/packages/volto/src/components/index.js
index 31f733f367..fc1196ee2b 100644
--- a/packages/volto/src/components/index.js
+++ b/packages/volto/src/components/index.js
@@ -64,8 +64,8 @@ export { default as FileView } from '@plone/volto/components/theme/View/FileView
export { default as ImageView } from '@plone/volto/components/theme/View/ImageView';
export { default as NewsItemView } from '@plone/volto/components/theme/View/NewsItemView';
-export const EventView = loadable(() =>
- import('@plone/volto/components/theme/View/EventView'),
+export const EventView = loadable(
+ () => import('@plone/volto/components/theme/View/EventView'),
);
export { default as ListingView } from '@plone/volto/components/theme/View/ListingView';
@@ -152,13 +152,14 @@ export { default as FormFieldWrapper } from '@plone/volto/components/manage/Widg
export { default as ArrayWidget } from '@plone/volto/components/manage/Widgets/ArrayWidget';
export { default as CheckboxWidget } from '@plone/volto/components/manage/Widgets/CheckboxWidget';
-export const DatetimeWidget = loadable(() =>
- import('@plone/volto/components/manage/Widgets/DatetimeWidget'),
+export const DatetimeWidget = loadable(
+ () => import('@plone/volto/components/manage/Widgets/DatetimeWidget'),
);
-export const RecurrenceWidget = loadable(() =>
- import(
- '@plone/volto/components/manage/Widgets/RecurrenceWidget/RecurrenceWidget'
- ),
+export const RecurrenceWidget = loadable(
+ () =>
+ import(
+ '@plone/volto/components/manage/Widgets/RecurrenceWidget/RecurrenceWidget'
+ ),
);
export { default as FileWidget } from '@plone/volto/components/manage/Widgets/FileWidget';
diff --git a/packages/volto/src/components/manage/Add/Add.jsx b/packages/volto/src/components/manage/Add/Add.jsx
index d1c14bee17..f5f187d9c5 100644
--- a/packages/volto/src/components/manage/Add/Add.jsx
+++ b/packages/volto/src/components/manage/Add/Add.jsx
@@ -11,7 +11,7 @@ import { compose } from 'redux';
import { keys, isEmpty } from 'lodash';
import { defineMessages, injectIntl } from 'react-intl';
import { Button, Grid, Menu } from 'semantic-ui-react';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import { v4 as uuid } from 'uuid';
import qs from 'query-string';
import { toast } from 'react-toastify';
@@ -39,6 +39,7 @@ import {
} from '@plone/volto/helpers';
import { preloadLazyLibs } from '@plone/volto/helpers/Loadable';
+import { tryParseJSON } from '@plone/volto/helpers';
import config from '@plone/volto/registry';
@@ -66,6 +67,10 @@ const messages = defineMessages({
id: 'Translate to {lang}',
defaultMessage: 'Translate to {lang}',
},
+ someErrors: {
+ id: 'There are some errors.',
+ defaultMessage: 'There are some errors.',
+ },
});
/**
@@ -168,13 +173,30 @@ class Add extends Component {
new DOMParser().parseFromString(message, 'text/html')?.all[0]
?.textContent || message;
+ const errorsList = tryParseJSON(error);
+ let erroMessage;
+ if (Array.isArray(errorsList)) {
+ const invariantErrors = errorsList
+ .filter((errorItem) => !('field' in errorItem))
+ .map((errorItem) => errorItem['message']);
+ if (invariantErrors.length > 0) {
+ // Plone invariant validation message.
+ erroMessage = invariantErrors.join(' - ');
+ } else {
+ // Error in specific field.
+ erroMessage = this.props.intl.formatMessage(messages.someErrors);
+ }
+ } else {
+ erroMessage = error;
+ }
+
this.setState({ error: error });
toast.error(
,
);
}
@@ -366,8 +388,8 @@ class Add extends Component {
}}
global
/>
- {this.state.isClient && (
-
+ {this.state.isClient &&
+ createPortal(
>
}
- />
-
- )}
- {visual && this.state.isClient && (
-
-
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
+ {visual &&
+ this.state.isClient &&
+ createPortal(, document.getElementById('sidebar'))}
);
diff --git a/packages/volto/src/components/manage/Add/Add.test.jsx b/packages/volto/src/components/manage/Add/Add.test.jsx
index 2a1ee56192..4d6500e885 100644
--- a/packages/volto/src/components/manage/Add/Add.test.jsx
+++ b/packages/volto/src/components/manage/Add/Add.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
import config from '@plone/volto/registry';
@@ -16,9 +16,8 @@ beforeAll(() => {
config.settings.loadables = {};
});
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() => ),
-}));
+jest.mock('../Toolbar/Toolbar', () => jest.fn(() => ));
+
jest.mock('../Form/Form', () => jest.fn(() => ));
describe('Add', () => {
@@ -39,13 +38,12 @@ describe('Add', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+ expect(container).toMatchSnapshot();
});
it('renders an add component', () => {
@@ -67,13 +65,13 @@ describe('Add', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
it('renders an add component with schema', () => {
@@ -106,12 +104,12 @@ describe('Add', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/Add/__snapshots__/Add.test.jsx.snap b/packages/volto/src/components/manage/Add/__snapshots__/Add.test.jsx.snap
index 00ad89f369..e0b4e6d306 100644
--- a/packages/volto/src/components/manage/Add/__snapshots__/Add.test.jsx.snap
+++ b/packages/volto/src/components/manage/Add/__snapshots__/Add.test.jsx.snap
@@ -1,7 +1,19 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`Add renders an add component 1`] = ``;
+exports[`Add renders an add component 1`] = `
+
+`;
-exports[`Add renders an add component with schema 1`] = ``;
+exports[`Add renders an add component with schema 1`] = `
+
+`;
-exports[`Add renders an empty add component 1`] = ``;
+exports[`Add renders an empty add component 1`] = `
+
+`;
diff --git a/packages/volto/src/components/manage/Aliases/Aliases.jsx b/packages/volto/src/components/manage/Aliases/Aliases.jsx
index 7554a9f20f..15af7a0326 100644
--- a/packages/volto/src/components/manage/Aliases/Aliases.jsx
+++ b/packages/volto/src/components/manage/Aliases/Aliases.jsx
@@ -8,7 +8,7 @@ import { Helmet } from '@plone/volto/helpers';
import { connect } from 'react-redux';
import { compose } from 'redux';
import { Link } from 'react-router-dom';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import {
Button,
Checkbox,
@@ -317,8 +317,8 @@ class Aliases extends Component {
- {this.state.isClient && (
-
+ {this.state.isClient &&
+ createPortal(
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
);
}
diff --git a/packages/volto/src/components/manage/Aliases/Aliases.test.jsx b/packages/volto/src/components/manage/Aliases/Aliases.test.jsx
index 31350c8d94..3ded78f739 100644
--- a/packages/volto/src/components/manage/Aliases/Aliases.test.jsx
+++ b/packages/volto/src/components/manage/Aliases/Aliases.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import { Provider } from 'react-intl-redux';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
@@ -9,9 +9,8 @@ import Aliases from './Aliases';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() => ),
-}));
+jest.mock('../Toolbar/Toolbar', () => jest.fn(() => ));
+
jest.mock('../Toolbar/More', () => jest.fn(() => ));
describe('Aliases', () => {
@@ -45,12 +44,13 @@ describe('Aliases', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/Aliases/__snapshots__/Aliases.test.jsx.snap b/packages/volto/src/components/manage/Aliases/__snapshots__/Aliases.test.jsx.snap
index 85b8d5c307..5bfa3e94a7 100644
--- a/packages/volto/src/components/manage/Aliases/__snapshots__/Aliases.test.jsx.snap
+++ b/packages/volto/src/components/manage/Aliases/__snapshots__/Aliases.test.jsx.snap
@@ -1,96 +1,97 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Aliases renders aliases object control 1`] = `
-
+
- URL Management for
-
- Blog
-
-
-
- Using this form, you can manage alternative urls for an item. This is an easy way to make an item available under two different URLs.
-
-
-
-
+
+
+ id="toolbar"
+ >
+
+
`;
diff --git a/packages/volto/src/components/manage/BlockChooser/BlockChooser.test.jsx b/packages/volto/src/components/manage/BlockChooser/BlockChooser.test.jsx
index 22fd5b5f48..447c20f61d 100644
--- a/packages/volto/src/components/manage/BlockChooser/BlockChooser.test.jsx
+++ b/packages/volto/src/components/manage/BlockChooser/BlockChooser.test.jsx
@@ -1,5 +1,4 @@
import React from 'react';
-import '@testing-library/jest-dom/extend-expect';
import { render, screen } from '@testing-library/react';
import { Provider } from 'react-intl-redux';
import configureStore from 'redux-mock-store';
diff --git a/packages/volto/src/components/manage/BlockChooser/BlockChooserButton.jsx b/packages/volto/src/components/manage/BlockChooser/BlockChooserButton.jsx
index d52389492d..136b413b70 100644
--- a/packages/volto/src/components/manage/BlockChooser/BlockChooserButton.jsx
+++ b/packages/volto/src/components/manage/BlockChooser/BlockChooserButton.jsx
@@ -7,7 +7,7 @@ import config from '@plone/volto/registry';
import { Button, Ref } from 'semantic-ui-react';
import { defineMessages, useIntl } from 'react-intl';
import { usePopper } from 'react-popper';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
const messages = defineMessages({
addBlock: {
@@ -115,8 +115,8 @@ const BlockChooserButton = (props) => {
/>
)}
- {addNewBlockOpened && (
-
+ {addNewBlockOpened &&
+ createPortal(
{
navRoot={navRoot}
contentType={contentType}
/>
-
-
- )}
+ ,
+ document.body,
+ )}
>
);
};
diff --git a/packages/volto/src/components/manage/BlockChooser/BlockChooserSearch.test.jsx b/packages/volto/src/components/manage/BlockChooser/BlockChooserSearch.test.jsx
index 7176bdda06..2bc70ced70 100644
--- a/packages/volto/src/components/manage/BlockChooser/BlockChooserSearch.test.jsx
+++ b/packages/volto/src/components/manage/BlockChooser/BlockChooserSearch.test.jsx
@@ -1,5 +1,4 @@
import React from 'react';
-import '@testing-library/jest-dom/extend-expect';
import { render } from '@testing-library/react';
import BlockChooserSearch from './BlockChooserSearch';
import { Provider } from 'react-intl-redux';
diff --git a/packages/volto/src/components/manage/BlockChooser/__snapshots__/BlockChooser.test.jsx.snap b/packages/volto/src/components/manage/BlockChooser/__snapshots__/BlockChooser.test.jsx.snap
index d61323c068..b0f6bc9a54 100644
--- a/packages/volto/src/components/manage/BlockChooser/__snapshots__/BlockChooser.test.jsx.snap
+++ b/packages/volto/src/components/manage/BlockChooser/__snapshots__/BlockChooser.test.jsx.snap
@@ -26,7 +26,6 @@ exports[`BlocksChooser Fallback BlockChooser component onMutateBlock 1`] = `
value=""
/>
-
Image
-
Listing
-
Video
-
@@ -142,7 +138,6 @@ exports[`BlocksChooser Fallback BlockChooser component onMutateBlock 1`] = `
style="height: 36px; width: auto; fill: currentColor;"
/>
Text
-
@@ -186,7 +181,6 @@ exports[`BlocksChooser Fallback BlockChooser component onMutateBlock 1`] = `
style="height: 36px; width: auto; fill: currentColor;"
/>
Image
-
Lead Image Field
-
Video
-
@@ -258,7 +250,6 @@ exports[`BlocksChooser Fallback BlockChooser component onMutateBlock 1`] = `
style="height: 36px; width: auto; fill: currentColor;"
/>
Listing
-
Table of Contents
-
Hero
-
Maps
-
HTML
-
Table
-
@@ -365,7 +351,6 @@ exports[`BlocksChooser renders a BlockChooser component 1`] = `
value=""
/>
-
Image
-
Listing
-
Video
-
@@ -481,7 +463,6 @@ exports[`BlocksChooser renders a BlockChooser component 1`] = `
style="height: 36px; width: auto; fill: currentColor;"
/>
Text
-
@@ -525,7 +506,6 @@ exports[`BlocksChooser renders a BlockChooser component 1`] = `
style="height: 36px; width: auto; fill: currentColor;"
/>
Image
-
Lead Image Field
-
Video
-
@@ -597,7 +575,6 @@ exports[`BlocksChooser renders a BlockChooser component 1`] = `
style="height: 36px; width: auto; fill: currentColor;"
/>
Listing
-
Table of Contents
-
Hero
-
Maps
-
HTML
-
Table
-
diff --git a/packages/volto/src/components/manage/Blocks/Block/Settings.test.jsx b/packages/volto/src/components/manage/Blocks/Block/Settings.test.jsx
index 57b7ea90ca..8cc2a7f301 100644
--- a/packages/volto/src/components/manage/Blocks/Block/Settings.test.jsx
+++ b/packages/volto/src/components/manage/Blocks/Block/Settings.test.jsx
@@ -1,7 +1,6 @@
import React from 'react';
import Settings from './Settings';
import { render } from '@testing-library/react';
-import '@testing-library/jest-dom/extend-expect';
import configureStore from 'redux-mock-store';
import config from '@plone/volto/registry';
import { Provider } from 'react-intl-redux';
diff --git a/packages/volto/src/components/manage/Blocks/Container/NewBlockAddButton.jsx b/packages/volto/src/components/manage/Blocks/Container/NewBlockAddButton.jsx
index 89076a0560..713f518e22 100644
--- a/packages/volto/src/components/manage/Blocks/Container/NewBlockAddButton.jsx
+++ b/packages/volto/src/components/manage/Blocks/Container/NewBlockAddButton.jsx
@@ -5,7 +5,7 @@ import { BlockChooser, Icon } from '@plone/volto/components';
import { useDetectClickOutside } from '@plone/volto/helpers';
import addSVG from '@plone/volto/icons/add.svg';
import { usePopper } from 'react-popper';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
const messages = defineMessages({
addBlock: {
@@ -59,24 +59,25 @@ const NewBlockAddButton = (props) => {
- {isOpenMenu ? (
-
-
-
-
-
- ) : null}
+ {isOpenMenu
+ ? createPortal(
+
+
+
,
+ document.body,
+ )
+ : null}
>
);
};
diff --git a/packages/volto/src/components/manage/Blocks/Image/Edit.jsx b/packages/volto/src/components/manage/Blocks/Image/Edit.jsx
index 4e272d53f9..37d2c04be6 100644
--- a/packages/volto/src/components/manage/Blocks/Image/Edit.jsx
+++ b/packages/volto/src/components/manage/Blocks/Image/Edit.jsx
@@ -283,19 +283,19 @@ class Edit extends Component {
data.image_scales
? undefined
: isInternalURL(data.url)
- ? // Backwards compat in the case that the block is storing the full server URL
- (() => {
- if (data.size === 'l')
+ ? // Backwards compat in the case that the block is storing the full server URL
+ (() => {
+ if (data.size === 'l')
+ return `${flattenToAppURL(data.url)}/@@images/image`;
+ if (data.size === 'm')
+ return `${flattenToAppURL(
+ data.url,
+ )}/@@images/image/preview`;
+ if (data.size === 's')
+ return `${flattenToAppURL(data.url)}/@@images/image/mini`;
return `${flattenToAppURL(data.url)}/@@images/image`;
- if (data.size === 'm')
- return `${flattenToAppURL(
- data.url,
- )}/@@images/image/preview`;
- if (data.size === 's')
- return `${flattenToAppURL(data.url)}/@@images/image/mini`;
- return `${flattenToAppURL(data.url)}/@@images/image`;
- })()
- : data.url
+ })()
+ : data.url
}
sizes={config.blocks.blocksConfig.image.getSizes(data)}
alt={data.alt || ''}
diff --git a/packages/volto/src/components/manage/Blocks/Image/ImageSidebar.jsx b/packages/volto/src/components/manage/Blocks/Image/ImageSidebar.jsx
index 02c18552a2..2df5c64190 100644
--- a/packages/volto/src/components/manage/Blocks/Image/ImageSidebar.jsx
+++ b/packages/volto/src/components/manage/Blocks/Image/ImageSidebar.jsx
@@ -61,9 +61,9 @@ const ImageSidebar = (props) => {
data.image_scales
? undefined
: isInternalURL(data.url)
- ? // Backwards compat in the case that the block is storing the full server URL
- `${flattenToAppURL(data.url)}/@@images/image/preview`
- : data.url
+ ? // Backwards compat in the case that the block is storing the full server URL
+ `${flattenToAppURL(data.url)}/@@images/image/preview`
+ : data.url
}
sizes="188px"
alt={intl.formatMessage(messages.preview)}
diff --git a/packages/volto/src/components/manage/Blocks/Image/View.jsx b/packages/volto/src/components/manage/Blocks/Image/View.jsx
index 6d0964ca90..e84dc64c14 100644
--- a/packages/volto/src/components/manage/Blocks/Image/View.jsx
+++ b/packages/volto/src/components/manage/Blocks/Image/View.jsx
@@ -51,21 +51,21 @@ export const View = ({ className, data, detached, properties, style }) => {
data.image_scales
? undefined
: isInternalURL(data.url)
- ? // Backwards compat in the case that the block is storing the full server URL
- (() => {
- if (data.size === 'l')
+ ? // Backwards compat in the case that the block is storing the full server URL
+ (() => {
+ if (data.size === 'l')
+ return `${flattenToAppURL(data.url)}/@@images/image`;
+ if (data.size === 'm')
+ return `${flattenToAppURL(
+ data.url,
+ )}/@@images/image/preview`;
+ if (data.size === 's')
+ return `${flattenToAppURL(
+ data.url,
+ )}/@@images/image/mini`;
return `${flattenToAppURL(data.url)}/@@images/image`;
- if (data.size === 'm')
- return `${flattenToAppURL(
- data.url,
- )}/@@images/image/preview`;
- if (data.size === 's')
- return `${flattenToAppURL(
- data.url,
- )}/@@images/image/mini`;
- return `${flattenToAppURL(data.url)}/@@images/image`;
- })()
- : data.url
+ })()
+ : data.url
}
sizes={config.blocks.blocksConfig.image.getSizes(data)}
alt={data.alt || ''}
diff --git a/packages/volto/src/components/manage/Blocks/Image/View.test.jsx b/packages/volto/src/components/manage/Blocks/Image/View.test.jsx
index fb073c2f21..81aac59bfd 100644
--- a/packages/volto/src/components/manage/Blocks/Image/View.test.jsx
+++ b/packages/volto/src/components/manage/Blocks/Image/View.test.jsx
@@ -1,5 +1,4 @@
import React from 'react';
-import '@testing-library/jest-dom/extend-expect';
import { render } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { Provider } from 'react-intl-redux';
diff --git a/packages/volto/src/components/manage/Blocks/Listing/withQuerystringResults.jsx b/packages/volto/src/components/manage/Blocks/Listing/withQuerystringResults.jsx
index 6dd025769b..08c7bdeb6b 100644
--- a/packages/volto/src/components/manage/Blocks/Listing/withQuerystringResults.jsx
+++ b/packages/volto/src/components/manage/Blocks/Listing/withQuerystringResults.jsx
@@ -67,19 +67,19 @@ export default function withQuerystringResults(WrappedComponent) {
const totalPages = showAsFolderListing
? Math.ceil(content.items_total / b_size)
: showAsQueryListing
- ? Math.ceil(querystringResults[subrequestID].total / b_size)
- : 0;
+ ? Math.ceil(querystringResults[subrequestID].total / b_size)
+ : 0;
const prevBatch = showAsFolderListing
? content.batching?.prev
: showAsQueryListing
- ? querystringResults[subrequestID].batching?.prev
- : null;
+ ? querystringResults[subrequestID].batching?.prev
+ : null;
const nextBatch = showAsFolderListing
? content.batching?.next
: showAsQueryListing
- ? querystringResults[subrequestID].batching?.next
- : null;
+ ? querystringResults[subrequestID].batching?.next
+ : null;
const isImageGallery =
(!data.variation && data.template === 'imageGallery') ||
diff --git a/packages/volto/src/components/manage/Blocks/Search/SelectStyling.jsx b/packages/volto/src/components/manage/Blocks/Search/SelectStyling.jsx
index 7aa2153c05..ec9d37e738 100644
--- a/packages/volto/src/components/manage/Blocks/Search/SelectStyling.jsx
+++ b/packages/volto/src/components/manage/Blocks/Search/SelectStyling.jsx
@@ -48,8 +48,8 @@ export const customSelectStyles = {
color: state.isSelected
? '#007bc1'
: state.isFocused
- ? '#4a4a4a'
- : 'inherit',
+ ? '#4a4a4a'
+ : 'inherit',
':active': {
backgroundColor: null,
},
diff --git a/packages/volto/src/components/manage/Blocks/Search/components/CheckboxFacet.jsx b/packages/volto/src/components/manage/Blocks/Search/components/CheckboxFacet.jsx
index f41acdd048..c4b86c1147 100644
--- a/packages/volto/src/components/manage/Blocks/Search/components/CheckboxFacet.jsx
+++ b/packages/volto/src/components/manage/Blocks/Search/components/CheckboxFacet.jsx
@@ -40,8 +40,8 @@ const CheckboxFacet = (props) => {
...(checked ? [value] : []),
]
: checked
- ? value
- : null,
+ ? value
+ : null,
)
}
/>
diff --git a/packages/volto/src/components/manage/Blocks/Search/components/Facets.jsx b/packages/volto/src/components/manage/Blocks/Search/components/Facets.jsx
index d19ea0ad7e..28ecc0c636 100644
--- a/packages/volto/src/components/manage/Blocks/Search/components/Facets.jsx
+++ b/packages/volto/src/components/manage/Blocks/Search/components/Facets.jsx
@@ -144,8 +144,8 @@ const Facets = (props) => {
? intl.formatMessage(messages.showFilters)
: intl.formatMessage(messages.moreFilters)
: advancedFilters === 2
- ? intl.formatMessage(messages.hideFilters)
- : intl.formatMessage(messages.lessFilters)}
+ ? intl.formatMessage(messages.hideFilters)
+ : intl.formatMessage(messages.lessFilters)}
)}
diff --git a/packages/volto/src/components/manage/Blocks/Search/components/SelectStyling.jsx b/packages/volto/src/components/manage/Blocks/Search/components/SelectStyling.jsx
index b2b1215710..bfc279b06f 100644
--- a/packages/volto/src/components/manage/Blocks/Search/components/SelectStyling.jsx
+++ b/packages/volto/src/components/manage/Blocks/Search/components/SelectStyling.jsx
@@ -48,8 +48,8 @@ export const customSelectStyles = {
color: state.isSelected
? '#007bc1'
: state.isFocused
- ? '#4a4a4a'
- : 'inherit',
+ ? '#4a4a4a'
+ : 'inherit',
':active': {
backgroundColor: null,
},
@@ -98,8 +98,8 @@ export const sortOnSelectStyles = {
color: state.isSelected
? '#007bc1'
: state.isFocused
- ? '#4a4a4a'
- : 'inherit',
+ ? '#4a4a4a'
+ : 'inherit',
':active': {
backgroundColor: null,
},
diff --git a/packages/volto/src/components/manage/Blocks/Table/Edit.jsx b/packages/volto/src/components/manage/Blocks/Table/Edit.jsx
index dee0234118..2f00b4ba77 100644
--- a/packages/volto/src/components/manage/Blocks/Table/Edit.jsx
+++ b/packages/volto/src/components/manage/Blocks/Table/Edit.jsx
@@ -8,7 +8,7 @@ import PropTypes from 'prop-types';
import { compose } from 'redux';
import { map, remove } from 'lodash';
import { Button, Segment, Table, Form } from 'semantic-ui-react';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import cx from 'classnames';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
@@ -670,8 +670,9 @@ class Edit extends Component {
)}
- {this.props.selected && this.state.isClient && (
-
+ {this.props.selected &&
+ this.state.isClient &&
+ createPortal(
-
- )}
+ ,
+ document.getElementById('sidebar-properties'),
+ )}
);
}
diff --git a/packages/volto/src/components/manage/Blocks/ToC/variations/__snapshots__/DefaultTocRenderer.test.jsx.snap b/packages/volto/src/components/manage/Blocks/ToC/variations/__snapshots__/DefaultTocRenderer.test.jsx.snap
index 59f2bde9df..b1037d169f 100644
--- a/packages/volto/src/components/manage/Blocks/ToC/variations/__snapshots__/DefaultTocRenderer.test.jsx.snap
+++ b/packages/volto/src/components/manage/Blocks/ToC/variations/__snapshots__/DefaultTocRenderer.test.jsx.snap
@@ -1,47 +1,44 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders a default toc renderer component 1`] = `
-Array [
- "",
- ,
-]
+ Test level 3
+
+
+
+
+
`;
diff --git a/packages/volto/src/components/manage/Contents/Contents.jsx b/packages/volto/src/components/manage/Contents/Contents.jsx
index c462592977..4641b74405 100644
--- a/packages/volto/src/components/manage/Contents/Contents.jsx
+++ b/packages/volto/src/components/manage/Contents/Contents.jsx
@@ -7,7 +7,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { compose } from 'redux';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import { Link } from 'react-router-dom';
import {
Button,
@@ -1989,9 +1989,9 @@ class Contents extends Component {
this.state.selected.length === 0
? checkboxUncheckedSVG
: this.state.selected.length ===
- this.state.items.length
- ? checkboxCheckedSVG
- : checkboxIndeterminateSVG
+ this.state.items.length
+ ? checkboxCheckedSVG
+ : checkboxIndeterminateSVG
}
color={
this.state.selected.length > 0
@@ -2159,8 +2159,8 @@ class Contents extends Component {
- {this.state.isClient && (
-
+ {this.state.isClient &&
+ createPortal(
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
) : (
diff --git a/packages/volto/src/components/manage/Contents/Contents.test.jsx b/packages/volto/src/components/manage/Contents/Contents.test.jsx
index 48f01c1562..87982d36d4 100644
--- a/packages/volto/src/components/manage/Contents/Contents.test.jsx
+++ b/packages/volto/src/components/manage/Contents/Contents.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
import { MemoryRouter } from 'react-router-dom';
@@ -14,9 +14,8 @@ beforeAll(
await require('@plone/volto/helpers/Loadable/Loadable').__setLoadables(),
);
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() => ),
-}));
+jest.mock('../Toolbar/Toolbar', () => jest.fn(() => ));
+
jest.mock('../../theme/Pagination/Pagination', () =>
jest.fn(() => ),
);
@@ -91,14 +90,15 @@ describe('Contents', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/Contents/ContentsPropertiesModal.test.jsx b/packages/volto/src/components/manage/Contents/ContentsPropertiesModal.test.jsx
index dd4a2b65bf..126e695026 100644
--- a/packages/volto/src/components/manage/Contents/ContentsPropertiesModal.test.jsx
+++ b/packages/volto/src/components/manage/Contents/ContentsPropertiesModal.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
@@ -23,7 +23,7 @@ describe('ContentsPropertiesModal', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
{
/>
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/Contents/__snapshots__/Contents.test.jsx.snap b/packages/volto/src/components/manage/Contents/__snapshots__/Contents.test.jsx.snap
index a348d4c3db..2c6dd5c65f 100644
--- a/packages/volto/src/components/manage/Contents/__snapshots__/Contents.test.jsx.snap
+++ b/packages/volto/src/components/manage/Contents/__snapshots__/Contents.test.jsx.snap
@@ -1,1067 +1,600 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Contents renders a folder contents view component 1`] = `
-
+
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
+
+ Home
+
- Select columns to show
+ /
+ Blog
+
+
+
+
+
-
-
-
-
-
-
-
- |
-
-
- |
-
- Title
- |
-
- Actions
- |
-
-
-
-
-
-
+
+
+ |
+
+
+ |
+
+ Title
+ |
+
+ Actions
+ |
+
+
+
+
+
+ class="contents-pagination"
+ >
+
+
-
-
-
+
+
+
+
+
diff --git a/packages/volto/src/components/manage/Contents/__snapshots__/ContentsPropertiesModal.test.jsx.snap b/packages/volto/src/components/manage/Contents/__snapshots__/ContentsPropertiesModal.test.jsx.snap
index 3c382ace86..c460a95329 100644
--- a/packages/volto/src/components/manage/Contents/__snapshots__/ContentsPropertiesModal.test.jsx.snap
+++ b/packages/volto/src/components/manage/Contents/__snapshots__/ContentsPropertiesModal.test.jsx.snap
@@ -1,7 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ContentsPropertiesModal renders a contents properties modal component 1`] = `
-
+
`;
diff --git a/packages/volto/src/components/manage/Controlpanels/AddonsControlpanel.jsx b/packages/volto/src/components/manage/Controlpanels/AddonsControlpanel.jsx
index 1248c90cbf..66e1bc7a06 100644
--- a/packages/volto/src/components/manage/Controlpanels/AddonsControlpanel.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/AddonsControlpanel.jsx
@@ -7,7 +7,7 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { compose } from 'redux';
import { Link } from 'react-router-dom';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import {
Accordion,
Button,
@@ -550,8 +550,8 @@ class AddonsControlpanel extends Component {
)}
- {this.state.isClient && (
-
+ {this.state.isClient &&
+ createPortal(
>
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
);
}
diff --git a/packages/volto/src/components/manage/Controlpanels/AddonsControlpanel.test.jsx b/packages/volto/src/components/manage/Controlpanels/AddonsControlpanel.test.jsx
index 460b69f85e..67ce84f2ac 100644
--- a/packages/volto/src/components/manage/Controlpanels/AddonsControlpanel.test.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/AddonsControlpanel.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
@@ -7,9 +7,7 @@ import AddonsControlpanel from './AddonsControlpanel';
const mockStore = configureStore();
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() =>
),
-}));
+jest.mock('../Toolbar/Toolbar', () => jest.fn(() =>
));
describe('AddonsControlpanel', () => {
it('renders an addon control component', () => {
@@ -57,12 +55,13 @@ describe('AddonsControlpanel', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/Controlpanels/Aliases.jsx b/packages/volto/src/components/manage/Controlpanels/Aliases.jsx
index 6a927d7ae5..6b985ea3d3 100644
--- a/packages/volto/src/components/manage/Controlpanels/Aliases.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/Aliases.jsx
@@ -10,7 +10,7 @@ import { compose } from 'redux';
import { Link } from 'react-router-dom';
import { getBaseUrl, getParentUrl, Helmet } from '@plone/volto/helpers';
import { removeAliases, addAliases, getAliases } from '@plone/volto/actions';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import {
Container,
Button,
@@ -652,8 +652,8 @@ class Aliases extends Component {
- {this.state.isClient && (
-
+ {this.state.isClient &&
+ createPortal(
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
);
}
diff --git a/packages/volto/src/components/manage/Controlpanels/Aliases.test.jsx b/packages/volto/src/components/manage/Controlpanels/Aliases.test.jsx
index 661611753f..c6a9a18010 100644
--- a/packages/volto/src/components/manage/Controlpanels/Aliases.test.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/Aliases.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { Provider } from 'react-intl-redux';
@@ -10,9 +10,7 @@ import { MemoryRouter } from 'react-router';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() => ),
-}));
+jest.mock('../Toolbar/Toolbar', () => jest.fn(() => ));
describe('Aliases', () => {
it('renders an aliases control component', () => {
@@ -59,14 +57,15 @@ describe('Aliases', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/Controlpanels/ContentType.jsx b/packages/volto/src/components/manage/Controlpanels/ContentType.jsx
index 097dd488ff..6b8edbb9e6 100644
--- a/packages/volto/src/components/manage/Controlpanels/ContentType.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/ContentType.jsx
@@ -8,7 +8,7 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { compose } from 'redux';
import { getParentUrl } from '@plone/volto/helpers';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import { Button, Header } from 'semantic-ui-react';
import { defineMessages, injectIntl } from 'react-intl';
import { toast } from 'react-toastify';
@@ -210,8 +210,8 @@ class ContentType extends Component {
hideActions
loading={this.props.cpanelRequest.update.loading}
/>
- {this.state.isClient && (
-
+ {this.state.isClient &&
+ createPortal(
>
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
);
}
diff --git a/packages/volto/src/components/manage/Controlpanels/ContentType.test.jsx b/packages/volto/src/components/manage/Controlpanels/ContentType.test.jsx
index 63aa9fcd68..159c303936 100644
--- a/packages/volto/src/components/manage/Controlpanels/ContentType.test.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/ContentType.test.jsx
@@ -1,16 +1,14 @@
import React from 'react';
-import renderer from 'react-test-renderer';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
import { MemoryRouter, Route } from 'react-router-dom';
+import { render } from '@testing-library/react';
import ContentType from './ContentType';
const mockStore = configureStore();
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() => ),
-}));
+jest.mock('../Toolbar/Toolbar', () => jest.fn(() => ));
jest.mock('../Form/Form', () => jest.fn(() => ));
describe('ContentType', () => {
@@ -35,7 +33,8 @@ describe('ContentType', () => {
messages: {},
},
});
- const component = renderer.create(
+
+ const { container } = render(
{
path={'/controlpanel/dexterity-types/:id'}
component={ContentType}
/>
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/Controlpanels/ContentTypeLayout.jsx b/packages/volto/src/components/manage/Controlpanels/ContentTypeLayout.jsx
index b2754d90ba..3e5cd1a388 100644
--- a/packages/volto/src/components/manage/Controlpanels/ContentTypeLayout.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/ContentTypeLayout.jsx
@@ -14,7 +14,7 @@ import {
getBlocksFieldname,
getBlocksLayoutFieldname,
} from '@plone/volto/helpers';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import { Button, Segment } from 'semantic-ui-react';
import { toast } from 'react-toastify';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
@@ -343,26 +343,30 @@ class ContentTypeLayout extends Component {
content={this.props.intl.formatMessage(messages.enable)}
/>
-
-
- this.onCancel()}>
-
-
- >
- }
- />
-
+ {this.state.isClient &&
+ createPortal(
+
+ this.onCancel()}
+ >
+
+
+ >
+ }
+ />,
+ document.getElementById('toolbar'),
+ )}
>
);
}
@@ -392,26 +396,30 @@ class ContentTypeLayout extends Component {
content={this.props.intl.formatMessage(messages.enable)}
/>
-
-
- this.onCancel()}>
-
-
- >
- }
- />
-
+ {this.state.isClient &&
+ createPortal(
+
+ this.onCancel()}
+ >
+
+
+ >
+ }
+ />,
+ document.getElementById('toolbar'),
+ )}
>
);
}
@@ -449,50 +457,50 @@ class ContentTypeLayout extends Component {
visual={this.state.visual}
hideActions
/>
-
-
-
-
-
-
-
- >
- }
- />
-
+ {this.state.isClient &&
+ createPortal(
+ ,
+ document.getElementById('sidebar'),
+ )}
+ {this.state.isClient &&
+ createPortal(
+
+
+
+ >
+ }
+ />,
+ document.getElementById('toolbar'),
+ )}
);
}
diff --git a/packages/volto/src/components/manage/Controlpanels/ContentTypeLayout.test.jsx b/packages/volto/src/components/manage/Controlpanels/ContentTypeLayout.test.jsx
index 70127b8477..aee7d78ec6 100644
--- a/packages/volto/src/components/manage/Controlpanels/ContentTypeLayout.test.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/ContentTypeLayout.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
import { MemoryRouter, Route } from 'react-router-dom';
@@ -8,9 +8,8 @@ import ContentTypeLayout from './ContentTypeLayout';
const mockStore = configureStore();
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() => ),
-}));
+jest.mock('../Toolbar/Toolbar', () => jest.fn(() => ));
+
jest.mock('../Form/Form', () => jest.fn(() => ));
describe('ContentTypeLayout', () => {
@@ -38,7 +37,7 @@ describe('ContentTypeLayout', () => {
schema: null,
},
});
- const component = renderer.create(
+ const { container } = render(
{
path={'/controlpanel/dexterity-types/:id/layout'}
component={ContentTypeLayout}
/>
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/Controlpanels/ContentTypeSchema.jsx b/packages/volto/src/components/manage/Controlpanels/ContentTypeSchema.jsx
index b8ebf1dea0..ab6888140b 100644
--- a/packages/volto/src/components/manage/Controlpanels/ContentTypeSchema.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/ContentTypeSchema.jsx
@@ -12,7 +12,7 @@ import saveSVG from '@plone/volto/icons/save.svg';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { defineMessages, injectIntl } from 'react-intl';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import { connect } from 'react-redux';
import { toast } from 'react-toastify';
import { compose } from 'redux';
@@ -283,8 +283,8 @@ class ContentTypeSchema extends Component {
onCancel={this.onCancel}
hideActions
/>
- {this.state.isClient && (
-
+ {this.state.isClient &&
+ createPortal(
>
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
);
}
diff --git a/packages/volto/src/components/manage/Controlpanels/ContentTypes.jsx b/packages/volto/src/components/manage/Controlpanels/ContentTypes.jsx
index 4c386c4bce..4759ed6fb2 100644
--- a/packages/volto/src/components/manage/Controlpanels/ContentTypes.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/ContentTypes.jsx
@@ -9,7 +9,7 @@ import { connect } from 'react-redux';
import { compose } from 'redux';
import { Link } from 'react-router-dom';
import { getParentUrl } from '@plone/volto/helpers';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import { last } from 'lodash';
import { Confirm, Container, Table, Button, Header } from 'semantic-ui-react';
import { toast } from 'react-toastify';
@@ -457,8 +457,8 @@ class ContentTypes extends Component {
- {this.state.isClient && (
-
+ {this.state.isClient &&
+ createPortal(
>
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
);
}
diff --git a/packages/volto/src/components/manage/Controlpanels/ContentTypes.test.jsx b/packages/volto/src/components/manage/Controlpanels/ContentTypes.test.jsx
index 876aed77df..5289835b93 100644
--- a/packages/volto/src/components/manage/Controlpanels/ContentTypes.test.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/ContentTypes.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
import { MemoryRouter, Route } from 'react-router-dom';
@@ -8,9 +8,8 @@ import ContentTypes from './ContentTypes';
const mockStore = configureStore();
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() => ),
-}));
+jest.mock('../Toolbar/Toolbar', () => jest.fn(() => ));
+
jest.mock('../Form/Form', () => jest.fn(() => ));
describe('ContentTypes', () => {
@@ -60,14 +59,15 @@ describe('ContentTypes', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/Controlpanels/Controlpanel.jsx b/packages/volto/src/components/manage/Controlpanels/Controlpanel.jsx
index a6fc83740f..a55477d787 100644
--- a/packages/volto/src/components/manage/Controlpanels/Controlpanel.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/Controlpanel.jsx
@@ -9,7 +9,7 @@ import { connect } from 'react-redux';
import { compose } from 'redux';
import { withRouter } from 'react-router-dom';
import { Helmet } from '@plone/volto/helpers';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import { Button, Container } from 'semantic-ui-react';
import { defineMessages, injectIntl } from 'react-intl';
import { toast } from 'react-toastify';
@@ -167,8 +167,8 @@ class Controlpanel extends Component {
loading={this.props.updateRequest.loading}
/>
- {this.state.isClient && (
-
+ {this.state.isClient &&
+ createPortal(
>
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
);
}
diff --git a/packages/volto/src/components/manage/Controlpanels/Controlpanel.test.jsx b/packages/volto/src/components/manage/Controlpanels/Controlpanel.test.jsx
index d03d87e2d6..1ee6a98c11 100644
--- a/packages/volto/src/components/manage/Controlpanels/Controlpanel.test.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/Controlpanel.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
import { MemoryRouter, Route } from 'react-router-dom';
@@ -8,9 +8,8 @@ import Controlpanel from './Controlpanel';
const mockStore = configureStore();
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() => ),
-}));
+jest.mock('../Toolbar/Toolbar', () => jest.fn(() => ));
+
jest.mock('../Form/Form', () => jest.fn(() => ));
describe('Controlpanel', () => {
@@ -36,14 +35,15 @@ describe('Controlpanel', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/Controlpanels/Controlpanels.jsx b/packages/volto/src/components/manage/Controlpanels/Controlpanels.jsx
index 263be3b02f..d2751c0142 100644
--- a/packages/volto/src/components/manage/Controlpanels/Controlpanels.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/Controlpanels.jsx
@@ -8,7 +8,7 @@ import { concat, filter, last, map, sortBy, uniqBy } from 'lodash';
import PropTypes from 'prop-types';
import { useEffect, useState } from 'react';
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { compose } from 'redux';
@@ -260,8 +260,8 @@ function Controlpanels({
- {isClient && (
-
+ {isClient &&
+ createPortal(
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
);
}
diff --git a/packages/volto/src/components/manage/Controlpanels/Controlpanels.test.jsx b/packages/volto/src/components/manage/Controlpanels/Controlpanels.test.jsx
index 23c9d83392..10ce403043 100644
--- a/packages/volto/src/components/manage/Controlpanels/Controlpanels.test.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/Controlpanels.test.jsx
@@ -1,7 +1,7 @@
import React from 'react';
import { Provider } from 'react-intl-redux';
import { MemoryRouter } from 'react-router-dom';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import configureStore from 'redux-mock-store';
import config from '@plone/volto/registry';
@@ -9,9 +9,7 @@ import Controlpanels from './Controlpanels';
const mockStore = configureStore();
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() => ),
-}));
+jest.mock('../Toolbar/Toolbar', () => jest.fn(() => ));
jest.mock('./VersionOverview', () =>
jest.fn(() => ),
@@ -73,15 +71,16 @@ describe('Controlpanels', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
it('renders an additional control panel', () => {
@@ -127,14 +126,15 @@ describe('Controlpanels', () => {
component: FooComponent,
},
];
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/Controlpanels/DatabaseInformation.jsx b/packages/volto/src/components/manage/Controlpanels/DatabaseInformation.jsx
index ec17c9b8ce..04b29655ad 100644
--- a/packages/volto/src/components/manage/Controlpanels/DatabaseInformation.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/DatabaseInformation.jsx
@@ -7,7 +7,7 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { compose } from 'redux';
import { Link } from 'react-router-dom';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import { Container, Divider, Message, Segment, Table } from 'semantic-ui-react';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
@@ -216,8 +216,8 @@ class DatabaseInformation extends Component {
- {this.state.isClient && (
-
+ {this.state.isClient &&
+ createPortal(
>
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
) : null;
}
diff --git a/packages/volto/src/components/manage/Controlpanels/Groups/GroupsControlpanel.jsx b/packages/volto/src/components/manage/Controlpanels/Groups/GroupsControlpanel.jsx
index fb4a4aac0e..97fcb5cb27 100644
--- a/packages/volto/src/components/manage/Controlpanels/Groups/GroupsControlpanel.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/Groups/GroupsControlpanel.jsx
@@ -37,7 +37,7 @@ import { find, map, pull } from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { FormattedMessage, injectIntl } from 'react-intl';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import { connect } from 'react-redux';
import { toast } from 'react-toastify';
@@ -279,16 +279,16 @@ class GroupsControlpanel extends Component {
entry.id === name && !entry.roles.includes(value)
? [...entry.roles, value]
: entry.id !== name
- ? entry.roles
- : pull(entry.roles, value),
+ ? entry.roles
+ : pull(entry.roles, value),
})),
authenticatedRole:
name === 'AuthenticatedUsers' &&
!prevState.authenticatedRole.includes(value)
? [...prevState.authenticatedRole, value]
: name !== 'AuthenticatedUsers'
- ? prevState.authenticatedRole
- : pull(prevState.authenticatedRole, value),
+ ? prevState.authenticatedRole
+ : pull(prevState.authenticatedRole, value),
}));
}
/**
@@ -598,8 +598,8 @@ class GroupsControlpanel extends Component {
- {this.state.isClient && (
-
+ {this.state.isClient &&
+ createPortal(
>
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
);
}
diff --git a/packages/volto/src/components/manage/Controlpanels/Groups/GroupsControlpanel.test.jsx b/packages/volto/src/components/manage/Controlpanels/Groups/GroupsControlpanel.test.jsx
index f520c37a07..faf5c4f04e 100644
--- a/packages/volto/src/components/manage/Controlpanels/Groups/GroupsControlpanel.test.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/Groups/GroupsControlpanel.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
import jwt from 'jsonwebtoken';
@@ -7,9 +7,8 @@ import jwt from 'jsonwebtoken';
import GroupsControlpanel from './GroupsControlpanel';
const mockStore = configureStore();
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() => ),
-}));
+jest.mock('../../Toolbar/Toolbar', () => jest.fn(() => ));
+
describe('UsersControlpanel', () => {
it('renders a user control component', () => {
const store = mockStore({
@@ -37,12 +36,13 @@ describe('UsersControlpanel', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/Controlpanels/Groups/__snapshots__/GroupsControlpanel.test.jsx.snap b/packages/volto/src/components/manage/Controlpanels/Groups/__snapshots__/GroupsControlpanel.test.jsx.snap
index 6a7e41b64b..36ba594690 100644
--- a/packages/volto/src/components/manage/Controlpanels/Groups/__snapshots__/GroupsControlpanel.test.jsx.snap
+++ b/packages/volto/src/components/manage/Controlpanels/Groups/__snapshots__/GroupsControlpanel.test.jsx.snap
@@ -1,128 +1,121 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`UsersControlpanel renders a user control component 1`] = `
-
+
-
- Groups
-
-
- Groups are logical collections of users, such as departments and business units. Groups are not directly related to permissions on a global level, you normally use Roles for that - and let certain Groups have a particular role. The symbol
-
+ class="container"
+ />
-
-
-
-
+
+ Groupname
+ |
+
+ Actions
+ |
+
+
+
+
+
-
-
+
+
+ id="toolbar"
+ >
+
+
`;
diff --git a/packages/volto/src/components/manage/Controlpanels/ModerateComments.jsx b/packages/volto/src/components/manage/Controlpanels/ModerateComments.jsx
index 182ce40ae0..d5036a50d2 100644
--- a/packages/volto/src/components/manage/Controlpanels/ModerateComments.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/ModerateComments.jsx
@@ -9,7 +9,7 @@ import { connect } from 'react-redux';
import { compose } from 'redux';
import { Link } from 'react-router-dom';
import { getParentUrl, Helmet } from '@plone/volto/helpers';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import { Container, Button, Table } from 'semantic-ui-react';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
@@ -267,8 +267,8 @@ class ModerateComments extends Component {
- {this.state.isClient && (
-
+ {this.state.isClient &&
+ createPortal(
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
);
}
diff --git a/packages/volto/src/components/manage/Controlpanels/ModerateComments.test.jsx b/packages/volto/src/components/manage/Controlpanels/ModerateComments.test.jsx
index ff5f785dcd..d5c79af439 100644
--- a/packages/volto/src/components/manage/Controlpanels/ModerateComments.test.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/ModerateComments.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
@@ -7,9 +7,8 @@ import ModerateComments from './ModerateComments';
const mockStore = configureStore();
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() => ),
-}));
+jest.mock('../Toolbar/Toolbar', () => jest.fn(() => ));
+
jest.mock('../../theme/Comments/CommentEditModal', () =>
jest.fn(() => ),
);
@@ -31,12 +30,13 @@ describe('ModerateComments', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/Controlpanels/Relations/Relations.jsx b/packages/volto/src/components/manage/Controlpanels/Relations/Relations.jsx
index f71caa058d..73c56efa41 100644
--- a/packages/volto/src/components/manage/Controlpanels/Relations/Relations.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/Relations/Relations.jsx
@@ -1,10 +1,10 @@
/**
* Relations Control Panel
*/
-import React, { useEffect } from 'react';
+import React, { useEffect, useState } from 'react';
import { find } from 'lodash';
import { useSelector } from 'react-redux';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import { useHistory } from 'react-router';
import { Link, useLocation } from 'react-router-dom';
import { FormattedMessage, useIntl } from 'react-intl';
@@ -23,6 +23,12 @@ const RelationsControlPanel = () => {
const location = useLocation();
const dispatch = useDispatch();
+ const [isClient, setIsClient] = useState(false);
+
+ useEffect(() => {
+ setIsClient(true);
+ }, []);
+
const brokenRelations = useSelector(
(state) => state.relations?.stats?.data?.broken,
);
@@ -88,8 +94,8 @@ const RelationsControlPanel = () => {
)}
- {__CLIENT__ && (
-
+ {isClient &&
+ createPortal(
{
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
>
);
};
diff --git a/packages/volto/src/components/manage/Controlpanels/Rules/AddRule.jsx b/packages/volto/src/components/manage/Controlpanels/Rules/AddRule.jsx
index 31df68dbc0..85aba340be 100644
--- a/packages/volto/src/components/manage/Controlpanels/Rules/AddRule.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/Rules/AddRule.jsx
@@ -9,7 +9,7 @@ import { connect } from 'react-redux';
import { compose } from 'redux';
import { Link } from 'react-router-dom';
import { getBaseUrl, getParentUrl, Helmet } from '@plone/volto/helpers';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import {
Button,
Checkbox,
@@ -331,8 +331,8 @@ class AddRule extends Component {
- {this.state.isClient && (
-
+ {this.state.isClient &&
+ createPortal(
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
);
}
diff --git a/packages/volto/src/components/manage/Controlpanels/Rules/AddRule.test.jsx b/packages/volto/src/components/manage/Controlpanels/Rules/AddRule.test.jsx
index 982d8798f6..5f83ebaef8 100644
--- a/packages/volto/src/components/manage/Controlpanels/Rules/AddRule.test.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/Rules/AddRule.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import { Provider } from 'react-intl-redux';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
@@ -9,9 +9,7 @@ import AddRule from './AddRule';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() => ),
-}));
+jest.mock('../../Toolbar/Toolbar', () => jest.fn(() => ));
describe('AddRule', () => {
it('renders rules add interface', () => {
@@ -21,12 +19,13 @@ describe('AddRule', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/Controlpanels/Rules/ConfigureRule.jsx b/packages/volto/src/components/manage/Controlpanels/Rules/ConfigureRule.jsx
index 4f92673898..65887da7f3 100644
--- a/packages/volto/src/components/manage/Controlpanels/Rules/ConfigureRule.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/Rules/ConfigureRule.jsx
@@ -9,7 +9,7 @@ import { connect } from 'react-redux';
import { compose } from 'redux';
import { Link } from 'react-router-dom';
import { getBaseUrl, getParentUrl, Helmet } from '@plone/volto/helpers';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import {
Button,
Card,
@@ -847,8 +847,8 @@ class ConfigureRule extends Component {
action="edit"
/>
)}
- {this.state.isClient && (
-
+ {this.state.isClient &&
+ createPortal(
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
);
}
diff --git a/packages/volto/src/components/manage/Controlpanels/Rules/ConfigureRule.test.jsx b/packages/volto/src/components/manage/Controlpanels/Rules/ConfigureRule.test.jsx
index ed9babcd14..9516fcc64b 100644
--- a/packages/volto/src/components/manage/Controlpanels/Rules/ConfigureRule.test.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/Rules/ConfigureRule.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import { Provider } from 'react-intl-redux';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
@@ -9,9 +9,7 @@ import ConfigureRule from './ConfigureRule';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() => ),
-}));
+jest.mock('../../Toolbar/Toolbar', () => jest.fn(() => ));
describe('ConfigureRule', () => {
it('renders rules configure interface', () => {
@@ -21,14 +19,15 @@ describe('ConfigureRule', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/Controlpanels/Rules/EditRule.jsx b/packages/volto/src/components/manage/Controlpanels/Rules/EditRule.jsx
index 025e53993d..b8cd8a9124 100644
--- a/packages/volto/src/components/manage/Controlpanels/Rules/EditRule.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/Rules/EditRule.jsx
@@ -9,7 +9,7 @@ import { connect } from 'react-redux';
import { compose } from 'redux';
import { Link } from 'react-router-dom';
import { getParentUrl, Helmet, getBaseUrl } from '@plone/volto/helpers';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import {
Button,
Checkbox,
@@ -367,8 +367,8 @@ class EditRule extends Component {
- {this.state.isClient && (
-
+ {this.state.isClient &&
+ createPortal(
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
);
}
diff --git a/packages/volto/src/components/manage/Controlpanels/Rules/EditRule.test.jsx b/packages/volto/src/components/manage/Controlpanels/Rules/EditRule.test.jsx
index b62949c0bc..100aa9a64b 100644
--- a/packages/volto/src/components/manage/Controlpanels/Rules/EditRule.test.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/Rules/EditRule.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import { Provider } from 'react-intl-redux';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
@@ -9,9 +9,7 @@ import EditRule from './EditRule';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() => ),
-}));
+jest.mock('../../Toolbar/Toolbar', () => jest.fn(() => ));
describe('EditRule', () => {
it('renders rules edit interface', () => {
@@ -21,12 +19,13 @@ describe('EditRule', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/Controlpanels/Rules/Rules.jsx b/packages/volto/src/components/manage/Controlpanels/Rules/Rules.jsx
index f8060a4ca1..0f3545d919 100644
--- a/packages/volto/src/components/manage/Controlpanels/Rules/Rules.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/Rules/Rules.jsx
@@ -9,7 +9,7 @@ import { connect } from 'react-redux';
import { compose } from 'redux';
import { Link } from 'react-router-dom';
import { getBaseUrl, getParentUrl, Helmet } from '@plone/volto/helpers';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import {
Button,
Checkbox,
@@ -420,8 +420,8 @@ class Rules extends Component {
- {this.state.isClient && (
-
+ {this.state.isClient &&
+ createPortal(
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
);
}
diff --git a/packages/volto/src/components/manage/Controlpanels/Rules/Rules.test.jsx b/packages/volto/src/components/manage/Controlpanels/Rules/Rules.test.jsx
index fe86ede91e..2a2813b7c6 100644
--- a/packages/volto/src/components/manage/Controlpanels/Rules/Rules.test.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/Rules/Rules.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import { Provider } from 'react-intl-redux';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
@@ -9,9 +9,7 @@ import Rules from './Rules';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() => ),
-}));
+jest.mock('../../Toolbar/Toolbar', () => jest.fn(() => ));
describe('Rules', () => {
it('renders rules control panel control', () => {
@@ -64,12 +62,13 @@ describe('Rules', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/Controlpanels/Rules/__snapshots__/AddRule.test.jsx.snap b/packages/volto/src/components/manage/Controlpanels/Rules/__snapshots__/AddRule.test.jsx.snap
index be2bd126a8..b85d427f10 100644
--- a/packages/volto/src/components/manage/Controlpanels/Rules/__snapshots__/AddRule.test.jsx.snap
+++ b/packages/volto/src/components/manage/Controlpanels/Rules/__snapshots__/AddRule.test.jsx.snap
@@ -1,242 +1,197 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AddRule renders rules add interface 1`] = `
-
+
-
-
- Add Rule
-
-
- Use the form below to define the new content rule
-
-
-
-
-
-
+
+
+
+
+
+ Whether or not execution of further rules should stop after this rule is executed
+
-
- Whether or not execution of further rules should stop after this rule is executed
-
-
-
-
-
+
+
+
+
+
+ Whether or not other rules should be triggered by the actions launched by this rule. Activate this only if you are sure this won't create infinite loops
+
-
- Whether or not other rules should be triggered by the actions launched by this rule. Activate this only if you are sure this won't create infinite loops
-
-
-
-
-
+
+
+
+
-
-
-
+
+
+ id="toolbar"
+ >
+
+
`;
diff --git a/packages/volto/src/components/manage/Controlpanels/Rules/__snapshots__/ConfigureRule.test.jsx.snap b/packages/volto/src/components/manage/Controlpanels/Rules/__snapshots__/ConfigureRule.test.jsx.snap
index f2a98c51aa..25bbc75cdb 100644
--- a/packages/volto/src/components/manage/Controlpanels/Rules/__snapshots__/ConfigureRule.test.jsx.snap
+++ b/packages/volto/src/components/manage/Controlpanels/Rules/__snapshots__/ConfigureRule.test.jsx.snap
@@ -1,184 +1,152 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ConfigureRule renders rules configure interface 1`] = `
-
+
-
-
- Configure Content Rule:
-
-
-
-
-
- Rules execute when a triggering event occurs. Rule actions will only be invoked if all the rule's conditions are met. You can add new actions and conditions using the buttons below.
-
-
+ Configure Content Rule:
+
+
+
+ Rules execute when a triggering event occurs. Rule actions will only be invoked if all the rule's conditions are met. You can add new actions and conditions using the buttons below.
+
+
-
- If all of the following conditions are met:
-
-
- Condition:
+
+ If all of the following conditions are met:
+
+ Condition:
+
- Select condition
+
+ Select condition
+
+
+
-
-
+
-
-
-
-
- Perform the following actions:
-
-
- Action:
+
+ Perform the following actions:
+
+ Action:
+
- Select action
+
+ Select action
+
+
+
-
-
+
-
-
-
-
- Assignments
-
- This rule is assigned to the following locations:
+
+
+ Assignments
+
+ This rule is assigned to the following locations:
+
-
-
+
+
-
-
-
-
+ id="toolbar"
+ >
+
+
`;
diff --git a/packages/volto/src/components/manage/Controlpanels/Rules/__snapshots__/EditRule.test.jsx.snap b/packages/volto/src/components/manage/Controlpanels/Rules/__snapshots__/EditRule.test.jsx.snap
index 4920f93180..931b391418 100644
--- a/packages/volto/src/components/manage/Controlpanels/Rules/__snapshots__/EditRule.test.jsx.snap
+++ b/packages/volto/src/components/manage/Controlpanels/Rules/__snapshots__/EditRule.test.jsx.snap
@@ -1,242 +1,197 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EditRule renders rules edit interface 1`] = `
-
+
-
-
- Edit Rule
-
-
- Use the form below to define the new content rule
-
-
-
-
+
+ Use the form below to define the new content rule
+
+
+
- Title
- -
- Please set a descriptive title for the rule.
+
+ Title
+ -
+ Please set a descriptive title for the rule.
+
-
-
- Description
- -
- Enter a short description of the rule and its purpose.
+
+ Description
+ -
+ Enter a short description of the rule and its purpose.
+
-
-
- Triggering event
- -
- The rule will execute when the following event occurs.
+
+ Triggering event
+ -
+ The rule will execute when the following event occurs.
+
-
-
-
-
+
+
-
-
+
+
+
+
+
+ Whether or not the rule is currently enabled
+
-
- Whether or not the rule is currently enabled
-
-
-
-
-
+
+
+
+
+
+ Whether or not execution of further rules should stop after this rule is executed
+
-
- Whether or not execution of further rules should stop after this rule is executed
-
-
-
-
-
+
+
+
+
+
+ Whether or not other rules should be triggered by the actions launched by this rule. Activate this only if you are sure this won't create infinite loops
+
-
- Whether or not other rules should be triggered by the actions launched by this rule. Activate this only if you are sure this won't create infinite loops
-
-
-
-
-
+
+
+
+
-
-
-
+
+
+ id="toolbar"
+ >
+
+
`;
diff --git a/packages/volto/src/components/manage/Controlpanels/Rules/__snapshots__/Rules.test.jsx.snap b/packages/volto/src/components/manage/Controlpanels/Rules/__snapshots__/Rules.test.jsx.snap
index 6badfd410f..dbdbfaefb5 100644
--- a/packages/volto/src/components/manage/Controlpanels/Rules/__snapshots__/Rules.test.jsx.snap
+++ b/packages/volto/src/components/manage/Controlpanels/Rules/__snapshots__/Rules.test.jsx.snap
@@ -1,93 +1,92 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Rules renders rules control panel control 1`] = `
-
+
-
-
- Content Rules
-
-
- Use the form below to define, change or remove content rules. Rules will automatically perform actions on content when certain triggers take place. After defining rules, you may want to go to a folder to assign them, using the 'rules' item in the actions menu.
-
-
- Filter Rules:
+ Content Rules
-
-
-
-
+
+
-
-
+
+
+ id="toolbar"
+ >
+
+
`;
diff --git a/packages/volto/src/components/manage/Controlpanels/Rules/components/VariableModal.test.jsx b/packages/volto/src/components/manage/Controlpanels/Rules/components/VariableModal.test.jsx.removed
similarity index 71%
rename from packages/volto/src/components/manage/Controlpanels/Rules/components/VariableModal.test.jsx
rename to packages/volto/src/components/manage/Controlpanels/Rules/components/VariableModal.test.jsx.removed
index 4d6bb73ab2..3d7220e637 100644
--- a/packages/volto/src/components/manage/Controlpanels/Rules/components/VariableModal.test.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/Rules/components/VariableModal.test.jsx.removed
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import { Provider } from 'react-intl-redux';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
@@ -9,9 +9,8 @@ import VariableModal from './VariableModal';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() => ),
-}));
+jest.mock('../Toolbar/Toolbar', () => jest.fn(() => ));
+
describe('VariableModal', () => {
it('renders rules add interface', () => {
@@ -21,12 +20,12 @@ describe('VariableModal', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/Controlpanels/Rules/components/__snapshots__/VariableModal.test.jsx.snap b/packages/volto/src/components/manage/Controlpanels/Rules/components/__snapshots__/VariableModal.test.jsx.snap
deleted file mode 100644
index 45338eec4d..0000000000
--- a/packages/volto/src/components/manage/Controlpanels/Rules/components/__snapshots__/VariableModal.test.jsx.snap
+++ /dev/null
@@ -1,8 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`VariableModal renders rules add interface 1`] = `
-
-
-
-
-`;
diff --git a/packages/volto/src/components/manage/Controlpanels/UndoControlpanel.jsx b/packages/volto/src/components/manage/Controlpanels/UndoControlpanel.jsx
index 62bbef85e3..3b95e2dd4a 100644
--- a/packages/volto/src/components/manage/Controlpanels/UndoControlpanel.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/UndoControlpanel.jsx
@@ -7,7 +7,7 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { compose } from 'redux';
import { Link } from 'react-router-dom';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import { Container, Segment, Table, Menu, Input } from 'semantic-ui-react';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import { Icon, Toolbar, Form, Toast } from '@plone/volto/components';
@@ -702,8 +702,8 @@ class UndoControlpanel extends Component {
)}
- {this.state.isClient && (
-
+ {this.state.isClient &&
+ createPortal(
>
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
);
}
diff --git a/packages/volto/src/components/manage/Controlpanels/UndoControlpanel.test.jsx b/packages/volto/src/components/manage/Controlpanels/UndoControlpanel.test.jsx
index 61d65fcc2c..ba7261c78e 100644
--- a/packages/volto/src/components/manage/Controlpanels/UndoControlpanel.test.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/UndoControlpanel.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
@@ -7,9 +7,8 @@ import UndoControlpanel from './UndoControlpanel';
const mockStore = configureStore();
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() => ),
-}));
+jest.mock('../Toolbar/Toolbar', () => jest.fn(() => ));
+
jest.mock('../Form/Form', () => jest.fn(() => ));
describe('UndoControlpanel', () => {
@@ -84,12 +83,13 @@ describe('UndoControlpanel', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/Controlpanels/UpgradeControlPanel.jsx b/packages/volto/src/components/manage/Controlpanels/UpgradeControlPanel.jsx
index b415db3597..fe1efd6c55 100644
--- a/packages/volto/src/components/manage/Controlpanels/UpgradeControlPanel.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/UpgradeControlPanel.jsx
@@ -7,7 +7,7 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { compose } from 'redux';
import { Link } from 'react-router-dom';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import {
Button,
Container,
@@ -311,8 +311,8 @@ class UpgradeControlPanel extends Component {
) : null}
- {this.state.isClient && (
-
+ {this.state.isClient &&
+ createPortal(
>
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
) : null;
}
diff --git a/packages/volto/src/components/manage/Controlpanels/Users/UserGroupMembershipControlPanel.jsx b/packages/volto/src/components/manage/Controlpanels/Users/UserGroupMembershipControlPanel.jsx
index 6195610e7f..0acb553959 100644
--- a/packages/volto/src/components/manage/Controlpanels/Users/UserGroupMembershipControlPanel.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/Users/UserGroupMembershipControlPanel.jsx
@@ -2,9 +2,9 @@
* User Control Panel [user group membership management]
* TODO Enrich with features of user control panel. Then replace user control panel.
*/
-import React, { useEffect } from 'react';
+import React, { useEffect, useState } from 'react';
import { find } from 'lodash';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import { useHistory } from 'react-router';
import { Link, useLocation } from 'react-router-dom';
import { FormattedMessage, useIntl } from 'react-intl';
@@ -45,6 +45,12 @@ const UserGroupMembershipPanel = () => {
id: 'plone_setup',
});
+ const [isClient, setIsClient] = useState(false);
+
+ useEffect(() => {
+ setIsClient(true);
+ }, []);
+
useEffect(() => {
dispatch(listActions('/'));
}, [dispatch]);
@@ -57,7 +63,7 @@ const UserGroupMembershipPanel = () => {
dispatch(getSystemInformation());
}, [dispatch]);
- if (__CLIENT__ && !ploneSetupAction) {
+ if (isClient && !ploneSetupAction) {
return ;
}
@@ -108,8 +114,8 @@ const UserGroupMembershipPanel = () => {
- {__CLIENT__ && (
-
+ {isClient &&
+ createPortal(
{
>
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
>
);
};
diff --git a/packages/volto/src/components/manage/Controlpanels/Users/UserGroupMembershipControlPanel.test.jsx b/packages/volto/src/components/manage/Controlpanels/Users/UserGroupMembershipControlPanel.test.jsx
index 2d9a74030d..26343e85fb 100644
--- a/packages/volto/src/components/manage/Controlpanels/Users/UserGroupMembershipControlPanel.test.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/Users/UserGroupMembershipControlPanel.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
import { MemoryRouter } from 'react-router-dom';
@@ -7,9 +7,8 @@ import { MemoryRouter } from 'react-router-dom';
import UserGroupMembershipControlPanel from './UserGroupMembershipControlPanel';
const mockStore = configureStore();
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() => ),
-}));
+jest.mock('../../Toolbar/Toolbar', () => jest.fn(() => ));
+
describe('UserGroupMembershipControlPanel', () => {
it('renders a user group membership control component', () => {
const store = mockStore({
@@ -49,14 +48,15 @@ describe('UserGroupMembershipControlPanel', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx b/packages/volto/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx
index a961ea260e..c7ae00337f 100644
--- a/packages/volto/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx
@@ -39,7 +39,7 @@ import { find, map, pull, difference } from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { FormattedMessage, injectIntl } from 'react-intl';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import { connect } from 'react-redux';
import { toast } from 'react-toastify';
import { bindActionCreators, compose } from 'redux';
@@ -359,8 +359,8 @@ class UsersControlpanel extends Component {
entry.id === name && !entry.roles.includes(value)
? [...entry.roles, value]
: entry.id !== name
- ? entry.roles
- : pull(entry.roles, value),
+ ? entry.roles
+ : pull(entry.roles, value),
})),
});
}
@@ -662,8 +662,8 @@ class UsersControlpanel extends Component {
- {this.state.isClient && (
-
+ {this.state.isClient &&
+ createPortal(
>
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
);
}
diff --git a/packages/volto/src/components/manage/Controlpanels/Users/UsersControlpanel.test.jsx b/packages/volto/src/components/manage/Controlpanels/Users/UsersControlpanel.test.jsx
index 3a2fecc71c..4ea089c4a2 100644
--- a/packages/volto/src/components/manage/Controlpanels/Users/UsersControlpanel.test.jsx
+++ b/packages/volto/src/components/manage/Controlpanels/Users/UsersControlpanel.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
import jwt from 'jsonwebtoken';
@@ -7,9 +7,8 @@ import jwt from 'jsonwebtoken';
import UsersControlpanel from './UsersControlpanel';
const mockStore = configureStore();
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() => ),
-}));
+jest.mock('../../Toolbar/Toolbar', () => jest.fn(() => ));
+
describe('UsersControlpanel', () => {
it('renders a user control component', () => {
const store = mockStore({
@@ -37,12 +36,13 @@ describe('UsersControlpanel', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/Controlpanels/Users/__snapshots__/UserGroupMembershipControlPanel.test.jsx.snap b/packages/volto/src/components/manage/Controlpanels/Users/__snapshots__/UserGroupMembershipControlPanel.test.jsx.snap
index 383fc4159d..3da50c6b65 100644
--- a/packages/volto/src/components/manage/Controlpanels/Users/__snapshots__/UserGroupMembershipControlPanel.test.jsx.snap
+++ b/packages/volto/src/components/manage/Controlpanels/Users/__snapshots__/UserGroupMembershipControlPanel.test.jsx.snap
@@ -1,37 +1,40 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`UserGroupMembershipControlPanel renders a user group membership control component 1`] = `
-
-
- Unauthorized
-
-
-
+
- You are trying to access a protected resource, please
-
+ Unauthorized
+
+
+
- log in
-
- first.
-
-
- If you are certain you have the correct web address but are encountering an error, please contact the
-
- Site Administration
-
- .
-
-
- Thank you.
-
+ You are trying to access a protected resource, please
+
+ log in
+
+ first.
+
+
+ If you are certain you have the correct web address but are encountering an error, please contact the
+
+ Site Administration
+
+ .
+
+
+ Thank you.
+
+
+
`;
diff --git a/packages/volto/src/components/manage/Controlpanels/Users/__snapshots__/UsersControlpanel.test.jsx.snap b/packages/volto/src/components/manage/Controlpanels/Users/__snapshots__/UsersControlpanel.test.jsx.snap
index 5e68f9ca78..3e42328f02 100644
--- a/packages/volto/src/components/manage/Controlpanels/Users/__snapshots__/UsersControlpanel.test.jsx.snap
+++ b/packages/volto/src/components/manage/Controlpanels/Users/__snapshots__/UsersControlpanel.test.jsx.snap
@@ -1,124 +1,117 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`UsersControlpanel renders a user control component 1`] = `
-
+
-
- Users
-
-
- Note that roles set here apply directly to a user. The symbol
-
+ class="container"
+ />
-
-
+
+ Note that roles set here apply directly to a user. The symbol
+
+ indicates a role inherited from membership in a group.
+
+
-
-
-
-
-
+
+
-
-
-
- User name
- |
-
- Actions
- |
-
-
-
-
-
-
+
+ User name
+ |
+
+ Actions
+ |
+
+
+
+
-
-
+
+
+ id="toolbar"
+ >
+
+
`;
diff --git a/packages/volto/src/components/manage/Controlpanels/__snapshots__/AddonsControlpanel.test.jsx.snap b/packages/volto/src/components/manage/Controlpanels/__snapshots__/AddonsControlpanel.test.jsx.snap
index 78ea65bb56..5e191feca7 100644
--- a/packages/volto/src/components/manage/Controlpanels/__snapshots__/AddonsControlpanel.test.jsx.snap
+++ b/packages/volto/src/components/manage/Controlpanels/__snapshots__/AddonsControlpanel.test.jsx.snap
@@ -1,221 +1,195 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AddonsControlpanel renders an addon control component 1`] = `
-
+
- Add-ons Settings
-
-
- Updates available
+ Add-ons Settings
- Update installed addons
-
-
-
- Activate and deactivate add-ons in the lists below.
-
- To make new add-ons show up here, add them to your configuration, build, and restart the server process. For detailed instructions see
-
-
+ Update installed addons
+
+
- Installing a third party add-on
-
- .
-
-
- Available
- :
+
+ To make new add-ons show up here, add them to your configuration, build, and restart the server process. For detailed instructions see
+
+
+ Installing a third party add-on
+
+ .
+
- 1
+ Available
+ :
+
+ 1
+
-
-
-
-
- Something Else
-
-
+ class="accordion ui"
+ >
+ class="ui divider"
+ />
+
- Does other things…
+ Something Else
+
-
+
- Install
-
+
+
+
+ Latest version
+ :
+ 1.0.0
+
- Latest version
- :
- 1.0.0
-
+ class="ui divider"
+ />
-
-
-
- Installed
- :
- 1
+ Installed
+ :
+
+ 1
+
-
-
-
-
- Whatever
-
- Update
-
-
-
+ class="accordion ui"
+ >
+ class="ui divider"
+ />
+
- Does stuff and junk…
+ Whatever
+
+ Update
+
+
-
+
- Update from version to
-
-
+
+
+
- Uninstall
-
+ Installed version
+ :
+ 1.0.0
+
- Installed version
- :
- 1.0.0
-
+ class="ui divider"
+ />
-
+ id="toolbar"
+ >
+
+
`;
diff --git a/packages/volto/src/components/manage/Controlpanels/__snapshots__/Aliases.test.jsx.snap b/packages/volto/src/components/manage/Controlpanels/__snapshots__/Aliases.test.jsx.snap
index 3faa80d47e..79e00ac7e7 100644
--- a/packages/volto/src/components/manage/Controlpanels/__snapshots__/Aliases.test.jsx.snap
+++ b/packages/volto/src/components/manage/Controlpanels/__snapshots__/Aliases.test.jsx.snap
@@ -1,518 +1,440 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Aliases renders an aliases control component 1`] = `
-
+
-
-
- URL Management
-
-
+ URL Management
+
+
- Alternative url path (Required)
-
-
- Enter the absolute path where the alternative url should exist. The path must start with '/'. Only urls that result in a 404 not found page will result in a redirect occurring.
-
-
+
+ Enter the absolute path where the alternative url should exist. The path must start with '/'. Only urls that result in a 404 not found page will result in a redirect occurring.
+
+
-
-
- Target Path (Required)
-
-
- Enter the absolute path of the target. The path must start with '/'. Target must exist or be an existing alternative url path to the target.
-
-
+
+ Enter the absolute path of the target. The path must start with '/'. Target must exist or be an existing alternative url path to the target.
+
+
+
-
-
-
-
-
-
+
- All existing alternative urls for this site
-
-
- Filter by prefix
-
-
-
-
- Manually or automatically added?
-
-
-
-
-
-
-
-
-
- Alternative url path → target url path (date and time of creation, manually created yes/no)
-
-
-
-
-
- Select
- |
-
- Alias
- |
-
- Target
- |
-
- Date
- |
-
- Manual
- |
-
-
+
+
+
+
+
-
-
-
-
-
- |
-
-
- /eventsalias
-
- |
-
-
- /events
-
- |
-
-
- 2022-05-16T11:52:35+00:00
-
- |
-
-
- true
-
- |
-
-
|
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
- /eventsgreatalias
-
- |
-
+ |
+ Alias
+ |
+
+ Target
+ |
+
+ Date
+ |
+
+ Manual
+ |
+
+
-
- /events
-
-
-
+
+
+
+
+ |
+
+
+ /eventsalias
+
+ |
+
+
+ /events
+
+ |
+
+
+ 2022-05-16T11:52:35+00:00
+
+ |
+
+
+ true
+
+ |
+
+
-
- 2022-05-17T09:38:19+00:00
-
-
-
+
+
+
+
+ |
+
+
+ /eventsgreatalias
+
+ |
+
+
+ /events
+
+ |
+
+
+ 2022-05-17T09:38:19+00:00
+
+ |
+
+
+ true
+
+ |
+
+
-
- true
-
-
-
-
+
+
+
+
+
+
+
+ /eventsincredible
+
+ |
+
+
+ /events
+
+ |
+
+
+ 2022-05-17T09:38:35+00:00
+
+ |
+
+
+ true
+
+ |
+
+
+
+
+
-
-
-
-
+
-
-
-
-
-
+
+
+
+
+ id="toolbar"
+ >
+
+
`;
diff --git a/packages/volto/src/components/manage/Controlpanels/__snapshots__/ContentType.test.jsx.snap b/packages/volto/src/components/manage/Controlpanels/__snapshots__/ContentType.test.jsx.snap
index ec12264619..4f14e1a276 100644
--- a/packages/volto/src/components/manage/Controlpanels/__snapshots__/ContentType.test.jsx.snap
+++ b/packages/volto/src/components/manage/Controlpanels/__snapshots__/ContentType.test.jsx.snap
@@ -1,20 +1,26 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ContentType renders dexterity content-type component 1`] = `
-
`;
diff --git a/packages/volto/src/components/manage/Controlpanels/__snapshots__/ContentTypeLayout.test.jsx.snap b/packages/volto/src/components/manage/Controlpanels/__snapshots__/ContentTypeLayout.test.jsx.snap
index 05ca0ff67e..e4adb1612b 100644
--- a/packages/volto/src/components/manage/Controlpanels/__snapshots__/ContentTypeLayout.test.jsx.snap
+++ b/packages/volto/src/components/manage/Controlpanels/__snapshots__/ContentTypeLayout.test.jsx.snap
@@ -1,3 +1,10 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`ContentTypeLayout renders dexterity content-type layout component 1`] = ``;
+exports[`ContentTypeLayout renders dexterity content-type layout component 1`] = `
+
+`;
diff --git a/packages/volto/src/components/manage/Controlpanels/__snapshots__/ContentTypes.test.jsx.snap b/packages/volto/src/components/manage/Controlpanels/__snapshots__/ContentTypes.test.jsx.snap
index e1b6dced61..1a4e81fdba 100644
--- a/packages/volto/src/components/manage/Controlpanels/__snapshots__/ContentTypes.test.jsx.snap
+++ b/packages/volto/src/components/manage/Controlpanels/__snapshots__/ContentTypes.test.jsx.snap
@@ -1,367 +1,249 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ContentTypes renders dexterity content-types controlpanel component 1`] = `
-
+
-
-
+
-
- Dexterity Content Types
-
-
-
+
+
+
+
+
+
+
+ id="toolbar"
+ >
+
+
`;
diff --git a/packages/volto/src/components/manage/Controlpanels/__snapshots__/Controlpanel.test.jsx.snap b/packages/volto/src/components/manage/Controlpanels/__snapshots__/Controlpanel.test.jsx.snap
index 8d62cd5bef..c496e9059b 100644
--- a/packages/volto/src/components/manage/Controlpanels/__snapshots__/Controlpanel.test.jsx.snap
+++ b/packages/volto/src/components/manage/Controlpanels/__snapshots__/Controlpanel.test.jsx.snap
@@ -1,18 +1,24 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Controlpanel renders a controlpanel component 1`] = `
-
+
+ class="ui container"
+ >
+
+
+ id="toolbar"
+ >
+
+
`;
diff --git a/packages/volto/src/components/manage/Controlpanels/__snapshots__/Controlpanels.test.jsx.snap b/packages/volto/src/components/manage/Controlpanels/__snapshots__/Controlpanels.test.jsx.snap
index de99eecd17..7c585afada 100644
--- a/packages/volto/src/components/manage/Controlpanels/__snapshots__/Controlpanels.test.jsx.snap
+++ b/packages/volto/src/components/manage/Controlpanels/__snapshots__/Controlpanels.test.jsx.snap
@@ -1,1157 +1,834 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Controlpanels renders a controlpanels component 1`] = `
-
+
- Site Setup
-
-
- General
-
-
+ Site Setup
+
+
+ General
+
+
-
+
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
+
+ Date and Time
+
+
+
+
+
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
+
+ URL Management
+
+
+
+
+
-
-
-
-
-
+
+
+ Undo
+
+
+
+
-
-
- Content
-
-
+ Content
+
+
-
-
+
+ Content Rules
+
+
+
+
+
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
+
+ Moderate Comments
+
+
+
+
+
-
-
-
-
-
+
+
+ Relations
+
+
+
+
-
-
- Security
-
-
-
-
- Users
-
-
+ Users
+
+
-
+
-
-
-
-
-
-
-
-
+
+ User Group Membership
+
+
+
+
+
-
-
-
-
-
+
+
+ Users
+
+
+
+
-
-
- Version Overview
+
+ Version Overview
+
+
-
+
`;
exports[`Controlpanels renders an additional control panel 1`] = `
-
+
- Site Setup
-
-
- Security
-
-
+ Site Setup
+
+
+ Security
+
+
-
-
- Add-on Configuration
-
-
+ Add-on Configuration
+
+
-
-
- General
-
-
+ General
+
+
-
+
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
+
+ URL Management
+
+
+
+
+
-
-
-
-
-
+
+
+ Undo
+
+
+
+
-
-
- Content
-
-
+ Content
+
+
-
-
+
+ Content Rules
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+ Moderate Comments
+
+
+
+
+
-
-
-
-
-
+
+
+ Relations
+
+
+
+
-
-
- Users
-
-
+ Users
+
+
-
+
-
-
-
-
-
-
-
-
+
+ User Group Membership
+
+
+
+
+
-
-
-
-
-
+
+
+ Users
+
+
+
+
-
-
- Version Overview
+
+ Version Overview
+
+
-
+ id="toolbar"
+ >
+
+
`;
diff --git a/packages/volto/src/components/manage/Controlpanels/__snapshots__/ModerateComments.test.jsx.snap b/packages/volto/src/components/manage/Controlpanels/__snapshots__/ModerateComments.test.jsx.snap
index 2c3c5473a4..41a8a9da88 100644
--- a/packages/volto/src/components/manage/Controlpanels/__snapshots__/ModerateComments.test.jsx.snap
+++ b/packages/volto/src/components/manage/Controlpanels/__snapshots__/ModerateComments.test.jsx.snap
@@ -1,68 +1,74 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ModerateComments renders a moderate comments component 1`] = `
-
`;
diff --git a/packages/volto/src/components/manage/Controlpanels/__snapshots__/UndoControlpanel.test.jsx.snap b/packages/volto/src/components/manage/Controlpanels/__snapshots__/UndoControlpanel.test.jsx.snap
index 5f3f74bc51..44d370a5b8 100644
--- a/packages/volto/src/components/manage/Controlpanels/__snapshots__/UndoControlpanel.test.jsx.snap
+++ b/packages/volto/src/components/manage/Controlpanels/__snapshots__/UndoControlpanel.test.jsx.snap
@@ -1,452 +1,414 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`UndoControlpanel renders undo controlpanel component 1`] = `
-
+
- Undo Controlpanel
-
-
-
+ class="ui segment primary"
+ >
+ Undo Controlpanel
+
-
+
+ id="toolbar"
+ >
+
+
`;
diff --git a/packages/volto/src/components/manage/Delete/Delete.jsx b/packages/volto/src/components/manage/Delete/Delete.jsx
index 805c6ac0a8..50da8f9b2e 100644
--- a/packages/volto/src/components/manage/Delete/Delete.jsx
+++ b/packages/volto/src/components/manage/Delete/Delete.jsx
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';
import { useDispatch, useSelector, shallowEqual } from 'react-redux';
import { useHistory, useLocation } from 'react-router-dom';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import { Button, Container, List, Segment } from 'semantic-ui-react';
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
import qs from 'query-string';
@@ -110,15 +110,15 @@ const Delete = () => {
- {isClient && (
-
+ {isClient &&
+ createPortal(
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
);
}
diff --git a/packages/volto/src/components/manage/Delete/Delete.test.jsx b/packages/volto/src/components/manage/Delete/Delete.test.jsx
index 7069d32a4e..cd1376c2c5 100644
--- a/packages/volto/src/components/manage/Delete/Delete.test.jsx
+++ b/packages/volto/src/components/manage/Delete/Delete.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
import { MemoryRouter } from 'react-router-dom';
@@ -8,9 +8,7 @@ import Delete from './Delete';
const mockStore = configureStore();
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() => ),
-}));
+jest.mock('../Toolbar/Toolbar', () => jest.fn(() => ));
describe('Delete', () => {
it('renders an empty delete component', () => {
@@ -27,15 +25,16 @@ describe('Delete', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
it('renders a delete component', () => {
@@ -54,14 +53,15 @@ describe('Delete', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/Delete/__snapshots__/Delete.test.jsx.snap b/packages/volto/src/components/manage/Delete/__snapshots__/Delete.test.jsx.snap
index 737742ef56..0d903b147c 100644
--- a/packages/volto/src/components/manage/Delete/__snapshots__/Delete.test.jsx.snap
+++ b/packages/volto/src/components/manage/Delete/__snapshots__/Delete.test.jsx.snap
@@ -1,70 +1,78 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Delete renders a delete component 1`] = `
-
+
- Do you really want to delete this item?
-
-
+ Do you really want to delete this item?
+
+
-
-
-
-
+
+
+
+ id="toolbar"
+ >
+
+
`;
-exports[`Delete renders an empty delete component 1`] = ``;
+exports[`Delete renders an empty delete component 1`] = `
+
+`;
diff --git a/packages/volto/src/components/manage/Diff/Diff.jsx b/packages/volto/src/components/manage/Diff/Diff.jsx
index c7302970cf..6b5b1caf21 100644
--- a/packages/volto/src/components/manage/Diff/Diff.jsx
+++ b/packages/volto/src/components/manage/Diff/Diff.jsx
@@ -11,7 +11,7 @@ import { compose } from 'redux';
import { filter, isEqual, map } from 'lodash';
import { Container, Button, Dropdown, Grid, Table } from 'semantic-ui-react';
import { Link, withRouter } from 'react-router-dom';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import qs from 'query-string';
@@ -333,8 +333,8 @@ class Diff extends Component {
view={this.props.view}
/>
)}
- {this.state.isClient && (
-
+ {this.state.isClient &&
+ createPortal(
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
);
}
diff --git a/packages/volto/src/components/manage/Diff/Diff.test.jsx b/packages/volto/src/components/manage/Diff/Diff.test.jsx
index 9da87ed980..c0a890a19d 100644
--- a/packages/volto/src/components/manage/Diff/Diff.test.jsx
+++ b/packages/volto/src/components/manage/Diff/Diff.test.jsx
@@ -8,9 +8,7 @@ import Diff from './Diff';
const mockStore = configureStore();
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() => ),
-}));
+jest.mock('../Toolbar/Toolbar', () => jest.fn(() => ));
jest.mock('@plone/volto/helpers/Loadable/Loadable');
beforeAll(
@@ -75,6 +73,7 @@ describe('Diff', () => {
+
,
);
diff --git a/packages/volto/src/components/manage/Diff/__snapshots__/Diff.test.jsx.snap b/packages/volto/src/components/manage/Diff/__snapshots__/Diff.test.jsx.snap
index 65383def7a..8512803d3c 100644
--- a/packages/volto/src/components/manage/Diff/__snapshots__/Diff.test.jsx.snap
+++ b/packages/volto/src/components/manage/Diff/__snapshots__/Diff.test.jsx.snap
@@ -269,6 +269,10 @@ exports[`Diff renders a diff component 1`] = `
+
+
diff --git a/packages/volto/src/components/manage/Display/Display.jsx b/packages/volto/src/components/manage/Display/Display.jsx
index e5673a1503..0680d84794 100644
--- a/packages/volto/src/components/manage/Display/Display.jsx
+++ b/packages/volto/src/components/manage/Display/Display.jsx
@@ -95,8 +95,8 @@ const customSelectStyles = {
color: state.isSelected
? '#007bc1'
: state.isFocused
- ? '#4a4a4a'
- : 'inherit',
+ ? '#4a4a4a'
+ : 'inherit',
':active': {
backgroundColor: null,
},
diff --git a/packages/volto/src/components/manage/Edit/Edit.jsx b/packages/volto/src/components/manage/Edit/Edit.jsx
index c8fbb89761..56e40f7996 100644
--- a/packages/volto/src/components/manage/Edit/Edit.jsx
+++ b/packages/volto/src/components/manage/Edit/Edit.jsx
@@ -11,7 +11,7 @@ import { compose } from 'redux';
import { asyncConnect, hasApiExpander } from '@plone/volto/helpers';
import { defineMessages, injectIntl } from 'react-intl';
import { Button, Grid, Menu } from 'semantic-ui-react';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import qs from 'query-string';
import { find } from 'lodash';
import { toast } from 'react-toastify';
@@ -41,6 +41,7 @@ import {
hasBlocksData,
} from '@plone/volto/helpers';
import { preloadLazyLibs } from '@plone/volto/helpers/Loadable';
+import { tryParseJSON } from '@plone/volto/helpers';
import saveSVG from '@plone/volto/icons/save.svg';
import clearSVG from '@plone/volto/icons/clear.svg';
@@ -64,6 +65,10 @@ const messages = defineMessages({
id: 'Error',
defaultMessage: 'Error',
},
+ someErrors: {
+ id: 'There are some errors.',
+ defaultMessage: 'There are some errors.',
+ },
});
/**
@@ -198,13 +203,30 @@ class Edit extends Component {
new DOMParser().parseFromString(message, 'text/html')?.all[0]
?.textContent || message;
+ const errorsList = tryParseJSON(error);
+ let erroMessage;
+ if (Array.isArray(errorsList)) {
+ const invariantErrors = errorsList
+ .filter((errorItem) => !('field' in errorItem))
+ .map((errorItem) => errorItem['message']);
+ if (invariantErrors.length > 0) {
+ // Plone invariant validation message.
+ erroMessage = invariantErrors.join(' - ');
+ } else {
+ // Error in specific field.
+ erroMessage = this.props.intl.formatMessage(messages.someErrors);
+ }
+ } else {
+ erroMessage = error;
+ }
+
this.setState({ error: error });
toast.error(
,
);
}
@@ -375,11 +397,10 @@ class Edit extends Component {
>
)}
- {editPermission && this.state.visual && this.state.isClient && (
-
-
-
- )}
+ {editPermission &&
+ this.state.visual &&
+ this.state.isClient &&
+ createPortal(
, document.getElementById('sidebar'))}
>
)}
{!editPermission && (
@@ -397,8 +418,8 @@ class Edit extends Component {
)}
>
)}
- {this.state.isClient && (
-
+ {this.state.isClient &&
+ createPortal(
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
);
}
diff --git a/packages/volto/src/components/manage/Edit/Edit.test.jsx b/packages/volto/src/components/manage/Edit/Edit.test.jsx
index 5e28911e95..28eff96b95 100644
--- a/packages/volto/src/components/manage/Edit/Edit.test.jsx
+++ b/packages/volto/src/components/manage/Edit/Edit.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
import jwt from 'jsonwebtoken';
@@ -8,9 +8,9 @@ import { __test__ as Edit } from './Edit';
const mockStore = configureStore();
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() => ),
-}));
+jest.mock('../Toolbar/Toolbar', () => jest.fn(() => ));
+jest.mock('../Sidebar/Sidebar', () => jest.fn(() => ));
+
jest.mock('../Form/Form', () => jest.fn(() => ));
describe('Edit', () => {
@@ -50,13 +50,15 @@ describe('Edit', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
it('renders an edit component', () => {
@@ -97,12 +99,14 @@ describe('Edit', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/Edit/__snapshots__/Edit.test.jsx.snap b/packages/volto/src/components/manage/Edit/__snapshots__/Edit.test.jsx.snap
index e8df203414..67d8997fd5 100644
--- a/packages/volto/src/components/manage/Edit/__snapshots__/Edit.test.jsx.snap
+++ b/packages/volto/src/components/manage/Edit/__snapshots__/Edit.test.jsx.snap
@@ -1,33 +1,53 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Edit renders an edit component 1`] = `
-
+
+ id="page-edit"
+ >
+
+
+ id="sidebar"
+ >
+
+
`;
exports[`Edit renders an empty edit component 1`] = `
-
+
+ id="page-edit"
+ >
+
+
+ id="sidebar"
+ >
+
+
`;
diff --git a/packages/volto/src/components/manage/Form/BlockDataForm.test.jsx b/packages/volto/src/components/manage/Form/BlockDataForm.test.jsx
index 6c77dc20f7..e0abf26532 100644
--- a/packages/volto/src/components/manage/Form/BlockDataForm.test.jsx
+++ b/packages/volto/src/components/manage/Form/BlockDataForm.test.jsx
@@ -1,7 +1,6 @@
import React from 'react';
import BlockDataForm from './BlockDataForm';
import { render } from '@testing-library/react';
-import '@testing-library/jest-dom/extend-expect';
import configureStore from 'redux-mock-store';
import config from '@plone/volto/registry';
import { Provider } from 'react-intl-redux';
diff --git a/packages/volto/src/components/manage/Form/Form.jsx b/packages/volto/src/components/manage/Form/Form.jsx
index 2e7c4ff4e6..40b8ae599a 100644
--- a/packages/volto/src/components/manage/Form/Form.jsx
+++ b/packages/volto/src/components/manage/Form/Form.jsx
@@ -31,7 +31,7 @@ import isBoolean from 'lodash/isBoolean';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { injectIntl } from 'react-intl';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import { connect } from 'react-redux';
import {
Accordion,
@@ -53,6 +53,7 @@ import {
} from '@plone/volto/actions';
import { compose } from 'redux';
import config from '@plone/volto/registry';
+import SlotRenderer from '../../theme/SlotRenderer/SlotRenderer';
/**
* Form container class.
@@ -237,6 +238,7 @@ class Form extends Component {
isClient: false,
// Ensure focus remain in field after change
inFocus: {},
+ sidebarMetadataIsAvailable: false,
};
this.onChangeField = this.onChangeField.bind(this);
this.onSelectBlock = this.onSelectBlock.bind(this);
@@ -312,6 +314,13 @@ class Form extends Component {
// Reset focus field
this.props.resetMetadataFocus();
}
+
+ if (
+ !this.state.sidebarMetadataIsAvailable &&
+ document.getElementById('sidebar-metadata')
+ ) {
+ this.setState(() => ({ sidebarMetadataIsAvailable: true }));
+ }
}
/**
@@ -641,131 +650,147 @@ class Form extends Component {
// Removing this from SSR is important, since react-beautiful-dnd supports SSR,
// but draftJS don't like it much and the hydration gets messed up
this.state.isClient && (
-
- {
- const newFormData = {
- ...formData,
- ...newBlockData,
- };
- this.setState({
- formData: newFormData,
- });
- if (this.props.global) {
- this.props.setFormData(newFormData);
- }
- }}
- onSetSelectedBlocks={(blockIds) =>
- this.setState({ multiSelected: blockIds })
- }
- onSelectBlock={this.onSelectBlock}
- />
- {
- if (this.props.global) {
- this.props.setFormData(state.formData);
- }
- return this.setState(state);
- }}
- />
- {
- const newFormData = {
- ...formData,
- ...newData,
- };
- this.setState({
- formData: newFormData,
- });
- if (this.props.global) {
- this.props.setFormData(newFormData);
- }
- }}
- onChangeField={this.onChangeField}
- onSelectBlock={this.onSelectBlock}
- properties={formData}
+ <>
+
- {this.state.isClient && this.props.editable && (
-
- 0}
- >
- {schema &&
- map(schema.fieldsets, (fieldset) => (
-
-
-
- ))}
-
-
- )}
-
+
+ {fieldset.title}
+ {metadataFieldsets.includes(fieldset.id) ? (
+
+ ) : (
+
+ )}
+
+
+
+ {map(fieldset.fields, (field, index) => (
+
+ ))}
+
+
+
+
+ ))}
+ ,
+ document.getElementById('sidebar-metadata'),
+ )}
+
+
+
+ >
)
) : (
@@ -931,6 +956,7 @@ const FormIntl = injectIntl(Form, { forwardRef: true });
export default compose(
connect(
(state, props) => ({
+ content: state.content.data,
globalData: state.form?.global,
metadataFieldsets: state.sidebar?.metadataFieldsets,
metadataFieldFocus: state.sidebar?.metadataFieldFocus,
diff --git a/packages/volto/src/components/manage/Form/Form.test.jsx b/packages/volto/src/components/manage/Form/Form.test.jsx
index 16e2362b65..1ac45f0e4d 100644
--- a/packages/volto/src/components/manage/Form/Form.test.jsx
+++ b/packages/volto/src/components/manage/Form/Form.test.jsx
@@ -18,6 +18,13 @@ describe('Form', () => {
locale: 'en',
messages: {},
},
+ content: {
+ data: {},
+ create: {
+ loading: false,
+ loaded: true,
+ },
+ },
});
const component = renderer.create(
diff --git a/packages/volto/src/components/manage/Form/ModalForm.test.jsx b/packages/volto/src/components/manage/Form/ModalForm.test.jsx
index 6e491ee79a..f6eb4e368d 100644
--- a/packages/volto/src/components/manage/Form/ModalForm.test.jsx
+++ b/packages/volto/src/components/manage/Form/ModalForm.test.jsx
@@ -3,7 +3,6 @@ import renderer from 'react-test-renderer';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
import { render } from '@testing-library/react';
-import '@testing-library/jest-dom/extend-expect';
import ModalForm from './ModalForm';
diff --git a/packages/volto/src/components/manage/History/History.jsx b/packages/volto/src/components/manage/History/History.jsx
index 5fa61001a4..f3694e5275 100644
--- a/packages/volto/src/components/manage/History/History.jsx
+++ b/packages/volto/src/components/manage/History/History.jsx
@@ -17,7 +17,7 @@ import {
Table,
} from 'semantic-ui-react';
import { concat, map, reverse, find } from 'lodash';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import { asyncConnect } from '@plone/volto/helpers';
@@ -314,8 +314,8 @@ class History extends Component {
- {this.state.isClient && (
-
+ {this.state.isClient &&
+ createPortal(
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
);
}
diff --git a/packages/volto/src/components/manage/History/History.test.jsx b/packages/volto/src/components/manage/History/History.test.jsx
index c0ddab5703..164e699399 100644
--- a/packages/volto/src/components/manage/History/History.test.jsx
+++ b/packages/volto/src/components/manage/History/History.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import configureStore from 'redux-mock-store';
import { StaticRouter } from 'react-router-dom';
import { Provider } from 'react-intl-redux';
@@ -9,9 +9,7 @@ import FakeTimers from '@sinonjs/fake-timers';
import History from './History';
const mockStore = configureStore();
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() => ),
-}));
+jest.mock('../Toolbar/Toolbar', () => jest.fn(() => ));
const FIXED_SYSTEM_TIME = '2017-04-23T15:38:00.000Z';
@@ -87,13 +85,14 @@ describe('History', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
it('redirects if unassigned', () => {
@@ -153,13 +152,14 @@ describe('History', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
it('redirects if unassigned, no token gives unauthorized', () => {
@@ -217,14 +217,15 @@ describe('History', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/History/__snapshots__/History.test.jsx.snap b/packages/volto/src/components/manage/History/__snapshots__/History.test.jsx.snap
index af2888cab6..975b25879e 100644
--- a/packages/volto/src/components/manage/History/__snapshots__/History.test.jsx.snap
+++ b/packages/volto/src/components/manage/History/__snapshots__/History.test.jsx.snap
@@ -1,265 +1,268 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`History redirects if unassigned 1`] = `
-
-
- Forbidden
-
-
+
- We apologize for the inconvenience, but you don't have permissions on this resource.
-
+
+ Forbidden
+
+
+ We apologize for the inconvenience, but you don't have permissions on this resource.
+
+
+
`;
exports[`History redirects if unassigned, no token gives unauthorized 1`] = `
-
-
- Unauthorized
-
-
-
+
- You are trying to access a protected resource, please
-
+ Unauthorized
+
+
+
- log in
-
- first.
-
-
- If you are certain you have the correct web address but are encountering an error, please contact the
-
- Site Administration
-
- .
-
-
- Thank you.
-
+ You are trying to access a protected resource, please
+
+ log in
+
+ first.
+
+
+ If you are certain you have the correct web address but are encountering an error, please contact the
+
+ Site Administration
+
+ .
+
+
+ Thank you.
+
+
+
`;
exports[`History renders a history component 1`] = `
-
+
- History of
-
- Blog
-
-
-
- You can view the history of your item below.
-
-
-
-
-
- #
- |
-
- What
- |
-
- Who
- |
-
- When
- |
-
- Change Note
- |
- |
-
-
-
+ Blog
+
+
+
+ You can view the history of your item below.
+
+
-
-
-
- |
-
-
- Publish
- (Private → Published)
-
- |
-
- Web Admin
- |
-
-
- |
-
-
- |
- |
-
-
+
+ What
+ |
+
+ Who
+ |
+
+ When
+ |
+
+ Change Note
+ |
+ |
+
+
+
-
-
- |
-
-
- Edited
-
- |
-
- Web Admin
- |
-
-
- |
-
- Changed text
- |
- |
+
+
+
+ Publish
+ (Private → Published)
+
+ |
+
+ Web Admin
+ |
+
+
+ |
+ |
+ |
+
+
-
+
+
+
+
+ Edited
+
+ |
+
+ Web Admin
+ |
+
+
+ |
+
+ Changed text
+ |
+
-
-
- |
-
-
-
-
- |
-
-
- Create
- (Private)
-
- |
-
+
+
+
+ |
+
+
- Web Admin
-
-
-
- |
-
-
- |
- |
-
-
-
+
+
+
+
+ Create
+ (Private)
+
+ |
+
+ Web Admin
+ |
+
+
+ |
+ |
+ |
+
+
+
+
+ id="toolbar"
+ >
+
+
`;
diff --git a/packages/volto/src/components/manage/LinksToItem/LinksToItem.jsx b/packages/volto/src/components/manage/LinksToItem/LinksToItem.jsx
index bc93a24055..a8ad6915cd 100644
--- a/packages/volto/src/components/manage/LinksToItem/LinksToItem.jsx
+++ b/packages/volto/src/components/manage/LinksToItem/LinksToItem.jsx
@@ -2,11 +2,11 @@
* LinksToItem component
* @module components/manage/LinksToItem/LinksToItem
*/
-import { useEffect } from 'react';
+import React, { useEffect, useState } from 'react';
import { find } from 'lodash';
import { Helmet } from '@plone/volto/helpers';
import { Link } from 'react-router-dom';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import { Container, Segment, Table } from 'semantic-ui-react';
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
import { useDispatch, useSelector } from 'react-redux';
@@ -19,7 +19,6 @@ import {
UniversalLink,
Unauthorized,
} from '@plone/volto/components';
-import { compose } from 'redux';
import { getBaseUrl } from '@plone/volto/helpers';
import backSVG from '@plone/volto/icons/back.svg';
@@ -57,6 +56,12 @@ const LinksToItem = (props) => {
id: 'plone_setup',
});
+ const [isClient, setIsClient] = useState(false);
+
+ useEffect(() => {
+ setIsClient(true);
+ }, []);
+
useEffect(() => {
dispatch(queryRelations(null, false, itempath, null, [itempath]));
}, [dispatch, itempath]);
@@ -101,41 +106,46 @@ const LinksToItem = (props) => {
{
- {Object.keys(links_ordered).map((relationtype) => {
+ {Object.keys(links_ordered).map((relationtype, index) => {
+ // TODO: keys driven by links_ordered[relationtype][index]['@id'])
return [].concat(
[
-
-
- {relationtype === 'isReferencing' ? (
-
- ) : relationtype === 'relatedItems' ? (
-
- ) : (
- <>
+
+
+
+ {relationtype === 'isReferencing' ? (
{relationtype} }}
+ id="Linking this item with hyperlink in text"
+ defaultMessage="Linking this item with hyperlink in text"
/>
- >
- )}
-
-
-
-
-
-
-
- ,
+ ) : relationtype === 'relatedItems' ? (
+
+ ) : (
+ <>
+ {relationtype},
+ }}
+ />
+ >
+ )}
+
+
+
+
+
+
+
+
+ ,
],
links_ordered[relationtype].map((link) => {
return (
@@ -173,8 +183,8 @@ const LinksToItem = (props) => {
)}
- {__CLIENT__ && (
-
+ {isClient &&
+ createPortal(
{
>
>
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
);
};
export const __test__ = LinksToItem;
-export default compose(
- asyncConnect([
- {
- key: 'actions',
- // Dispatch async/await to make the operation synchronous, otherwise it returns
- // before the promise is resolved
- promise: async ({ location, store: { dispatch } }) =>
- await dispatch(listActions(getBaseUrl(location.pathname))),
+
+export default asyncConnect([
+ {
+ key: 'content',
+ // Dispatch async/await to make the operation synchronous, otherwise it returns
+ // before the promise is resolved
+ promise: async ({ location, store: { dispatch } }) => {
+ const pathname = getBaseUrl(location.pathname);
+ return await dispatch(getContent(pathname));
},
- ]),
-)(LinksToItem);
+ },
+ {
+ key: 'actions',
+ // Dispatch async/await to make the operation synchronous, otherwise it returns
+ // before the promise is resolved
+ promise: async ({ location, store: { dispatch } }) =>
+ await dispatch(listActions(getBaseUrl(location.pathname))),
+ },
+ {
+ key: 'relations',
+ // Dispatch async/await to make the operation synchronous, otherwise it returns
+ // before the promise is resolved
+ promise: async ({ location, store: { dispatch } }) => {
+ const pathname = getBaseUrl(location.pathname);
+ return await dispatch(
+ queryRelations(null, false, pathname, null, [pathname]),
+ );
+ },
+ },
+])(LinksToItem);
diff --git a/packages/volto/src/components/manage/LinksToItem/LinksToItem.test.jsx b/packages/volto/src/components/manage/LinksToItem/LinksToItem.test.jsx
index 4c590cbbcc..210bd35124 100644
--- a/packages/volto/src/components/manage/LinksToItem/LinksToItem.test.jsx
+++ b/packages/volto/src/components/manage/LinksToItem/LinksToItem.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import { Provider } from 'react-intl-redux';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
@@ -10,9 +10,8 @@ import { __test__ as LinksToItem } from './LinksToItem';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() => ),
-}));
+jest.mock('../Toolbar/Toolbar', () => jest.fn(() => ));
+
jest.mock('../Toolbar/More', () => jest.fn(() => ));
describe('LinksToItem', () => {
@@ -99,14 +98,15 @@ describe('LinksToItem', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/LinksToItem/__snapshots__/LinksToItem.test.jsx.snap b/packages/volto/src/components/manage/LinksToItem/__snapshots__/LinksToItem.test.jsx.snap
index 71a4163128..45741837c2 100644
--- a/packages/volto/src/components/manage/LinksToItem/__snapshots__/LinksToItem.test.jsx.snap
+++ b/packages/volto/src/components/manage/LinksToItem/__snapshots__/LinksToItem.test.jsx.snap
@@ -1,142 +1,142 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LinksToItem renders "links and references" view 1`] = `
-
+
- Content that links to or references
-
- page #1
-
-
-
-
-
+ page #1
+
+
+
+
-
- Linking this item with hyperlink in text
- |
-
- Review state
- |
-
- Type
- |
-
-
-
+ Linking this item with hyperlink in text
+
+ |
+ Review state
+ |
+
+ Type
+ |
+
+
-
-
- Basil
+
+ Basil
+
+
+
+
+
+ published
-
- |
-
-
- published
-
- |
-
-
- Document
-
- |
-
-
-
- Referencing this item as related item
- |
-
- Review state
- |
-
+ |
+
+ Document
+
+ |
+
+
- Type
-
-
-
-
+ Referencing this item as related item
+
+ |
+ Review state
+ |
+
+ Type
+ |
+
+
-
-
- Cucumber
+
+ Cucumber
+
+
+
+
+
+ published
-
- |
-
-
- published
-
- |
-
-
- Document
-
- |
-
-
-
+
+
+
+ Document
+
+ |
+
+
+
+
+ id="toolbar"
+ >
+
+
`;
diff --git a/packages/volto/src/components/manage/Multilingual/CreateTranslation.test.jsx b/packages/volto/src/components/manage/Multilingual/CreateTranslation.test.jsx
deleted file mode 100644
index 56ff1b32a1..0000000000
--- a/packages/volto/src/components/manage/Multilingual/CreateTranslation.test.jsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import React from 'react';
-import renderer from 'react-test-renderer';
-import configureStore from 'redux-mock-store';
-import { Provider } from 'react-intl-redux';
-import { MemoryRouter } from 'react-router-dom';
-import config from '@plone/volto/registry';
-
-import CreateTranslation from './CreateTranslation';
-
-beforeAll(() => {
- config.settings.isMultilingual = true;
- config.settings.supportedLanguages = ['de', 'es'];
-});
-
-const mockStore = configureStore();
-
-describe('CreateTranslation', () => {
- it('renders a CreateTranslation component', () => {
- const store = mockStore({
- intl: {
- locale: 'en',
- messages: {},
- },
- translations: {
- translationLocation: '/es',
- },
- });
- const component = renderer.create(
-
-
-
-
- ,
- );
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
- });
-});
diff --git a/packages/volto/src/components/manage/Multilingual/ManageTranslations.jsx b/packages/volto/src/components/manage/Multilingual/ManageTranslations.jsx
index a375b98e89..37fe9a11dc 100644
--- a/packages/volto/src/components/manage/Multilingual/ManageTranslations.jsx
+++ b/packages/volto/src/components/manage/Multilingual/ManageTranslations.jsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useEffect, useState } from 'react';
import { Button, Container, Segment, Table } from 'semantic-ui-react';
import { Helmet } from '@plone/volto/helpers';
import { flattenToAppURL, getBaseUrl, langmap } from '@plone/volto/helpers';
@@ -15,7 +15,7 @@ import {
} from '@plone/volto/actions';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { useSelector, useDispatch } from 'react-redux';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import { toast } from 'react-toastify';
import addSVG from '@plone/volto/icons/add.svg';
@@ -64,6 +64,12 @@ const ManageTranslations = (props) => {
const content = useSelector((state) => state.content.data);
const dispatch = useDispatch();
+ const [isClient, setIsClient] = useState(false);
+
+ useEffect(() => {
+ setIsClient(true);
+ }, []);
+
const { isObjectBrowserOpen, openObjectBrowser } = props;
const currentSelectedItem = React.useRef(null);
@@ -269,8 +275,8 @@ const ManageTranslations = (props) => {
)}
- {__CLIENT__ && (
-
+ {isClient &&
+ createPortal(
{
/>
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
);
diff --git a/packages/volto/src/components/manage/Multilingual/ManageTranslations.test.jsx b/packages/volto/src/components/manage/Multilingual/ManageTranslations.test.jsx
index a8d51b4101..f4a85720ad 100644
--- a/packages/volto/src/components/manage/Multilingual/ManageTranslations.test.jsx
+++ b/packages/volto/src/components/manage/Multilingual/ManageTranslations.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
import { MemoryRouter } from 'react-router-dom';
@@ -12,9 +12,7 @@ beforeAll(() => {
config.settings.supportedLanguages = ['de', 'es'];
});
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() => ),
-}));
+jest.mock('../Toolbar/Toolbar', () => jest.fn(() => ));
const mockStore = configureStore();
@@ -36,7 +34,7 @@ describe('ManageTranslations', () => {
},
},
});
- const component = renderer.create(
+ const { container } = render(
{
},
}}
/>
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/Multilingual/__snapshots__/CreateTranslation.test.jsx.snap b/packages/volto/src/components/manage/Multilingual/__snapshots__/CreateTranslation.test.jsx.snap
deleted file mode 100644
index 6676d8b34e..0000000000
--- a/packages/volto/src/components/manage/Multilingual/__snapshots__/CreateTranslation.test.jsx.snap
+++ /dev/null
@@ -1,3 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`CreateTranslation renders a CreateTranslation component 1`] = `null`;
diff --git a/packages/volto/src/components/manage/Multilingual/__snapshots__/ManageTranslations.test.jsx.snap b/packages/volto/src/components/manage/Multilingual/__snapshots__/ManageTranslations.test.jsx.snap
index af4ba1ee3a..c91ee38455 100644
--- a/packages/volto/src/components/manage/Multilingual/__snapshots__/ManageTranslations.test.jsx.snap
+++ b/packages/volto/src/components/manage/Multilingual/__snapshots__/ManageTranslations.test.jsx.snap
@@ -1,212 +1,160 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ManageTranslations renders a ManageTranslations component 1`] = `
-
+
- Manage translations for
-
- My page
-
-
-
-
-
-
- Language
- |
-
- Path
- |
-
- Tools
- |
-
-
-
+ My page
+
+
+
-
-
- Deutsch
- |
-
-
+ Language
+
+ |
+ Path
+ |
+
-
-
-
- |
+ |
+
+
+
-
+ Deutsch
+
+
-
-
-
-
+ |
+
-
-
- |
-
-
-
- Español
- |
-
+
+
+
+
+
+
+ |
+
+
-
-
-
-
-
-
+
-
-
-
-
+ |
+
-
-
- |
- |
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/volto/src/components/manage/Preferences/ChangePassword.jsx b/packages/volto/src/components/manage/Preferences/ChangePassword.jsx
index ca817592f6..b686ca0c9f 100644
--- a/packages/volto/src/components/manage/Preferences/ChangePassword.jsx
+++ b/packages/volto/src/components/manage/Preferences/ChangePassword.jsx
@@ -9,7 +9,7 @@ import { Helmet } from '@plone/volto/helpers';
import { connect } from 'react-redux';
import { compose } from 'redux';
import { Link, withRouter } from 'react-router-dom';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import { defineMessages, injectIntl } from 'react-intl';
import { Container } from 'semantic-ui-react';
import jwtDecode from 'jwt-decode';
@@ -195,8 +195,8 @@ class ChangePassword extends Component {
onCancel={this.onCancel}
loading={this.props.loading}
/>
- {this.state.isClient && (
-
+ {this.state.isClient &&
+ createPortal(
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
);
}
diff --git a/packages/volto/src/components/manage/Preferences/ChangePassword.test.jsx b/packages/volto/src/components/manage/Preferences/ChangePassword.test.jsx
index 0df20b57de..7746c796f2 100644
--- a/packages/volto/src/components/manage/Preferences/ChangePassword.test.jsx
+++ b/packages/volto/src/components/manage/Preferences/ChangePassword.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import { Provider } from 'react-intl-redux';
import configureStore from 'redux-mock-store';
import jwt from 'jsonwebtoken';
@@ -9,9 +9,7 @@ import ChangePassword from './ChangePassword';
const mockStore = configureStore();
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() =>
),
-}));
+jest.mock('../Toolbar/Toolbar', () => jest.fn(() =>
));
describe('ChangePassword', () => {
it('renders a change password component', () => {
@@ -28,15 +26,23 @@ describe('ChangePassword', () => {
loading: false,
},
},
+ content: {
+ data: {},
+ create: {
+ loading: false,
+ loaded: true,
+ },
+ },
});
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/Preferences/PersonalInformation.test.jsx b/packages/volto/src/components/manage/Preferences/PersonalInformation.test.jsx
index d08924a6d6..b7abdfaf81 100644
--- a/packages/volto/src/components/manage/Preferences/PersonalInformation.test.jsx
+++ b/packages/volto/src/components/manage/Preferences/PersonalInformation.test.jsx
@@ -18,9 +18,7 @@ const userSchema = {
loading: false,
};
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() =>
),
-}));
+jest.mock('../Toolbar/Toolbar', () => jest.fn(() =>
));
describe('PersonalInformation', () => {
it('renders a personal information component', async () => {
@@ -42,6 +40,13 @@ describe('PersonalInformation', () => {
locale: 'en',
messages: {},
},
+ content: {
+ data: {},
+ create: {
+ loading: false,
+ loaded: true,
+ },
+ },
});
const component = renderer.create(
@@ -76,6 +81,13 @@ describe('PersonalInformation', () => {
locale: 'en',
messages: {},
},
+ content: {
+ data: {},
+ create: {
+ loading: false,
+ loaded: true,
+ },
+ },
});
const component = renderer.create(
diff --git a/packages/volto/src/components/manage/Preferences/PersonalPreferences.test.jsx b/packages/volto/src/components/manage/Preferences/PersonalPreferences.test.jsx
index 6d30297e6f..8a72fe0d9f 100644
--- a/packages/volto/src/components/manage/Preferences/PersonalPreferences.test.jsx
+++ b/packages/volto/src/components/manage/Preferences/PersonalPreferences.test.jsx
@@ -8,9 +8,7 @@ import PersonalPreferences from './PersonalPreferences';
const mockStore = configureStore();
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() => ),
-}));
+jest.mock('../Toolbar/Toolbar', () => jest.fn(() => ));
jest.mock('@plone/volto/helpers/Loadable/Loadable');
beforeAll(
@@ -31,6 +29,13 @@ describe('PersonalPreferences', () => {
itemsTotal: 1,
},
},
+ content: {
+ data: {},
+ create: {
+ loading: false,
+ loaded: true,
+ },
+ },
});
const { container } = render(
diff --git a/packages/volto/src/components/manage/Preferences/__snapshots__/ChangePassword.test.jsx.snap b/packages/volto/src/components/manage/Preferences/__snapshots__/ChangePassword.test.jsx.snap
index ef3e37d8c5..1dbacb73fa 100644
--- a/packages/volto/src/components/manage/Preferences/__snapshots__/ChangePassword.test.jsx.snap
+++ b/packages/volto/src/components/manage/Preferences/__snapshots__/ChangePassword.test.jsx.snap
@@ -1,113 +1,92 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ChangePassword renders a change password component 1`] = `
-
+
-
-
-
+
+
+
+ id="toolbar"
+ >
+
+
`;
diff --git a/packages/volto/src/components/manage/Rules/Rules.jsx b/packages/volto/src/components/manage/Rules/Rules.jsx
index cc0aacf11f..f47c14c567 100644
--- a/packages/volto/src/components/manage/Rules/Rules.jsx
+++ b/packages/volto/src/components/manage/Rules/Rules.jsx
@@ -8,7 +8,7 @@ import { Helmet } from '@plone/volto/helpers';
import { connect } from 'react-redux';
import { compose } from 'redux';
import { Link } from 'react-router-dom';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import {
Button,
Checkbox,
@@ -484,8 +484,8 @@ class Rules extends Component {
)}
- {this.state.isClient && (
-
+ {this.state.isClient &&
+ createPortal(
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
);
}
diff --git a/packages/volto/src/components/manage/Rules/Rules.test.jsx b/packages/volto/src/components/manage/Rules/Rules.test.jsx
index fad9e8a3ab..ba8f414ce4 100644
--- a/packages/volto/src/components/manage/Rules/Rules.test.jsx
+++ b/packages/volto/src/components/manage/Rules/Rules.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import { Provider } from 'react-intl-redux';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
@@ -9,9 +9,8 @@ import Rules from './Rules';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() =>
),
-}));
+jest.mock('../Toolbar/Toolbar', () => jest.fn(() =>
));
+
jest.mock('../Toolbar/More', () => jest.fn(() =>
));
describe('Rules', () => {
@@ -65,12 +64,13 @@ describe('Rules', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/Rules/__snapshots__/Rules.test.jsx.snap b/packages/volto/src/components/manage/Rules/__snapshots__/Rules.test.jsx.snap
index cdc03912ae..8562329969 100644
--- a/packages/volto/src/components/manage/Rules/__snapshots__/Rules.test.jsx.snap
+++ b/packages/volto/src/components/manage/Rules/__snapshots__/Rules.test.jsx.snap
@@ -1,29 +1,35 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Rules renders rules object control 1`] = `
-
+
- Content rules for
-
- Blog
-
-
-
- The following content rules are active in this Page. Use the content rules control panel to create new rules or delete or modify existing ones.
+
+ Content rules for
+
+ Blog
+
+
+
+ The following content rules are active in this Page. Use the content rules control panel to create new rules or delete or modify existing ones.
+
+ id="toolbar"
+ >
+
+
`;
diff --git a/packages/volto/src/components/manage/Sharing/Sharing.jsx b/packages/volto/src/components/manage/Sharing/Sharing.jsx
index 31d715ad0a..b50ece9658 100644
--- a/packages/volto/src/components/manage/Sharing/Sharing.jsx
+++ b/packages/volto/src/components/manage/Sharing/Sharing.jsx
@@ -10,7 +10,7 @@ import { connect } from 'react-redux';
import { compose } from 'redux';
import { Link, withRouter } from 'react-router-dom';
import { find, isEqual, map } from 'lodash';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import {
Button,
Checkbox,
@@ -505,8 +505,8 @@ class SharingComponent extends Component {
- {this.state.isClient && (
-
+ {this.state.isClient &&
+ createPortal(
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
);
}
diff --git a/packages/volto/src/components/manage/Sharing/Sharing.test.jsx b/packages/volto/src/components/manage/Sharing/Sharing.test.jsx
index 9e0cafbfff..4924fe2379 100644
--- a/packages/volto/src/components/manage/Sharing/Sharing.test.jsx
+++ b/packages/volto/src/components/manage/Sharing/Sharing.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
import jwt from 'jsonwebtoken';
@@ -10,9 +10,7 @@ import Sharing from './Sharing';
const mockStore = configureStore();
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() =>
),
-}));
+jest.mock('../Toolbar/Toolbar', () => jest.fn(() =>
));
describe('Sharing', () => {
it('renders a sharing component', () => {
@@ -57,16 +55,18 @@ describe('Sharing', () => {
messages: {},
},
});
- const component = renderer.create(
+
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/manage/Sharing/__snapshots__/Sharing.test.jsx.snap b/packages/volto/src/components/manage/Sharing/__snapshots__/Sharing.test.jsx.snap
index f56ecded12..b9502bffc7 100644
--- a/packages/volto/src/components/manage/Sharing/__snapshots__/Sharing.test.jsx.snap
+++ b/packages/volto/src/components/manage/Sharing/__snapshots__/Sharing.test.jsx.snap
@@ -1,217 +1,186 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Sharing renders a sharing component 1`] = `
-
+
- Sharing for
-
- Blog
-
-
-
- You can control who can view and edit your item using the list below.
-
-
-
-
+ Blog
+
+
+
+ You can control who can view and edit your item using the list below.
+
+
-
-
-
-
-
+
+
-
-
-
- Name
- |
-
- Can add
- |
-
-
-
-
+ Name
+
+
+ Can add
+ |
+
+
+
-
-
+
+
+ John Doe
+ (john-doe)
+ |
+ |
-
- John Doe
- (john-doe)
-
- |
-
-
-
-
+
+
+
+
+ By default, permissions from the container of this item are inherited. If you disable this, only the explicitly defined sharing permissions will be valid. In the overview, the symbol
+
+ indicates an inherited value. Similarly, the symbol
+
+ indicates a global role, which is managed by the site administrator.
+
-
- By default, permissions from the container of this item are inherited. If you disable this, only the explicitly defined sharing permissions will be valid. In the overview, the symbol
-
- indicates an inherited value. Similarly, the symbol
-
- indicates a global role, which is managed by the site administrator.
-
-
-
-
-
-
-
+
+
+
+
+
+ id="toolbar"
+ >
+
+
`;
diff --git a/packages/volto/src/components/manage/Sidebar/ObjectBrowser.jsx b/packages/volto/src/components/manage/Sidebar/ObjectBrowser.jsx
index 0a31dcd486..a509550b95 100644
--- a/packages/volto/src/components/manage/Sidebar/ObjectBrowser.jsx
+++ b/packages/volto/src/components/manage/Sidebar/ObjectBrowser.jsx
@@ -98,7 +98,7 @@ const withObjectBrowser = (WrappedComponent) =>
{
panes={[
!!documentTab && {
menuItem: {
+ key: 'documentTab',
as: 'button',
className: 'ui button',
content: type || intl.formatMessage(messages.document),
@@ -152,6 +153,7 @@ const Sidebar = (props) => {
},
!!blockTab && {
menuItem: {
+ key: 'blockTab',
as: 'button',
className: 'ui button',
content: intl.formatMessage(messages.block),
diff --git a/packages/volto/src/components/manage/Sidebar/SidebarPopup.jsx b/packages/volto/src/components/manage/Sidebar/SidebarPopup.jsx
index 14cd922a1f..58d6897fc3 100644
--- a/packages/volto/src/components/manage/Sidebar/SidebarPopup.jsx
+++ b/packages/volto/src/components/manage/Sidebar/SidebarPopup.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import { CSSTransition } from 'react-transition-group';
import PropTypes from 'prop-types';
import { doesNodeContainClick } from 'semantic-ui-react/dist/commonjs/lib';
@@ -32,9 +32,13 @@ const SidebarPopup = (props) => {
classNames="overlay-container"
unmountOnExit
>
-
-
-
+ <>
+ {document?.body &&
+ createPortal(
+ ,
+ document?.body,
+ )}
+ >
)}
{
classNames="sidebar-container"
unmountOnExit
>
-
-
-
+ <>
+ {createPortal(
+ ,
+ document.body,
+ )}
+ >
>
);
diff --git a/packages/volto/src/components/manage/Sidebar/SidebarPortal.jsx b/packages/volto/src/components/manage/Sidebar/SidebarPortal.jsx
index c99cb92fef..f77b80c2a7 100644
--- a/packages/volto/src/components/manage/Sidebar/SidebarPortal.jsx
+++ b/packages/volto/src/components/manage/Sidebar/SidebarPortal.jsx
@@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
/**
* Portal that wraps Sidebar components
@@ -16,8 +16,9 @@ const SidebarPortal = ({ children, selected, tab = 'sidebar-properties' }) => {
return (
<>
- {selected && (
-
+ {isClient &&
+ selected &&
+ createPortal(
-
- )}
+ ,
+ document.getElementById(tab),
+ )}
>
);
};
diff --git a/packages/volto/src/components/manage/Sidebar/__snapshots__/SidebarPopup.test.jsx.snap b/packages/volto/src/components/manage/Sidebar/__snapshots__/SidebarPopup.test.jsx.snap
index 6d383e2cda..8144871def 100644
--- a/packages/volto/src/components/manage/Sidebar/__snapshots__/SidebarPopup.test.jsx.snap
+++ b/packages/volto/src/components/manage/Sidebar/__snapshots__/SidebarPopup.test.jsx.snap
@@ -8,23 +8,29 @@ exports[`sidebar popup is not rendered when the block is not selected 1`] = `
timeout={500}
unmountOnExit={true}
>
-
-
+ >
+
+ Tested, but you shouldn't see this in the snapshot!
+
+ ,
+ "containerInfo": ,
+ "implementation": null,
+ "key": null,
+ }
+
`;
@@ -37,23 +43,29 @@ exports[`sidebar popup is rendered when the block is selected 1`] = `
timeout={500}
unmountOnExit={true}
>
-
-
+ >
+
+ Tested!
+
+ ,
+ "containerInfo": ,
+ "implementation": null,
+ "key": null,
+ }
+
`;
diff --git a/packages/volto/src/components/manage/Sidebar/__snapshots__/SidebarPortal.test.jsx.snap b/packages/volto/src/components/manage/Sidebar/__snapshots__/SidebarPortal.test.jsx.snap
index d90ec6d71b..8ef45208d7 100644
--- a/packages/volto/src/components/manage/Sidebar/__snapshots__/SidebarPortal.test.jsx.snap
+++ b/packages/volto/src/components/manage/Sidebar/__snapshots__/SidebarPortal.test.jsx.snap
@@ -2,34 +2,4 @@
exports[`sidebar portal is not rendered when the block is not selected 1`] = ``;
-exports[`sidebar portal is rendered when the block is selected 1`] = `
-
-
-
-
-
-`;
+exports[`sidebar portal is rendered when the block is selected 1`] = ``;
diff --git a/packages/volto/src/components/manage/Widgets/ArrayWidget.jsx b/packages/volto/src/components/manage/Widgets/ArrayWidget.jsx
index e4b9a5a0ea..c66f7d237a 100644
--- a/packages/volto/src/components/manage/Widgets/ArrayWidget.jsx
+++ b/packages/volto/src/components/manage/Widgets/ArrayWidget.jsx
@@ -325,27 +325,27 @@ class ArrayWidget extends Component {
this.props.vocabBaseUrl
? choices
: this.props.choices
- ? [
- ...choices,
- ...(this.props.noValueOption &&
- (this.props.default === undefined ||
- this.props.default === null)
- ? [
- {
- label: this.props.intl.formatMessage(
- messages.no_value,
- ),
- value: 'no-value',
- },
- ]
- : []),
- ]
- : [
- {
- label: this.props.intl.formatMessage(messages.no_value),
- value: 'no-value',
- },
- ]
+ ? [
+ ...choices,
+ ...(this.props.noValueOption &&
+ (this.props.default === undefined ||
+ this.props.default === null)
+ ? [
+ {
+ label: this.props.intl.formatMessage(
+ messages.no_value,
+ ),
+ value: 'no-value',
+ },
+ ]
+ : []),
+ ]
+ : [
+ {
+ label: this.props.intl.formatMessage(messages.no_value),
+ value: 'no-value',
+ },
+ ]
}
styles={customSelectStyles}
theme={selectTheme}
diff --git a/packages/volto/src/components/manage/Widgets/FileWidget.jsx b/packages/volto/src/components/manage/Widgets/FileWidget.jsx
index 34db736933..4a717b97de 100644
--- a/packages/volto/src/components/manage/Widgets/FileWidget.jsx
+++ b/packages/volto/src/components/manage/Widgets/FileWidget.jsx
@@ -84,8 +84,8 @@ const FileWidget = (props) => {
const imgsrc = value?.download
? `${flattenToAppURL(value?.download)}?id=${Date.now()}`
: null || value?.data
- ? `data:${value['content-type']};${value.encoding},${value.data}`
- : null;
+ ? `data:${value['content-type']};${value.encoding},${value.data}`
+ : null;
/**
* Drop handler
@@ -112,7 +112,7 @@ const FileWidget = (props) => {
if (imageMimetypes.includes(fields[1])) {
setFileType(true);
let imagePreview = document.getElementById(`field-${id}-image`);
- imagePreview.src = reader.result;
+ if (imagePreview) imagePreview.src = reader.result;
} else {
setFileType(false);
}
diff --git a/packages/volto/src/components/manage/Widgets/ObjectBrowserWidget.test.jsx b/packages/volto/src/components/manage/Widgets/ObjectBrowserWidget.test.jsx
index 70e1a4a53b..2783a93d5a 100644
--- a/packages/volto/src/components/manage/Widgets/ObjectBrowserWidget.test.jsx
+++ b/packages/volto/src/components/manage/Widgets/ObjectBrowserWidget.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
import ObjectBrowserWidget from './ObjectBrowserWidget';
@@ -14,7 +14,7 @@ test('renders a objectBrowser widget component', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
{
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
diff --git a/packages/volto/src/components/manage/Widgets/ObjectListWidget.test.js b/packages/volto/src/components/manage/Widgets/ObjectListWidget.test.js
index 161549dee1..8332bf129d 100644
--- a/packages/volto/src/components/manage/Widgets/ObjectListWidget.test.js
+++ b/packages/volto/src/components/manage/Widgets/ObjectListWidget.test.js
@@ -2,7 +2,6 @@ import React from 'react';
import { Provider } from 'react-intl-redux';
import { render } from '@testing-library/react';
import configureStore from 'redux-mock-store';
-import '@testing-library/jest-dom/extend-expect';
import ObjectListWidget from './ObjectListWidget';
jest.mock('@plone/volto/helpers/Loadable/Loadable');
diff --git a/packages/volto/src/components/manage/Widgets/ObjectWidget.test.jsx b/packages/volto/src/components/manage/Widgets/ObjectWidget.test.jsx
index b182d3f2bc..6f0d2d9e22 100644
--- a/packages/volto/src/components/manage/Widgets/ObjectWidget.test.jsx
+++ b/packages/volto/src/components/manage/Widgets/ObjectWidget.test.jsx
@@ -3,7 +3,6 @@ import renderer from 'react-test-renderer';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
import { render, fireEvent } from '@testing-library/react';
-import '@testing-library/jest-dom/extend-expect';
import ObjectWidget from './ObjectWidget';
const mockStore = configureStore();
diff --git a/packages/volto/src/components/manage/Widgets/RecurrenceWidget/RecurrenceWidget.jsx b/packages/volto/src/components/manage/Widgets/RecurrenceWidget/RecurrenceWidget.jsx
index 3b8a7b9ed8..53d776b81f 100644
--- a/packages/volto/src/components/manage/Widgets/RecurrenceWidget/RecurrenceWidget.jsx
+++ b/packages/volto/src/components/manage/Widgets/RecurrenceWidget/RecurrenceWidget.jsx
@@ -445,8 +445,8 @@ class RecurrenceWidget extends Component {
field === 'dtstart'
? value
: rruleSet.dtstart()
- ? rruleSet.dtstart()
- : this.moment().utc().toDate();
+ ? rruleSet.dtstart()
+ : this.moment().utc().toDate();
var exdates =
field === 'exdates' ? value : Object.assign([], rruleSet.exdates());
diff --git a/packages/volto/src/components/manage/Widgets/ReferenceWidget.jsx b/packages/volto/src/components/manage/Widgets/ReferenceWidget.jsx
index 78fa1b7b8e..4b4b402a8b 100644
--- a/packages/volto/src/components/manage/Widgets/ReferenceWidget.jsx
+++ b/packages/volto/src/components/manage/Widgets/ReferenceWidget.jsx
@@ -244,8 +244,8 @@ class ReferenceWidget extends Component {
)
: []
: value
- ? flattenToAppURL(value['@id'])
- : ''
+ ? flattenToAppURL(value['@id'])
+ : ''
}
onChange={(event, data) => {
return onChange(
diff --git a/packages/volto/src/components/manage/Widgets/SchemaWidget.test.jsx b/packages/volto/src/components/manage/Widgets/SchemaWidget.test.jsx
index 44b19cc1a0..4ded955e95 100644
--- a/packages/volto/src/components/manage/Widgets/SchemaWidget.test.jsx
+++ b/packages/volto/src/components/manage/Widgets/SchemaWidget.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
@@ -21,7 +21,7 @@ test('renders a schema widget component', () => {
},
});
- const component = renderer.create(
+ const { container } = render(
{
/>
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
diff --git a/packages/volto/src/components/manage/Widgets/SchemaWidgetFieldset.test.jsx b/packages/volto/src/components/manage/Widgets/SchemaWidgetFieldset.test.jsx
index 95af6d1e0c..62667d8373 100644
--- a/packages/volto/src/components/manage/Widgets/SchemaWidgetFieldset.test.jsx
+++ b/packages/volto/src/components/manage/Widgets/SchemaWidgetFieldset.test.jsx
@@ -1,6 +1,6 @@
import React from 'react';
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import SchemaWidgetFieldsetComponent from './SchemaWidgetFieldset';
jest.mock('@plone/volto/helpers/Loadable/Loadable');
@@ -10,7 +10,7 @@ beforeAll(
);
test('renders a contents item component', () => {
- const component = renderer.create(
+ const { container } = render(
x}>
{(provided, snapshot) => (
@@ -28,7 +28,7 @@ test('renders a contents item component', () => {
onShowEditFieldset={(x) => x}
onShowDeleteFieldset={(x) => x}
onClick={(x) => x}
- getItemStyle={(x) => x}
+ getItemStyle={(x) => ({})}
isDraggable={false}
isDisabled={false}
/>
@@ -38,6 +38,6 @@ test('renders a contents item component', () => {
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
diff --git a/packages/volto/src/components/manage/Widgets/SelectAutoComplete.jsx b/packages/volto/src/components/manage/Widgets/SelectAutoComplete.jsx
index 920bd94873..5f76fee3b7 100644
--- a/packages/volto/src/components/manage/Widgets/SelectAutoComplete.jsx
+++ b/packages/volto/src/components/manage/Widgets/SelectAutoComplete.jsx
@@ -277,12 +277,12 @@ export default compose(
return props.items?.choices
? { choices: props.items.choices, lang: state.intl.locale }
: vocabState
- ? {
- choices: vocabState,
- vocabBaseUrl,
- lang: state.intl.locale,
- }
- : { vocabBaseUrl, lang: state.intl.locale };
+ ? {
+ choices: vocabState,
+ vocabBaseUrl,
+ lang: state.intl.locale,
+ }
+ : { vocabBaseUrl, lang: state.intl.locale };
},
{ getVocabulary, getVocabularyTokenTitle },
),
diff --git a/packages/volto/src/components/manage/Widgets/SelectStyling.jsx b/packages/volto/src/components/manage/Widgets/SelectStyling.jsx
index 8cd811fd82..a9c337f9ab 100644
--- a/packages/volto/src/components/manage/Widgets/SelectStyling.jsx
+++ b/packages/volto/src/components/manage/Widgets/SelectStyling.jsx
@@ -150,10 +150,10 @@ export const customSelectStyles = {
color: state.isSelected
? '#007bc1'
: state.isDisabled
- ? '#b5b5b5'
- : state.isFocused
- ? '#4a4a4a'
- : 'inherit',
+ ? '#b5b5b5'
+ : state.isFocused
+ ? '#4a4a4a'
+ : 'inherit',
':active': {
backgroundColor: null,
},
diff --git a/packages/volto/src/components/manage/Widgets/SelectUtils.js b/packages/volto/src/components/manage/Widgets/SelectUtils.js
index 2e34fd867a..b5ad063c6c 100644
--- a/packages/volto/src/components/manage/Widgets/SelectUtils.js
+++ b/packages/volto/src/components/manage/Widgets/SelectUtils.js
@@ -28,8 +28,8 @@ export function convertValueToVocabQuery(value) {
isObject(v)
? v.value ?? v.token
: isString(v) || isBoolean(v)
- ? v
- : null,
+ ? v
+ : null,
)
.filter((f) => f !== null),
};
diff --git a/packages/volto/src/components/manage/Widgets/UrlWidget.test.jsx b/packages/volto/src/components/manage/Widgets/UrlWidget.test.jsx
index e9625d4f3f..35dfe39c35 100644
--- a/packages/volto/src/components/manage/Widgets/UrlWidget.test.jsx
+++ b/packages/volto/src/components/manage/Widgets/UrlWidget.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
@@ -15,7 +15,7 @@ test('renders an url widget component', () => {
},
});
- const component = renderer.create(
+ const { container } = render(
{
/>
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
diff --git a/packages/volto/src/components/manage/Widgets/VocabularyTermsWidget.test.jsx b/packages/volto/src/components/manage/Widgets/VocabularyTermsWidget.test.jsx
index 1d505fbe27..32e5ff4008 100644
--- a/packages/volto/src/components/manage/Widgets/VocabularyTermsWidget.test.jsx
+++ b/packages/volto/src/components/manage/Widgets/VocabularyTermsWidget.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
@@ -55,7 +55,7 @@ test('renders a dictionary widget component', () => {
},
],
};
- const component = renderer.create(
+ const { container } = render(
{
/>
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
diff --git a/packages/volto/src/components/manage/Widgets/__snapshots__/ArrayWidget.test.jsx.snap b/packages/volto/src/components/manage/Widgets/__snapshots__/ArrayWidget.test.jsx.snap
index a5b5ac4d20..5a9dbbe75c 100644
--- a/packages/volto/src/components/manage/Widgets/__snapshots__/ArrayWidget.test.jsx.snap
+++ b/packages/volto/src/components/manage/Widgets/__snapshots__/ArrayWidget.test.jsx.snap
@@ -251,9 +251,7 @@ exports[`renders an array widget component 1`] = `
"whiteSpace": "pre",
}
}
- >
-
-
+ />
diff --git a/packages/volto/src/components/manage/Widgets/__snapshots__/ObjectBrowserWidget.test.jsx.snap b/packages/volto/src/components/manage/Widgets/__snapshots__/ObjectBrowserWidget.test.jsx.snap
index 0b08cefc6c..5704b6974f 100644
--- a/packages/volto/src/components/manage/Widgets/__snapshots__/ObjectBrowserWidget.test.jsx.snap
+++ b/packages/volto/src/components/manage/Widgets/__snapshots__/ObjectBrowserWidget.test.jsx.snap
@@ -1,74 +1,60 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders a objectBrowser widget component 1`] = `
-
+
-
+
+
-
-
- No items selected
+
+ No items selected
+
+
-
-
diff --git a/packages/volto/src/components/manage/Widgets/__snapshots__/SchemaWidget.test.jsx.snap b/packages/volto/src/components/manage/Widgets/__snapshots__/SchemaWidget.test.jsx.snap
index 4ff936138b..0f8e642ef6 100644
--- a/packages/volto/src/components/manage/Widgets/__snapshots__/SchemaWidget.test.jsx.snap
+++ b/packages/volto/src/components/manage/Widgets/__snapshots__/SchemaWidget.test.jsx.snap
@@ -2,130 +2,99 @@
exports[`renders a schema widget component 1`] = `
-
+
-
-
+ style="background: transparent;"
+ />
-
-
-
-
+
-
-
+
+
diff --git a/packages/volto/src/components/manage/Widgets/__snapshots__/SchemaWidgetFieldset.test.jsx.snap b/packages/volto/src/components/manage/Widgets/__snapshots__/SchemaWidgetFieldset.test.jsx.snap
index 2ef48dc97a..6b64777f79 100644
--- a/packages/volto/src/components/manage/Widgets/__snapshots__/SchemaWidgetFieldset.test.jsx.snap
+++ b/packages/volto/src/components/manage/Widgets/__snapshots__/SchemaWidgetFieldset.test.jsx.snap
@@ -1,39 +1,34 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders a contents item component 1`] = `
-
`;
diff --git a/packages/volto/src/components/manage/Widgets/__snapshots__/TokenWidget.test.jsx.snap b/packages/volto/src/components/manage/Widgets/__snapshots__/TokenWidget.test.jsx.snap
index 3040534bdf..2d2d3857df 100644
--- a/packages/volto/src/components/manage/Widgets/__snapshots__/TokenWidget.test.jsx.snap
+++ b/packages/volto/src/components/manage/Widgets/__snapshots__/TokenWidget.test.jsx.snap
@@ -103,9 +103,7 @@ exports[`renders a token widget component 1`] = `
"whiteSpace": "pre",
}
}
- >
-
-
+ />
diff --git a/packages/volto/src/components/manage/Widgets/__snapshots__/UrlWidget.test.jsx.snap b/packages/volto/src/components/manage/Widgets/__snapshots__/UrlWidget.test.jsx.snap
index 3695fbc0c9..7b3e2e5222 100644
--- a/packages/volto/src/components/manage/Widgets/__snapshots__/UrlWidget.test.jsx.snap
+++ b/packages/volto/src/components/manage/Widgets/__snapshots__/UrlWidget.test.jsx.snap
@@ -1,77 +1,61 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders an url widget component 1`] = `
-
+
-
+
+
-
-
diff --git a/packages/volto/src/components/manage/Widgets/__snapshots__/VocabularyTermsWidget.test.jsx.snap b/packages/volto/src/components/manage/Widgets/__snapshots__/VocabularyTermsWidget.test.jsx.snap
index 7fbf01c87a..1874373aa8 100644
--- a/packages/volto/src/components/manage/Widgets/__snapshots__/VocabularyTermsWidget.test.jsx.snap
+++ b/packages/volto/src/components/manage/Widgets/__snapshots__/VocabularyTermsWidget.test.jsx.snap
@@ -1,386 +1,257 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders a dictionary widget component 1`] = `
-
+
@@ -108,11 +100,8 @@ exports[`ContextNavigation renders a navigation component with an active item wh
href="/folder2/folder21"
title=""
>
-
Folder21
-
-
-
Doc211
-
-
-
@@ -167,11 +151,7 @@ exports[`ContextNavigation renders a navigation component with an active item wh
`;
-exports[`ContextNavigation renders a navigation component with only one active item even if there are similar item names 1`] = `
-
-
-
-`;
+exports[`ContextNavigation renders a navigation component with only one active item even if there are similar item names 1`] = ``;
exports[`ContextNavigation renders a navigation slot component without active items 1`] = `
@@ -198,11 +178,8 @@ exports[`ContextNavigation renders a navigation slot component without active it
class="contenttype-undefined"
href="/front-page"
>
-
Welcome to Plone!
-
-
-
Blog
-
-
-
Users
-
-
diff --git a/packages/volto/src/components/theme/Pagination/__snapshots__/Pagination.test.jsx.snap b/packages/volto/src/components/theme/Pagination/__snapshots__/Pagination.test.jsx.snap
index d7816b0a8e..70e8398a6f 100644
--- a/packages/volto/src/components/theme/Pagination/__snapshots__/Pagination.test.jsx.snap
+++ b/packages/volto/src/components/theme/Pagination/__snapshots__/Pagination.test.jsx.snap
@@ -30,7 +30,6 @@ exports[`Pagination renders no pagination when only 1 page 1`] = `
viewBox=""
xmlns=""
/>
-
-
-
-
diff --git a/packages/volto/src/components/theme/PasswordReset/PasswordReset.test.jsx b/packages/volto/src/components/theme/PasswordReset/PasswordReset.test.jsx
index 31401e2388..e5432c97b9 100644
--- a/packages/volto/src/components/theme/PasswordReset/PasswordReset.test.jsx
+++ b/packages/volto/src/components/theme/PasswordReset/PasswordReset.test.jsx
@@ -22,6 +22,13 @@ describe('PasswordReset', () => {
locale: 'en',
messages: {},
},
+ content: {
+ data: {},
+ create: {
+ loading: false,
+ loaded: true,
+ },
+ },
});
const component = renderer.create(
diff --git a/packages/volto/src/components/theme/PasswordReset/RequestPasswordReset.test.jsx b/packages/volto/src/components/theme/PasswordReset/RequestPasswordReset.test.jsx
index b1b085c371..47a0345881 100644
--- a/packages/volto/src/components/theme/PasswordReset/RequestPasswordReset.test.jsx
+++ b/packages/volto/src/components/theme/PasswordReset/RequestPasswordReset.test.jsx
@@ -22,6 +22,13 @@ describe('RequestPasswordReset', () => {
locale: 'en',
messages: {},
},
+ content: {
+ data: {},
+ create: {
+ loading: false,
+ loaded: true,
+ },
+ },
});
const component = renderer.create(
diff --git a/packages/volto/src/components/theme/Register/Register.test.jsx b/packages/volto/src/components/theme/Register/Register.test.jsx
index 6b288fc81d..f78c37ecbf 100644
--- a/packages/volto/src/components/theme/Register/Register.test.jsx
+++ b/packages/volto/src/components/theme/Register/Register.test.jsx
@@ -22,6 +22,13 @@ describe('Register', () => {
locale: 'en',
messages: {},
},
+ content: {
+ data: {},
+ create: {
+ loading: false,
+ loaded: true,
+ },
+ },
});
const component = renderer.create(
diff --git a/packages/volto/src/components/theme/Search/Search.jsx b/packages/volto/src/components/theme/Search/Search.jsx
index e6d0341662..e5b9c17e74 100644
--- a/packages/volto/src/components/theme/Search/Search.jsx
+++ b/packages/volto/src/components/theme/Search/Search.jsx
@@ -10,7 +10,7 @@ import { compose } from 'redux';
import { UniversalLink } from '@plone/volto/components';
import { asyncConnect } from '@plone/volto/helpers';
import { FormattedMessage } from 'react-intl';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import { Container, Pagination, Button, Header } from 'semantic-ui-react';
import qs from 'query-string';
import classNames from 'classnames';
@@ -309,15 +309,15 @@ class Search extends Component {
- {this.state.isClient && (
-
+ {this.state.isClient &&
+ createPortal(
}
- />
-
- )}
+ />,
+ document.getElementById('toolbar'),
+ )}
);
}
diff --git a/packages/volto/src/components/theme/Search/Search.test.jsx b/packages/volto/src/components/theme/Search/Search.test.jsx
index b5d2416169..86a5a6cb00 100644
--- a/packages/volto/src/components/theme/Search/Search.test.jsx
+++ b/packages/volto/src/components/theme/Search/Search.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
import { MemoryRouter } from 'react-router-dom';
@@ -8,9 +8,10 @@ import { __test__ as Search } from './Search';
const mockStore = configureStore();
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() => ),
-}));
+jest.mock('../../manage/Toolbar/Toolbar', () =>
+ jest.fn(() => ),
+);
+
jest.mock('./SearchTags', () => jest.fn(() => ));
describe('Search', () => {
@@ -27,15 +28,16 @@ describe('Search', () => {
const history = {
location: { pathname: '/blog', search: '?SearchableText=blog' },
};
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
it('renders a search component', () => {
@@ -59,14 +61,15 @@ describe('Search', () => {
const history = {
location: { pathname: '/blog', search: '?SearchableText=blog' },
};
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/packages/volto/src/components/theme/Search/__snapshots__/Search.test.jsx.snap b/packages/volto/src/components/theme/Search/__snapshots__/Search.test.jsx.snap
index c65a7fb602..3f2a0de48f 100644
--- a/packages/volto/src/components/theme/Search/__snapshots__/Search.test.jsx.snap
+++ b/packages/volto/src/components/theme/Search/__snapshots__/Search.test.jsx.snap
@@ -1,120 +1,126 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Search renders a search component 1`] = `
-
+
-
-
-
- Search results for
-
- blog
-
-
-
-
- No results found
-
-
-
-
-
+ Search results for
+
+ blog
+
+
-
- My blog
-
+ id="search-tags"
+ />
+
+ No results found
-
-
-
-
-
+
+ Blog
+
+
+
+
+ My blog
+
+
+
+
+
+
+
+
+ id="toolbar"
+ >
+
+
`;
exports[`Search renders an empty search component 1`] = `
-
+
-
-
-
- Search results for
-
- blog
-
-
-
+ id="toolbar"
+ >
+
+
`;
diff --git a/packages/volto/src/components/theme/SlotRenderer/SlotRenderer.test.jsx b/packages/volto/src/components/theme/SlotRenderer/SlotRenderer.test.jsx
new file mode 100644
index 0000000000..ac915dee89
--- /dev/null
+++ b/packages/volto/src/components/theme/SlotRenderer/SlotRenderer.test.jsx
@@ -0,0 +1,55 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import { MemoryRouter } from 'react-router-dom';
+import SlotRenderer from './SlotRenderer';
+import config from '@plone/volto/registry';
+
+describe('SlotRenderer Component', () => {
+ const RouteConditionTrue = () => () => true;
+ const RouteConditionFalse = () => () => false;
+ const ContentTypeConditionTrue = () => () => true;
+ const ContentTypeConditionFalse = () => () => false;
+
+ test('renders a SlotRenderer component for the aboveContentTitle with two slots in the root', () => {
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: (props) => ,
+ predicates: [RouteConditionTrue()],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'save',
+ component: (props) => ,
+ predicates: [RouteConditionFalse(), ContentTypeConditionFalse()],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'edit',
+ component: (props) => ,
+ predicates: [RouteConditionFalse()],
+ });
+
+ config.registerSlotComponent({
+ slot: 'toolbar',
+ name: 'edit',
+ component: (props) => (
+
+ ),
+ predicates: [RouteConditionTrue(), ContentTypeConditionTrue()],
+ });
+
+ const { container } = render(
+
+
+ ,
+ );
+
+ const divSlot = container.querySelector('div');
+ expect(divSlot).toHaveClass('slot-component-true');
+ const asideSlot = container.querySelector('aside');
+ expect(asideSlot).toHaveClass('slot-component-true-predicates');
+ });
+});
diff --git a/packages/volto/src/components/theme/SlotRenderer/SlotRenderer.tsx b/packages/volto/src/components/theme/SlotRenderer/SlotRenderer.tsx
new file mode 100644
index 0000000000..40a4f9b298
--- /dev/null
+++ b/packages/volto/src/components/theme/SlotRenderer/SlotRenderer.tsx
@@ -0,0 +1,54 @@
+import { useLocation } from 'react-router-dom';
+import config from '@plone/volto/registry';
+
+import type { Content } from '@plone/types';
+
+/*
+Usage:
+
+*/
+
+const SlotRenderer = ({
+ name,
+ content,
+ navRoot,
+}: {
+ name: string;
+ content: Content;
+ navRoot?: Content;
+}) => {
+ const pathname = useLocation().pathname;
+
+ let slots = config.getSlot(name, {
+ content,
+ pathname,
+ // This is to cover the use case while adding a new content and we don't have
+ // have the navRoot information in the initial content. This will be
+ // useful for SlotRenderers rendered in the `Add` route.
+ navRoot: content?.['@components']?.navroot?.navroot || navRoot,
+ });
+
+ if (!slots) {
+ return null;
+ }
+
+ return (
+ <>
+ {slots.map(
+ ({
+ component,
+ name,
+ }: {
+ component: React.ComponentType;
+ name: string;
+ }) => {
+ // ^^ Weird compilation issue for Jest tests, that forced to re-declare the type above
+ const SlotComponent = component;
+ return ;
+ },
+ )}
+ >
+ );
+};
+
+export default SlotRenderer;
diff --git a/packages/volto/src/components/theme/View/View.jsx b/packages/volto/src/components/theme/View/View.jsx
index e836e452ab..418897e5f0 100644
--- a/packages/volto/src/components/theme/View/View.jsx
+++ b/packages/volto/src/components/theme/View/View.jsx
@@ -8,7 +8,7 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { compose } from 'redux';
import { Redirect } from 'react-router-dom';
-import { Portal } from 'react-portal';
+import { createPortal } from 'react-dom';
import { injectIntl } from 'react-intl';
import qs from 'query-string';
@@ -28,6 +28,7 @@ import {
} from '@plone/volto/helpers';
import config from '@plone/volto/registry';
+import SlotRenderer from '../SlotRenderer/SlotRenderer';
/**
* View container class.
@@ -244,6 +245,7 @@ class View extends Component {
: null
}
/>
+
+
{config.settings.showTags &&
this.props.content.subjects &&
this.props.content.subjects.length > 0 && (
@@ -266,11 +269,11 @@ class View extends Component {
{this.props.content.allow_discussion && (
)}
- {this.state.isClient && (
-
- } />
-
- )}
+ {this.state.isClient &&
+ createPortal(
+ } />,
+ document.getElementById('toolbar'),
+ )}
);
}
diff --git a/packages/volto/src/components/theme/View/View.test.jsx b/packages/volto/src/components/theme/View/View.test.jsx
index 66d02014c6..0ecb074695 100644
--- a/packages/volto/src/components/theme/View/View.test.jsx
+++ b/packages/volto/src/components/theme/View/View.test.jsx
@@ -1,5 +1,5 @@
import React, { useEffect } from 'react';
-import renderer from 'react-test-renderer';
+import { render } from '@testing-library/react';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
@@ -25,14 +25,18 @@ beforeAll(() => {
const mockStore = configureStore();
-jest.mock('react-portal', () => ({
- Portal: jest.fn(() => ),
-}));
+jest.mock('../../manage/Toolbar/Toolbar', () =>
+ jest.fn(() => ),
+);
+
jest.mock('../SocialSharing/SocialSharing', () =>
jest.fn(() => ),
);
jest.mock('../Comments/Comments', () => jest.fn(() => ));
jest.mock('../Tags/Tags', () => jest.fn(() => ));
+jest.mock('../SlotRenderer/SlotRenderer', () =>
+ jest.fn(() => ),
+);
jest.mock('../ContentMetadataTags/ContentMetadataTags', () =>
jest.fn(() => ),
);
@@ -147,13 +151,14 @@ describe('View', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
it('renders a summary view', () => {
@@ -167,13 +172,14 @@ describe('View', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
it('renders a tabular view', () => {
@@ -187,13 +193,14 @@ describe('View', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
it('renders a document view', () => {
@@ -207,13 +214,14 @@ describe('View', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { container } = render(
+
,
);
- const json = component.toJSON();
- expect(json).toMatchSnapshot();
+
+ expect(container).toMatchSnapshot();
});
it('renders a new view element if the @id changed', () => {
@@ -235,16 +243,18 @@ describe('View', () => {
messages: {},
},
});
- const component = renderer.create(
+ const { rerender } = render(
+
,
);
expect(instanceCount).toBe(1);
store.getState().content.data['@id'] = '/b';
- component.update(
+ rerender(
+
,
);
expect(instanceCount).toBe(2);
diff --git a/packages/volto/src/components/theme/View/__snapshots__/View.test.jsx.snap b/packages/volto/src/components/theme/View/__snapshots__/View.test.jsx.snap
index 544bbf3fa3..6a915aa342 100644
--- a/packages/volto/src/components/theme/View/__snapshots__/View.test.jsx.snap
+++ b/packages/volto/src/components/theme/View/__snapshots__/View.test.jsx.snap
@@ -1,51 +1,94 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`View renders a document view 1`] = `
-
+
-
+ id="view"
+ >
+
+
+
+
+
+ id="toolbar"
+ >
+
+
`;
exports[`View renders a summary view 1`] = `
-
+
-
+ id="view"
+ >
+
+
+
+
+
+ id="toolbar"
+ >
+
+
`;
exports[`View renders a tabular view 1`] = `
-
+
+ id="view"
+ >
+
+
+
+
+
+ id="toolbar"
+ >
+
+
+
+`;
+
+exports[`View renders an empty view 1`] = `
+
`;
-
-exports[`View renders an empty view 1`] = ``;
diff --git a/packages/volto/src/components/theme/Widgets/BooleanWidget.jsx b/packages/volto/src/components/theme/Widgets/BooleanWidget.jsx
index ac62a4ab5c..6abb15db0c 100644
--- a/packages/volto/src/components/theme/Widgets/BooleanWidget.jsx
+++ b/packages/volto/src/components/theme/Widgets/BooleanWidget.jsx
@@ -24,8 +24,8 @@ const BooleanWidget = ({ value, children, className, intl }) => {
: intl.formatMessage(messages.no),
)
: value
- ? intl.formatMessage(messages.yes)
- : intl.formatMessage(messages.no)}
+ ? intl.formatMessage(messages.yes)
+ : intl.formatMessage(messages.no)}
) : (
''
diff --git a/packages/volto/src/components/theme/Widgets/__snapshots__/ArrayWidget.test.js.snap b/packages/volto/src/components/theme/Widgets/__snapshots__/ArrayWidget.test.js.snap
index 5606114c33..14acae810a 100644
--- a/packages/volto/src/components/theme/Widgets/__snapshots__/ArrayWidget.test.js.snap
+++ b/packages/volto/src/components/theme/Widgets/__snapshots__/ArrayWidget.test.js.snap
@@ -4,7 +4,6 @@ exports[`ArrayWidget renders a full vocabulary array view widget component 1`] =
-
Foo
@@ -19,7 +18,6 @@ exports[`ArrayWidget renders a full vocabulary array view widget component with
-
Foo
@@ -38,7 +36,6 @@ exports[`ArrayWidget renders a simple array view widget component 1`] = `
-
foo
@@ -53,7 +50,6 @@ exports[`ArrayWidget renders a vocabulary array view widget component 1`] = `
-
Foo
@@ -64,4 +60,4 @@ exports[`ArrayWidget renders a vocabulary array view widget component 1`] = `
`;
-exports[`ArrayWidget renders an empty array view widget component 1`] = `""`;
+exports[`ArrayWidget renders an empty array view widget component 1`] = `null`;
diff --git a/packages/volto/src/components/theme/Widgets/__snapshots__/BooleanWidget.test.js.snap b/packages/volto/src/components/theme/Widgets/__snapshots__/BooleanWidget.test.js.snap
index 530cb8978b..6731e6cdbe 100644
--- a/packages/volto/src/components/theme/Widgets/__snapshots__/BooleanWidget.test.js.snap
+++ b/packages/volto/src/components/theme/Widgets/__snapshots__/BooleanWidget.test.js.snap
@@ -36,4 +36,4 @@ exports[`BooleanWidget renders a boolean true view widget component with childre
`;
-exports[`BooleanWidget renders an empty boolean view widget component 1`] = `""`;
+exports[`BooleanWidget renders an empty boolean view widget component 1`] = `null`;
diff --git a/packages/volto/src/components/theme/Widgets/__snapshots__/DateWidget.test.js.snap b/packages/volto/src/components/theme/Widgets/__snapshots__/DateWidget.test.js.snap
index 535c64414d..7dfb2d2935 100644
--- a/packages/volto/src/components/theme/Widgets/__snapshots__/DateWidget.test.js.snap
+++ b/packages/volto/src/components/theme/Widgets/__snapshots__/DateWidget.test.js.snap
@@ -26,4 +26,4 @@ exports[`DateWidget renders a date view widget component with children 1`] = `
`;
-exports[`DateWidget renders an empty date view widget component 1`] = `""`;
+exports[`DateWidget renders an empty date view widget component 1`] = `null`;
diff --git a/packages/volto/src/components/theme/Widgets/__snapshots__/DatetimeWidget.test.js.snap b/packages/volto/src/components/theme/Widgets/__snapshots__/DatetimeWidget.test.js.snap
index 86947ef154..6f97180b22 100644
--- a/packages/volto/src/components/theme/Widgets/__snapshots__/DatetimeWidget.test.js.snap
+++ b/packages/volto/src/components/theme/Widgets/__snapshots__/DatetimeWidget.test.js.snap
@@ -26,4 +26,4 @@ exports[`DatetimeWidget renders a date view widget component with children 1`] =
`;
-exports[`DatetimeWidget renders an empty date view widget component 1`] = `""`;
+exports[`DatetimeWidget renders an empty date view widget component 1`] = `null`;
diff --git a/packages/volto/src/components/theme/Widgets/__snapshots__/DescriptionWidget.test.js.snap b/packages/volto/src/components/theme/Widgets/__snapshots__/DescriptionWidget.test.js.snap
index 61e322ff75..e7b55c5f45 100644
--- a/packages/volto/src/components/theme/Widgets/__snapshots__/DescriptionWidget.test.js.snap
+++ b/packages/volto/src/components/theme/Widgets/__snapshots__/DescriptionWidget.test.js.snap
@@ -18,4 +18,4 @@ exports[`DescriptionWidget renders a description view widget component with chil
`;
-exports[`DescriptionWidget renders an empty description view widget component 1`] = `""`;
+exports[`DescriptionWidget renders an empty description view widget component 1`] = `null`;
diff --git a/packages/volto/src/components/theme/Widgets/__snapshots__/EmailWidget.test.js.snap b/packages/volto/src/components/theme/Widgets/__snapshots__/EmailWidget.test.js.snap
index 1a6eebaa10..e43bb4ed46 100644
--- a/packages/volto/src/components/theme/Widgets/__snapshots__/EmailWidget.test.js.snap
+++ b/packages/volto/src/components/theme/Widgets/__snapshots__/EmailWidget.test.js.snap
@@ -26,4 +26,4 @@ exports[`EmailWidget renders an email view widget component with children 1`] =
`;
-exports[`EmailWidget renders an empty email view widget component 1`] = `""`;
+exports[`EmailWidget renders an empty email view widget component 1`] = `null`;
diff --git a/packages/volto/src/components/theme/Widgets/__snapshots__/FileWidget.test.js.snap b/packages/volto/src/components/theme/Widgets/__snapshots__/FileWidget.test.js.snap
index f37b373c4d..c1ded79bd9 100644
--- a/packages/volto/src/components/theme/Widgets/__snapshots__/FileWidget.test.js.snap
+++ b/packages/volto/src/components/theme/Widgets/__snapshots__/FileWidget.test.js.snap
@@ -47,4 +47,4 @@ exports[`FileWidget renders a simple file view widget component 1`] = `
`;
-exports[`FileWidget renders an empty file view widget component 1`] = `""`;
+exports[`FileWidget renders an empty file view widget component 1`] = `null`;
diff --git a/packages/volto/src/components/theme/Widgets/__snapshots__/ImageWidget.test.js.snap b/packages/volto/src/components/theme/Widgets/__snapshots__/ImageWidget.test.js.snap
index 7d2e6d47ca..47437e40d1 100644
--- a/packages/volto/src/components/theme/Widgets/__snapshots__/ImageWidget.test.js.snap
+++ b/packages/volto/src/components/theme/Widgets/__snapshots__/ImageWidget.test.js.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`ImageWidget renders an empty image view widget component 1`] = `""`;
+exports[`ImageWidget renders an empty image view widget component 1`] = `null`;
exports[`ImageWidget renders an image view widget component 1`] = `
`;
-exports[`PasswordWidget renders an empty password view widget component 1`] = `""`;
+exports[`PasswordWidget renders an empty password view widget component 1`] = `null`;
diff --git a/packages/volto/src/components/theme/Widgets/__snapshots__/RelationWidget.test.js.snap b/packages/volto/src/components/theme/Widgets/__snapshots__/RelationWidget.test.js.snap
index d7c722318c..7fbf15d328 100644
--- a/packages/volto/src/components/theme/Widgets/__snapshots__/RelationWidget.test.js.snap
+++ b/packages/volto/src/components/theme/Widgets/__snapshots__/RelationWidget.test.js.snap
@@ -92,4 +92,4 @@ exports[`RelationWidget renders a relation view widget component 1`] = `
`;
-exports[`RelationWidget renders an empty relation view widget component 1`] = `""`;
+exports[`RelationWidget renders an empty relation view widget component 1`] = `null`;
diff --git a/packages/volto/src/components/theme/Widgets/__snapshots__/RelationsWidget.test.js.snap b/packages/volto/src/components/theme/Widgets/__snapshots__/RelationsWidget.test.js.snap
index 4b2b53ac47..7f5a2f0070 100644
--- a/packages/volto/src/components/theme/Widgets/__snapshots__/RelationsWidget.test.js.snap
+++ b/packages/volto/src/components/theme/Widgets/__snapshots__/RelationsWidget.test.js.snap
@@ -264,4 +264,4 @@ exports[`RelationsWidget renders a vocabulary relations view widget component 1`
`;
-exports[`RelationsWidget renders an empty relations view widget component 1`] = `""`;
+exports[`RelationsWidget renders an empty relations view widget component 1`] = `null`;
diff --git a/packages/volto/src/components/theme/Widgets/__snapshots__/RichTextWidget.test.js.snap b/packages/volto/src/components/theme/Widgets/__snapshots__/RichTextWidget.test.js.snap
index 565d050626..00ea582f44 100644
--- a/packages/volto/src/components/theme/Widgets/__snapshots__/RichTextWidget.test.js.snap
+++ b/packages/volto/src/components/theme/Widgets/__snapshots__/RichTextWidget.test.js.snap
@@ -22,4 +22,4 @@ exports[`RichTextWidget renders a rich text view widget component with children
/>
`;
-exports[`RichTextWidget renders an empty rich text view widget component 1`] = `""`;
+exports[`RichTextWidget renders an empty rich text view widget component 1`] = `null`;
diff --git a/packages/volto/src/components/theme/Widgets/__snapshots__/SelectWidget.test.js.snap b/packages/volto/src/components/theme/Widgets/__snapshots__/SelectWidget.test.js.snap
index e5d9e555b6..dd913ad14c 100644
--- a/packages/volto/src/components/theme/Widgets/__snapshots__/SelectWidget.test.js.snap
+++ b/packages/volto/src/components/theme/Widgets/__snapshots__/SelectWidget.test.js.snap
@@ -26,4 +26,4 @@ exports[`SelectWidget renders a select view widget component 1`] = `
`;
-exports[`SelectWidget renders an empty select view widget component 1`] = `""`;
+exports[`SelectWidget renders an empty select view widget component 1`] = `null`;
diff --git a/packages/volto/src/components/theme/Widgets/__snapshots__/TextWidget.test.js.snap b/packages/volto/src/components/theme/Widgets/__snapshots__/TextWidget.test.js.snap
index 5129b795b8..35aab38e8d 100644
--- a/packages/volto/src/components/theme/Widgets/__snapshots__/TextWidget.test.js.snap
+++ b/packages/volto/src/components/theme/Widgets/__snapshots__/TextWidget.test.js.snap
@@ -28,4 +28,4 @@ exports[`TextWidget renders a text view widget component with children 1`] = `
`;
-exports[`TextWidget renders an empty text view widget component 1`] = `""`;
+exports[`TextWidget renders an empty text view widget component 1`] = `null`;
diff --git a/packages/volto/src/components/theme/Widgets/__snapshots__/TitleWidget.test.js.snap b/packages/volto/src/components/theme/Widgets/__snapshots__/TitleWidget.test.js.snap
index efd0541d37..c911ccadae 100644
--- a/packages/volto/src/components/theme/Widgets/__snapshots__/TitleWidget.test.js.snap
+++ b/packages/volto/src/components/theme/Widgets/__snapshots__/TitleWidget.test.js.snap
@@ -18,4 +18,4 @@ exports[`TitleWidget renders a title view widget component with children 1`] = `
`;
-exports[`TitleWidget renders an empty title view widget component 1`] = `""`;
+exports[`TitleWidget renders an empty title view widget component 1`] = `null`;
diff --git a/packages/volto/src/components/theme/Widgets/__snapshots__/TokenWidget.test.js.snap b/packages/volto/src/components/theme/Widgets/__snapshots__/TokenWidget.test.js.snap
index ca60a25602..c651a7a33e 100644
--- a/packages/volto/src/components/theme/Widgets/__snapshots__/TokenWidget.test.js.snap
+++ b/packages/volto/src/components/theme/Widgets/__snapshots__/TokenWidget.test.js.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`TokenWidget renders an empty tags view widget component 1`] = `""`;
+exports[`TokenWidget renders an empty tags view widget component 1`] = `null`;
exports[`TokenWidget renders tags view widget component 1`] = `
`;
-exports[`UrlWidget renders an empty URL view widget component 1`] = `""`;
+exports[`UrlWidget renders an empty URL view widget component 1`] = `null`;
diff --git a/packages/volto/src/config/Loadables.jsx b/packages/volto/src/config/Loadables.jsx
index 17ab692f14..76c31156e5 100644
--- a/packages/volto/src/config/Loadables.jsx
+++ b/packages/volto/src/config/Loadables.jsx
@@ -39,22 +39,22 @@ export const loadables = {
// draftjs libs
immutableLib: loadable.lib(() => import('immutable')),
draftJs: loadable.lib(() => import('draft-js')),
- draftJsLibIsSoftNewlineEvent: loadable.lib(() =>
- import('draft-js/lib/isSoftNewlineEvent'),
+ draftJsLibIsSoftNewlineEvent: loadable.lib(
+ () => import('draft-js/lib/isSoftNewlineEvent'),
),
draftJsFilters: loadable.lib(() => import('draftjs-filters')),
- draftJsInlineToolbarPlugin: loadable.lib(() =>
- import('draft-js-inline-toolbar-plugin'),
+ draftJsInlineToolbarPlugin: loadable.lib(
+ () => import('draft-js-inline-toolbar-plugin'),
),
draftJsImportHtml: loadable.lib(() => import('draft-js-import-html')),
- draftJsBlockBreakoutPlugin: loadable.lib(() =>
- import('draft-js-block-breakout-plugin'),
+ draftJsBlockBreakoutPlugin: loadable.lib(
+ () => import('draft-js-block-breakout-plugin'),
),
- draftJsCreateInlineStyleButton: loadable.lib(() =>
- import('draft-js-buttons/lib/utils/createInlineStyleButton'),
+ draftJsCreateInlineStyleButton: loadable.lib(
+ () => import('draft-js-buttons/lib/utils/createInlineStyleButton'),
),
- draftJsCreateBlockStyleButton: loadable.lib(() =>
- import('draft-js-buttons/lib/utils/createBlockStyleButton'),
+ draftJsCreateBlockStyleButton: loadable.lib(
+ () => import('draft-js-buttons/lib/utils/createBlockStyleButton'),
),
draftJsPluginsUtils: loadable.lib(() => import('draft-js-plugins-utils')),
};
diff --git a/packages/volto/src/config/Views.jsx b/packages/volto/src/config/Views.jsx
index eaa09f8ec8..bb60818ece 100644
--- a/packages/volto/src/config/Views.jsx
+++ b/packages/volto/src/config/Views.jsx
@@ -18,8 +18,8 @@ import Unauthorized from '@plone/volto/components/theme/Unauthorized/Unauthorize
import Forbidden from '@plone/volto/components/theme/Forbidden/Forbidden';
import ServerError from '@plone/volto/components/theme/Error/ServerError';
-const EventView = loadable(() =>
- import('@plone/volto/components/theme/View/EventView'),
+const EventView = loadable(
+ () => import('@plone/volto/components/theme/View/EventView'),
);
defineMessages({
diff --git a/packages/volto/src/config/Widgets.jsx b/packages/volto/src/config/Widgets.jsx
index d51544e244..9abee081a6 100644
--- a/packages/volto/src/config/Widgets.jsx
+++ b/packages/volto/src/config/Widgets.jsx
@@ -55,13 +55,14 @@ import TitleViewWidget from '@plone/volto/components/theme/Widgets/TitleWidget';
import TokenViewWidget from '@plone/volto/components/theme/Widgets/TokenWidget';
import UrlViewWidget from '@plone/volto/components/theme/Widgets/UrlWidget';
-export const DatetimeWidget = loadable(() =>
- import('@plone/volto/components/manage/Widgets/DatetimeWidget'),
+export const DatetimeWidget = loadable(
+ () => import('@plone/volto/components/manage/Widgets/DatetimeWidget'),
);
-export const RecurrenceWidget = loadable(() =>
- import(
- '@plone/volto/components/manage/Widgets/RecurrenceWidget/RecurrenceWidget'
- ),
+export const RecurrenceWidget = loadable(
+ () =>
+ import(
+ '@plone/volto/components/manage/Widgets/RecurrenceWidget/RecurrenceWidget'
+ ),
);
// Widgets mapping
diff --git a/packages/volto/src/config/index.js b/packages/volto/src/config/index.js
index 8c295e5a1c..9b394a473c 100644
--- a/packages/volto/src/config/index.js
+++ b/packages/volto/src/config/index.js
@@ -75,7 +75,7 @@ let config = {
okRoute: '/ok',
apiPath,
apiExpanders: [
- // Added here for documentation purposes, addded at the end because it
+ // Added here for documentation purposes, added at the end because it
// depends on a value of this object.
// Add the following expanders for only issuing a single request.
// https://6.docs.plone.org/volto/configuration/settings-reference.html#term-apiExpanders
@@ -223,6 +223,7 @@ let config = {
},
addonRoutes: [],
addonReducers: {},
+ slots: {},
components,
};
@@ -250,5 +251,6 @@ ConfigRegistry.widgets = config.widgets;
ConfigRegistry.addonRoutes = config.addonRoutes;
ConfigRegistry.addonReducers = config.addonReducers;
ConfigRegistry.components = config.components;
+ConfigRegistry.slots = config.slots;
applyAddonConfiguration(ConfigRegistry);
diff --git a/packages/volto/src/helpers/AsyncConnect/AsyncConnect.test.js b/packages/volto/src/helpers/AsyncConnect/AsyncConnect.test.js
index 2b6a371334..fcf54dfa7c 100644
--- a/packages/volto/src/helpers/AsyncConnect/AsyncConnect.test.js
+++ b/packages/volto/src/helpers/AsyncConnect/AsyncConnect.test.js
@@ -12,8 +12,6 @@ import { AsyncConnectWithContext, AsyncConnect } from './AsyncConnect'; // , Asy
import { asyncConnect, loadOnServer } from './';
import { matchAllRoutes } from './utils';
-import '@testing-library/jest-dom/extend-expect';
-
describe('', () => {
const initialEmptyState = {
router: {
diff --git a/packages/volto/src/helpers/Blocks/Blocks.js b/packages/volto/src/helpers/Blocks/Blocks.js
index b912f88291..06d17c501c 100644
--- a/packages/volto/src/helpers/Blocks/Blocks.js
+++ b/packages/volto/src/helpers/Blocks/Blocks.js
@@ -483,26 +483,26 @@ export function applySchemaDefaults({ data = {}, schema, intl }) {
[currentField]: schema.properties[currentField].default,
}
: intl &&
- schema.properties[currentField].schema &&
- !(schema.properties[currentField].widget === 'object_list') // TODO: this should be renamed as itemSchema
- ? {
- ...accumulator,
- [currentField]: {
- ...applySchemaDefaults({
- data: { ...data[currentField], ...accumulator[currentField] },
- schema:
- typeof schema.properties[currentField].schema === 'function'
- ? schema.properties[currentField].schema({
- data: accumulator[currentField],
- formData: accumulator[currentField],
- intl,
- })
- : schema.properties[currentField].schema,
- intl,
- }),
- },
- }
- : accumulator;
+ schema.properties[currentField].schema &&
+ !(schema.properties[currentField].widget === 'object_list') // TODO: this should be renamed as itemSchema
+ ? {
+ ...accumulator,
+ [currentField]: {
+ ...applySchemaDefaults({
+ data: { ...data[currentField], ...accumulator[currentField] },
+ schema:
+ typeof schema.properties[currentField].schema === 'function'
+ ? schema.properties[currentField].schema({
+ data: accumulator[currentField],
+ formData: accumulator[currentField],
+ intl,
+ })
+ : schema.properties[currentField].schema,
+ intl,
+ }),
+ },
+ }
+ : accumulator;
}, {}),
data,
);
diff --git a/packages/volto/src/helpers/FormValidation/FormValidation.js b/packages/volto/src/helpers/FormValidation/FormValidation.js
index df276bf11a..95c470fa77 100644
--- a/packages/volto/src/helpers/FormValidation/FormValidation.js
+++ b/packages/volto/src/helpers/FormValidation/FormValidation.js
@@ -157,7 +157,7 @@ const widgetValidation = {
* The string that comes my not be a valid JSON
* @param {string} requestItem
*/
-const tryParseJSON = (requestItem) => {
+export const tryParseJSON = (requestItem) => {
let resultObj = null;
try {
resultObj = JSON.parse(requestItem);
diff --git a/packages/volto/src/helpers/Html/Html.jsx b/packages/volto/src/helpers/Html/Html.jsx
index 22fec9a59a..d4c79ee2de 100644
--- a/packages/volto/src/helpers/Html/Html.jsx
+++ b/packages/volto/src/helpers/Html/Html.jsx
@@ -143,8 +143,8 @@ class Html extends Component {
rel: !criticalCss
? elem.props.rel
: elem.props.as === 'style'
- ? 'prefetch'
- : elem.props.rel,
+ ? 'prefetch'
+ : elem.props.rel,
}),
)}
{/* Styles in development are loaded with Webpack's style-loader, in production,
diff --git a/packages/volto/src/helpers/Html/__snapshots__/Html.test.jsx.snap b/packages/volto/src/helpers/Html/__snapshots__/Html.test.jsx.snap
index 08b1b54bbd..08d9709f22 100644
--- a/packages/volto/src/helpers/Html/__snapshots__/Html.test.jsx.snap
+++ b/packages/volto/src/helpers/Html/__snapshots__/Html.test.jsx.snap
@@ -8,11 +8,6 @@ exports[`Html renders a html component 1`] = `
-
-
-
-
-
-
-
-
-
-