@@ -30,6 +30,7 @@ code_dir: downloads/code
|
|||||||
i18n_dir: :lang
|
i18n_dir: :lang
|
||||||
skip_render:
|
skip_render:
|
||||||
- "*.txt"
|
- "*.txt"
|
||||||
|
- admin/**/*
|
||||||
|
|
||||||
# Writing
|
# Writing
|
||||||
new_post_name: :title.md # File name of new posts
|
new_post_name: :title.md # File name of new posts
|
||||||
|
|||||||
19
source/admin/.editorconfig
Normal file
19
source/admin/.editorconfig
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# https://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.js]
|
||||||
|
quote_type = single
|
||||||
|
spaces_around_operators = true
|
||||||
|
|
||||||
|
[*.css]
|
||||||
|
quote_type = single
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
105
source/admin/.eslintrc.js
Normal file
105
source/admin/.eslintrc.js
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const packages = fs
|
||||||
|
.readdirSync(`${__dirname}/packages`, { withFileTypes: true })
|
||||||
|
.filter(dirent => dirent.isDirectory())
|
||||||
|
.map(dirent => dirent.name);
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
parser: 'babel-eslint',
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:react/recommended',
|
||||||
|
'plugin:cypress/recommended',
|
||||||
|
'prettier',
|
||||||
|
'plugin:import/recommended',
|
||||||
|
],
|
||||||
|
env: {
|
||||||
|
es6: true,
|
||||||
|
browser: true,
|
||||||
|
node: true,
|
||||||
|
jest: true,
|
||||||
|
'cypress/globals': true,
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
DECAP_CMS_VERSION: false,
|
||||||
|
DECAP_CMS_APP_VERSION: false,
|
||||||
|
DECAP_CMS_CORE_VERSION: false,
|
||||||
|
CMS_ENV: false,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'no-console': [0],
|
||||||
|
'react/prop-types': [0],
|
||||||
|
'import/no-named-as-default': 0,
|
||||||
|
'import/order': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
'newlines-between': 'always',
|
||||||
|
groups: [['builtin', 'external'], ['internal', 'parent', 'sibling', 'index'], ['type']],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'no-duplicate-imports': 'error',
|
||||||
|
'@emotion/no-vanilla': 'error',
|
||||||
|
'@emotion/pkg-renaming': 'error',
|
||||||
|
'@emotion/import-from-emotion': 'error',
|
||||||
|
'@emotion/styled-import': 'error',
|
||||||
|
'require-atomic-updates': [0],
|
||||||
|
'object-shorthand': ['error', 'always'],
|
||||||
|
'func-style': ['error', 'declaration'],
|
||||||
|
'prefer-const': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
destructuring: 'all',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'unicorn/prefer-string-slice': 'error',
|
||||||
|
'react/no-unknown-property': ['error', { ignore: ['css', 'bold', 'italic', 'delete'] }],
|
||||||
|
},
|
||||||
|
plugins: ['babel', '@emotion', 'cypress', 'unicorn'],
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: 'detect',
|
||||||
|
},
|
||||||
|
'import/resolver': {
|
||||||
|
node: {
|
||||||
|
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'import/core-modules': [...packages, 'decap-cms-app/dist/esm'],
|
||||||
|
},
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ['*.ts', '*.tsx'],
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:react/recommended',
|
||||||
|
'plugin:cypress/recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'prettier',
|
||||||
|
'plugin:import/recommended',
|
||||||
|
'plugin:import/typescript',
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 2018,
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'no-duplicate-imports': [0], // handled by @typescript-eslint
|
||||||
|
'@typescript-eslint/ban-types': [0], // TODO enable in future
|
||||||
|
'@typescript-eslint/no-non-null-assertion': [0],
|
||||||
|
'@typescript-eslint/consistent-type-imports': 'error',
|
||||||
|
'@typescript-eslint/explicit-function-return-type': [0],
|
||||||
|
'@typescript-eslint/explicit-module-boundary-types': [0],
|
||||||
|
'@typescript-eslint/no-duplicate-imports': 'error',
|
||||||
|
'@typescript-eslint/no-use-before-define': [
|
||||||
|
'error',
|
||||||
|
{ functions: false, classes: true, variables: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
1
source/admin/.gitattributes
vendored
Normal file
1
source/admin/.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
8
source/admin/.github/.kodiak.toml
vendored
Normal file
8
source/admin/.github/.kodiak.toml
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
version = 1
|
||||||
|
|
||||||
|
[merge.automerge_dependencies]
|
||||||
|
versions = ["minor", "patch"]
|
||||||
|
usernames = ["renovate"]
|
||||||
|
|
||||||
|
[approve]
|
||||||
|
auto_approve_usernames = ["renovate"]
|
||||||
1
source/admin/.github/CODEOWNERS
vendored
Normal file
1
source/admin/.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
* @decaporg/maintainers
|
||||||
2
source/admin/.github/FUNDING.yml
vendored
Normal file
2
source/admin/.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
github: decaporg
|
||||||
|
open_collective: decap
|
||||||
48
source/admin/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
48
source/admin/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Report a problem you are experiencing
|
||||||
|
title: 'Please replace with a clear and descriptive title'
|
||||||
|
labels: 'type: bug'
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
If you are reporting a new issue, make sure that we do not have any duplicates already open. You can ensure this by searching the issue list for this repository. If there is a duplicate, please add a comment to the existing issue instead.
|
||||||
|
|
||||||
|
Please include as much of the information requested below as possible. If you fail to provide the requested information within 7 days, we cannot debug your issue and will close it. We will, however, reopen it if you later provide the information.
|
||||||
|
|
||||||
|
If you have an issue that can be shown visually, please provide a screenshot or GIF of the problem as well.
|
||||||
|
-->
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
<!-- A clear and concise description of what the bug is. -->
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
<!--
|
||||||
|
Steps to reproduce the behavior. For example:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
-->
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
<!-- A clear and concise description of what you expected to happen. -->
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||||
|
|
||||||
|
**Applicable Versions:**
|
||||||
|
<!--You can find the CMS version by checking your web browser's developer tools console while in the CMS. -->
|
||||||
|
- Decap CMS version: [e.g. 2.0.4]
|
||||||
|
- Git provider: [e.g. GitHub, BitBucket]
|
||||||
|
- OS: [e.g. Windows 7]
|
||||||
|
- Browser version [e.g. chrome 22, safari 11]
|
||||||
|
<!-- If using NPM: -->
|
||||||
|
- Node.JS version:
|
||||||
|
|
||||||
|
**CMS configuration**
|
||||||
|
<!-- Please link or paste your CMS `config.yml` here. -->
|
||||||
|
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
<!-- Add any other context about the problem here. -->
|
||||||
26
source/admin/.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
26
source/admin/.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: 'Please replace with a clear and descriptive title'
|
||||||
|
labels: 'type: feature'
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Please make sure that we do not have any requests for this feature already open. You can ensure this by searching the issue list for this repository. If there is a duplicate, please add a comment to the existing issue instead.
|
||||||
|
-->
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
<!--
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
-->
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
<!-- A clear and concise description of what you want to happen. -->
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
<!--
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
-->
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
<!-- Add any other context or screenshots about the feature request here. -->
|
||||||
34
source/admin/.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
34
source/admin/.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<!--
|
||||||
|
Thanks for submitting a pull request!
|
||||||
|
|
||||||
|
Please make sure you've read and understood our contributing guidelines here:
|
||||||
|
https://github.com/decaporg/decap-cms/blob/main/CONTRIBUTING.md
|
||||||
|
|
||||||
|
If this is a bug fix, make sure your description includes "fixes #xxxx", or
|
||||||
|
"closes #xxxx", where #xxxx is the issue number.
|
||||||
|
|
||||||
|
Please provide enough information so that others can review your pull request.
|
||||||
|
The first two fields are mandatory:
|
||||||
|
-->
|
||||||
|
|
||||||
|
**Summary**
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Explain the **motivation** for making this change.
|
||||||
|
What existing problem does the pull request solve?
|
||||||
|
-->
|
||||||
|
|
||||||
|
**Test plan**
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Demonstrate the code is solid.
|
||||||
|
Example: The exact commands you ran and their output, screenshots / videos if the pull request changes UI.
|
||||||
|
-->
|
||||||
|
|
||||||
|
**Checklist**
|
||||||
|
|
||||||
|
Please add a `x` inside each checkbox:
|
||||||
|
|
||||||
|
- [ ] I have read the [contribution guidelines](https://github.com/decaporg/decap-cms/blob/main/CONTRIBUTING.md).
|
||||||
|
|
||||||
|
**A picture of a cute animal (not mandatory but encouraged)**
|
||||||
17
source/admin/.github/stale.yml
vendored
Normal file
17
source/admin/.github/stale.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Number of days of inactivity before an issue becomes stale
|
||||||
|
daysUntilStale: 60
|
||||||
|
# Number of days of inactivity before a stale issue is closed
|
||||||
|
daysUntilClose: 14
|
||||||
|
# Issues with these labels will never be considered stale
|
||||||
|
exemptLabels:
|
||||||
|
- pinned
|
||||||
|
- security
|
||||||
|
# Label to use when marking an issue as stale
|
||||||
|
staleLabel: 'status: stale'
|
||||||
|
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||||
|
markComment: >
|
||||||
|
This issue has been automatically marked as stale because it has not had
|
||||||
|
recent activity. It will be closed if no further activity occurs. Thank you
|
||||||
|
for your contributions.
|
||||||
|
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||||
|
closeComment: false
|
||||||
33
source/admin/.github/workflows/create-release.yml
vendored
Normal file
33
source/admin/.github/workflows/create-release.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
name: Create release
|
||||||
|
|
||||||
|
on:
|
||||||
|
create
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
create-release:
|
||||||
|
name: Create GitHub Release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: startsWith(github.ref, 'refs/tags/decap-cms@')
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Get semver number
|
||||||
|
id: get_semver
|
||||||
|
env:
|
||||||
|
TAG_NAME: ${{ github.ref }}
|
||||||
|
run: echo "::set-output name=pkg::${TAG_NAME:10}"
|
||||||
|
|
||||||
|
- name: Create release on GitHub API
|
||||||
|
id: create_release
|
||||||
|
uses: actions/create-release@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
tag_name: ${{ github.ref }}
|
||||||
|
release_name: ${{ steps.get_semver.outputs.pkg }}
|
||||||
|
body: |
|
||||||
|
:scroll: [Changelog](https://github.com/${{ github.repository }}/blob/${{ steps.get_semver.outputs.pkg }}/CHANGELOG.md)
|
||||||
|
draft: false
|
||||||
|
prerelease: false
|
||||||
30
source/admin/.github/workflows/labeler.yml
vendored
Normal file
30
source/admin/.github/workflows/labeler.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
name: Label PR
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, edited]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
label-pr:
|
||||||
|
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: netlify/pr-labeler-action@v1.0.0
|
||||||
|
if: startsWith(github.event.pull_request.title, 'fix')
|
||||||
|
with:
|
||||||
|
token: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
|
label: 'type: bug'
|
||||||
|
- uses: netlify/pr-labeler-action@v1.0.0
|
||||||
|
if: startsWith(github.event.pull_request.title, 'chore')
|
||||||
|
with:
|
||||||
|
token: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
|
label: 'type: chore'
|
||||||
|
- uses: netlify/pr-labeler-action@v1.0.0
|
||||||
|
if: startsWith(github.event.pull_request.title, 'feat')
|
||||||
|
with:
|
||||||
|
token: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
|
label: 'type: feature'
|
||||||
|
- uses: netlify/pr-labeler-action@v1.0.0
|
||||||
|
if: startsWith(github.event.pull_request.title, 'security')
|
||||||
|
with:
|
||||||
|
token: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
|
label: 'type: security'
|
||||||
62
source/admin/.github/workflows/nodejs.yml
vendored
Normal file
62
source/admin/.github/workflows/nodejs.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
name: Node CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, reopened]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
changes:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
cms: ${{ steps.filter.outputs.cms }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: dorny/paths-filter@v2
|
||||||
|
id: filter
|
||||||
|
with:
|
||||||
|
filters: |
|
||||||
|
cms:
|
||||||
|
- '!website/**'
|
||||||
|
|
||||||
|
build:
|
||||||
|
needs: changes
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [macos-latest, windows-latest, ubuntu-latest]
|
||||||
|
node-version: [20.x, 22.x]
|
||||||
|
fail-fast: true
|
||||||
|
if: ${{ needs.changes.outputs.cms == 'true' }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
check-latest: true
|
||||||
|
cache: 'npm'
|
||||||
|
- name: log versions
|
||||||
|
run: node --version && npm --version && yarn --version
|
||||||
|
- name: install dependencies
|
||||||
|
run: npm ci
|
||||||
|
- name: run unit tests
|
||||||
|
run: npm run test:ci
|
||||||
|
- name: build demo site
|
||||||
|
run: npm run build:demo
|
||||||
|
- name: run e2e tests
|
||||||
|
if: matrix.os == 'ubuntu-latest' && matrix.node-version == '22.x'
|
||||||
|
run: npm run test:e2e:run-ci
|
||||||
|
env:
|
||||||
|
IS_FORK: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true || github.repository_owner != 'decaporg' }}
|
||||||
|
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||||
|
NODE_OPTIONS: --max-old-space-size=4096
|
||||||
|
TZ: Europe/Amsterdam
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
if: matrix.os == 'ubuntu-latest' && matrix.node-version == '22.x' && failure()
|
||||||
|
with:
|
||||||
|
name: cypress-results
|
||||||
|
path: |
|
||||||
|
cypress/screenshots
|
||||||
|
cypress/videos
|
||||||
|
|
||||||
22
source/admin/.gitignore
vendored
Normal file
22
source/admin/.gitignore
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
dist/
|
||||||
|
bin/
|
||||||
|
public/
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log
|
||||||
|
.DS_Store
|
||||||
|
.tern-project
|
||||||
|
yarn-error.log
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
manifest.yml
|
||||||
|
.imdone/
|
||||||
|
cypress/videos
|
||||||
|
cypress/screenshots
|
||||||
|
__diff_output__
|
||||||
|
coverage/
|
||||||
|
.cache
|
||||||
|
*.log
|
||||||
|
.env
|
||||||
|
.temp/
|
||||||
|
storybook-static/
|
||||||
|
.nx
|
||||||
4
source/admin/.husky/commit-msg
Normal file
4
source/admin/.husky/commit-msg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npx --no-install commitlint --edit $1
|
||||||
2
source/admin/.npmrc
Normal file
2
source/admin/.npmrc
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
legacy-peer-deps=true
|
||||||
|
lockfileVersion=3
|
||||||
1
source/admin/.nvmrc
Normal file
1
source/admin/.nvmrc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
--lts
|
||||||
9
source/admin/.prettierignore
Normal file
9
source/admin/.prettierignore
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
dist/
|
||||||
|
bin/
|
||||||
|
public/
|
||||||
|
.cache/
|
||||||
|
packages/decap-cms-backend-github/src/fragmentTypes.js
|
||||||
|
packages/decap-cms-backend-gitlab/src/AuthenticationPage.js
|
||||||
|
packages/decap-cms-backend-proxy/src/implementation.ts
|
||||||
|
|
||||||
|
/.nx/cache
|
||||||
6
source/admin/.prettierrc
Normal file
6
source/admin/.prettierrc
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"trailingComma": "all",
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 100
|
||||||
|
}
|
||||||
7
source/admin/.storybook/main.js
Normal file
7
source/admin/.storybook/main.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
stories: [
|
||||||
|
'../packages/decap-cms-core/src/**/*.stories.js',
|
||||||
|
'../packages/decap-cms-ui-default/src/**/*.stories.js',
|
||||||
|
],
|
||||||
|
addons: ['@storybook/addon-actions', '@storybook/addon-links'],
|
||||||
|
};
|
||||||
34
source/admin/.stylelintrc
Normal file
34
source/admin/.stylelintrc
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"processors": ["stylelint-processor-styled-components"],
|
||||||
|
"extends": ["stylelint-config-standard-scss", "stylelint-config-styled-components"],
|
||||||
|
"customSyntax": "postcss-scss",
|
||||||
|
"rules": {
|
||||||
|
"block-no-empty": null,
|
||||||
|
"no-duplicate-selectors": null,
|
||||||
|
"no-empty-source": null,
|
||||||
|
"no-extra-semicolons": null,
|
||||||
|
"declaration-empty-line-before": null,
|
||||||
|
"string-quotes": null,
|
||||||
|
"selector-class-pattern": null,
|
||||||
|
"selector-pseudo-element-colon-notation": null,
|
||||||
|
"rule-empty-line-before": null,
|
||||||
|
"declaration-colon-newline-after": null,
|
||||||
|
"at-rule-empty-line-before": null,
|
||||||
|
"alpha-value-notation": null,
|
||||||
|
"color-function-notation": null,
|
||||||
|
"keyframes-name-pattern": null,
|
||||||
|
"value-list-comma-newline-after": null,
|
||||||
|
"no-descending-specificity": null,
|
||||||
|
"selector-type-no-unknown": [
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
"ignoreTypes": ["$dummyValue"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"value-keyword-case": [
|
||||||
|
"lower",
|
||||||
|
{ "ignoreKeywords": ["dummyValue"], "camelCaseSvgKeywords": true }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ignoreFiles": ["packages/decap-cms-lib-auth/index.d.ts"]
|
||||||
|
}
|
||||||
419
source/admin/CHANGELOG.md
Normal file
419
source/admin/CHANGELOG.md
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
# Changelog
|
||||||
|
Decap CMS is a collection of npm packages with their own versions and changelogs, each listed
|
||||||
|
below. The legacy pre-2.0 changelog is below as well.
|
||||||
|
|
||||||
|
## Core and distributions
|
||||||
|
- [decap-cms](https://github.com/decaporg/decap-cms/blob/main/packages/decap-cms/CHANGELOG.md)
|
||||||
|
- [decap-cms-core](https://github.com/decaporg/decap-cms/blob/main/packages/decap-cms-core/CHANGELOG.md)
|
||||||
|
|
||||||
|
## Shared libraries
|
||||||
|
- [decap-cms-lib-auth](https://github.com/decaporg/decap-cms/blob/main/packages/decap-cms-lib-auth/CHANGELOG.md)
|
||||||
|
- [decap-cms-lib-util](https://github.com/decaporg/decap-cms/blob/main/packages/decap-cms-lib-util/CHANGELOG.md)
|
||||||
|
- [decap-cms-ui-default](https://github.com/decaporg/decap-cms/blob/main/packages/decap-cms-ui-default/CHANGELOG.md)
|
||||||
|
|
||||||
|
## Backends
|
||||||
|
- [decap-cms-backend-bitbucket](https://github.com/decaporg/decap-cms/blob/main/packages/decap-cms-backend-bitbucket/CHANGELOG.md)
|
||||||
|
- [decap-cms-backend-git-gateway](https://github.com/decaporg/decap-cms/blob/main/packages/decap-cms-backend-git-gateway/CHANGELOG.md)
|
||||||
|
- [decap-cms-backend-github](https://github.com/decaporg/decap-cms/blob/main/packages/decap-cms-backend-github/CHANGELOG.md)
|
||||||
|
- [decap-cms-backend-gitlab](https://github.com/decaporg/decap-cms/blob/main/packages/decap-cms-backend-gitlab/CHANGELOG.md)
|
||||||
|
- [decap-cms-backend-test](https://github.com/decaporg/decap-cms/blob/main/packages/decap-cms-backend-test/CHANGELOG.md)
|
||||||
|
|
||||||
|
## Editor Components
|
||||||
|
- [decap-cms-editor-component-image](https://github.com/decaporg/decap-cms/blob/main/packages/decap-cms-editor-component-image/CHANGELOG.md)
|
||||||
|
|
||||||
|
## Widgets
|
||||||
|
- [decap-cms-widget-boolean](https://github.com/decaporg/decap-cms/blob/main/packages/decap-cms-widget-boolean/CHANGELOG.md)
|
||||||
|
- [decap-cms-widget-date](https://github.com/decaporg/decap-cms/blob/main/packages/decap-cms-widget-date/CHANGELOG.md)
|
||||||
|
- [decap-cms-widget-datetime](https://github.com/decaporg/decap-cms/blob/main/packages/decap-cms-widget-datetime/CHANGELOG.md)
|
||||||
|
- [decap-cms-widget-file](https://github.com/decaporg/decap-cms/blob/main/packages/decap-cms-widget-file/CHANGELOG.md)
|
||||||
|
- [decap-cms-widget-image](https://github.com/decaporg/decap-cms/blob/main/packages/decap-cms-widget-image/CHANGELOG.md)
|
||||||
|
- [decap-cms-widget-list](https://github.com/decaporg/decap-cms/blob/main/packages/decap-cms-widget-list/CHANGELOG.md)
|
||||||
|
- [decap-cms-widget-markdown](https://github.com/decaporg/decap-cms/blob/main/packages/decap-cms-widget-markdown/CHANGELOG.md)
|
||||||
|
- [decap-cms-widget-number](https://github.com/decaporg/decap-cms/blob/main/packages/decap-cms-widget-number/CHANGELOG.md)
|
||||||
|
- [decap-cms-widget-object](https://github.com/decaporg/decap-cms/blob/main/packages/decap-cms-widget-object/CHANGELOG.md)
|
||||||
|
- [decap-cms-widget-relation](https://github.com/decaporg/decap-cms/blob/main/packages/decap-cms-widget-relation/CHANGELOG.md)
|
||||||
|
- [decap-cms-widget-select](https://github.com/decaporg/decap-cms/blob/main/packages/decap-cms-widget-select/CHANGELOG.md)
|
||||||
|
- [decap-cms-widget-string](https://github.com/decaporg/decap-cms/blob/main/packages/decap-cms-widget-string/CHANGELOG.md)
|
||||||
|
- [decap-cms-widget-text](https://github.com/decaporg/decap-cms/blob/main/packages/decap-cms-widget-text/CHANGELOG.md)
|
||||||
|
|
||||||
|
## Legacy Changelog
|
||||||
|
|
||||||
|
## [Unreleased] ([demo](https://cms-demo.netlify.com/))
|
||||||
|
<details>
|
||||||
|
<summary>
|
||||||
|
Changes that have landed in main but are not yet released.
|
||||||
|
Click to see more.
|
||||||
|
</summary>
|
||||||
|
|
||||||
|
## v2
|
||||||
|
* (possibly breaking): return date object from date/datetime widgets if no format set ([@erquhart](https://github.com/erquhart) in [#1296](https://github.com/decaporg/decap-cms/pull/1296))
|
||||||
|
* check for title/slug field on config load ([@tech4him1](https://github.com/tech4him1) in [#1203](https://github.com/decaporg/decap-cms/pull/1203))
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## 1.9.4 (July 21, 2018) ([demo](https://1-9-4--cms-demo.netlify.com))
|
||||||
|
Fix multipart extension support for GitLab
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
* Support extensions with multiple parts for GitLab ([@Nic128](https://github.com/Nic128) in [#1478](https://github.com/decaporg/decap-cms/pull/1478))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.9.3 (July 3, 2018) ([demo](https://1-9-3--cms-demo.netlify.com))
|
||||||
|
Fix numbers in TOML output
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
* fix int value output in TOML format file (@slathrop in #1458)
|
||||||
|
|
||||||
|
|
||||||
|
## 1.9.2 (June 15, 2018) ([demo](https://1-9-2--cms-demo.netlify.com))
|
||||||
|
Fix test repo crash
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
* fix test-repo crash on non-existent folder ([@tech4him1](https://github.com/tech4him1) in [#1444](https://github.com/decaporg/decap-cms/pull/1444))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.9.1 (June 14, 2018) ([demo](https://1-9-1--cms-demo.netlify.com))
|
||||||
|
Fix GitLab Implicit OAuth
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
* fix GitLab Implicit OAuth ([@tech4him1](https://github.com/tech4him1) in [#1439](https://github.com/decaporg/decap-cms/pull/1439))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.9.0 (June 12, 2018) ([demo](https://1-9-0--cms-demo.netlify.com))
|
||||||
|
GitLab support is here!!! 🎉🎉🎉
|
||||||
|
|
||||||
|
### Features
|
||||||
|
* add GitLab backend with Cursor API ([@Benaiah](https://github.com/Benaiah) in [#1343](https://github.com/decaporg/decap-cms/pull/1343))
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
* fix workflow top panel styling ([@erquhart](https://github.com/erquhart) in [#1398](https://github.com/decaporg/decap-cms/pull/1398))
|
||||||
|
* only use `label_singular` when one item is rendered in List widget ([@robertkarlsson](https://github.com/robertkarlsson) in [#1422](https://github.com/decaporg/decap-cms/pull/1422))
|
||||||
|
* fix hidden widgets being rendered in editor components ([@robertkarlsson](https://github.com/robertkarlsson) in [#1414](https://github.com/decaporg/decap-cms/pull/1414))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.8.4 (May 25, 2018) ([demo](https://1-8-4--cms-demo.netlify.com))
|
||||||
|
Fix markdown widget styling.
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
* fix markdown widget styling ([@erquhart](https://github.com/erquhart) in [#1384](https://github.com/decaporg/decap-cms/pull/1384))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.8.3 (May 25, 2018) ([demo](https://1-8-3--cms-demo.netlify.com/))
|
||||||
|
Update dependencies.
|
||||||
|
|
||||||
|
|
||||||
|
## 1.8.2 (May 24, 2018) ([demo](https://1-8-2--cms-demo.netlify.com/))
|
||||||
|
Fix failure to save/publish.
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
* fix save/publish failure, revert overwrite prevention feature (@erquhart)
|
||||||
|
|
||||||
|
|
||||||
|
## 1.8.1 (May 23, 2018) ([demo](https://1-8-1--cms-demo.netlify.com/))
|
||||||
|
Allow upload of files larger than 1MB to GitHub, prevent unintentional file overwrites.
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
* prevent overwriting when generated slug matches an existing file ([@brianlmacdonald](https://github.com/brianlmacdonald) in [#1239](https://github.com/decaporg/decap-cms/pull/1239))
|
||||||
|
* fix large files failing to load ([@tech4him1](https://github.com/tech4him1) in [#1224](https://github.com/decaporg/decap-cms/pull/1224))
|
||||||
|
|
||||||
|
### Beta Features
|
||||||
|
* enable custom commit message templates ([@delucis](https://github.com/delucis) in [#1359](https://github.com/decaporg/decap-cms/pull/1359))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.8.0 (May 16, 2018) ([demo](https://1-8-0--cms-demo.netlify.com/))
|
||||||
|
Customizable relation widget display fields, squash merges for editorial workflow, perf
|
||||||
|
improvements.
|
||||||
|
|
||||||
|
### Features
|
||||||
|
* support `displayFields` config property for the relation widget ([@zurawiki](https://github.com/zurawiki) in [#1303](https://github.com/decaporg/decap-cms/pull/1303))
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
* prevent login for `git-gateway` backend when Git Gateway is not enabled for Netlify site ([@tech4him1](https://github.com/tech4him1) in [#1295](https://github.com/decaporg/decap-cms/pull/1295))
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
* use `cloneElement` when possible for editor preview pane widgets ([@danielmahon](https://github.com/danielmahon) in [#1248](https://github.com/decaporg/decap-cms/pull/1248))
|
||||||
|
* upgrade to Webpack 4 ([@tech4him1](https://github.com/tech4him1) in [#1214](https://github.com/decaporg/decap-cms/pull/1214))
|
||||||
|
|
||||||
|
### Beta Features
|
||||||
|
* support `squash_merges` config option for GitHub backend ([@delucis](https://github.com/delucis) in [#1330](https://github.com/decaporg/decap-cms/pull/1330))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.7.0 (April 24, 2018) ([demo](https://1-7-0--cms-demo.netlify.com/))
|
||||||
|
Allow custom auth endpoint, bug fixes.
|
||||||
|
|
||||||
|
### Features
|
||||||
|
* allow custom auth endpoint ([@erquhart](https://github.com/erquhart) in [#1294](https://github.com/decaporg/decap-cms/pull/1294))
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
* skip validation of optional fields when empty (@Dammmien in #1237)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
* fix GitHub auth button icon alignment (@erquhart in #1299)
|
||||||
|
* fix Git Gateway login hang (@ekoeryanto in #1240)
|
||||||
|
|
||||||
|
|
||||||
|
## 1.6.0 (April 19, 2018) ([demo](https://1-6-0--cms-demo.netlify.com/))
|
||||||
|
Markdown toolbar customization, manual date widget entry, bug fixes.
|
||||||
|
|
||||||
|
### Features
|
||||||
|
* Allow markdown editor toolbar customization ([@Dammmien](https://github.com/Dammmien) in [#1236](https://github.com/decaporg/decap-cms/pull/1236))
|
||||||
|
* Allow login screen to be skipped for test repo backend ([@erquhart](https://github.com/erquhart) in [#1291](https://github.com/decaporg/decap-cms/pull/1291))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
* Fix button/icon alignment on Safari 10 ([@maciejmatu](https://github.com/maciejmatu) in [#1227](https://github.com/decaporg/decap-cms/pull/1227))
|
||||||
|
* Allow typing in date widget ([@Dammmien](https://github.com/Dammmien) in [#1247](https://github.com/decaporg/decap-cms/pull/1247))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.5.0 (April 11, 2018) ([demo](https://1-5-0--cms-demo.netlify.com/))
|
||||||
|
New time based slug placeholders, set config.yml URL with <link>.
|
||||||
|
|
||||||
|
### Features
|
||||||
|
* Add hour, minute, and second slug fields ([@terrierscript](https://github.com/terrierscript) in [#1207](https://github.com/decaporg/decap-cms/pull/1207))
|
||||||
|
* Allow setting config URL with <link> ([@brianlmacdonald](https://github.com/brianlmacdonald) in [#1146](https://github.com/decaporg/decap-cms/pull/1146))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
* Fix broken new media uploads for Git Gateway ([@tech4him1](https://github.com/tech4him1) in [#1221](https://github.com/decaporg/decap-cms/pull/1221))
|
||||||
|
|
||||||
|
### Dev Experience
|
||||||
|
* Enable editorial workflow for test backend ([@erquhart](https://github.com/erquhart) in [#1225](https://github.com/decaporg/decap-cms/pull/1225))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.4.0 (March 29, 2018) ([demo](https://1-4-0--cms-demo.netlify.com/))
|
||||||
|
Filename creation can now be customized to exclude Unicode! Also, check out the new Beta Features! 💥
|
||||||
|
|
||||||
|
### Features
|
||||||
|
* Add option to strip Unicode from entry filenames ([@tech4him1](https://github.com/tech4him1) in [#1135](https://github.com/decaporg/decap-cms/pull/1135))
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
* Hide "create new" button for single files ([@tech4him1](https://github.com/tech4him1) in [#1200](https://github.com/decaporg/decap-cms/pull/1200))
|
||||||
|
* Filter editorial workflow entries by PR base branch ([@erquhart](https://github.com/erquhart) in [#1155](https://github.com/decaporg/decap-cms/pull/1155))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
* Allow list widget "add" button to be disabled ([@gazebosx3](https://github.com/gazebosx3) in [#1102](https://github.com/decaporg/decap-cms/pull/1102))
|
||||||
|
* Fix broken thumbnail when uploading an image to a private repository ([@Quicksaver](https://github.com/Quicksaver) in [#994](https://github.com/decaporg/decap-cms/pull/994))
|
||||||
|
* Get default value from each widget rather than setting all to null ([@MichaelRomani](https://github.com/MichaelRomani) in [#1126](https://github.com/decaporg/decap-cms/pull/1126))
|
||||||
|
* Fix editor validation notifications for editorial workflow ([@erquhart](https://github.com/erquhart) in [#1204](https://github.com/decaporg/decap-cms/pull/1204))
|
||||||
|
* Prevent Git Gateway users with invalid tokens from logging in ([@tech4him1](https://github.com/tech4him1) in [#1209](https://github.com/decaporg/decap-cms/pull/1209))
|
||||||
|
* Fix relation list preview ([@Quicksaver](https://github.com/Quicksaver) in [#1199](https://github.com/decaporg/decap-cms/pull/1199))
|
||||||
|
* Fix missing config file handling ([@talves](https://github.com/talves) in [#1182](https://github.com/decaporg/decap-cms/pull/1182))
|
||||||
|
* Fix initially blank date fields ([@tech4him1](https://github.com/tech4him1) in [#1210](https://github.com/decaporg/decap-cms/pull/1210))
|
||||||
|
|
||||||
|
### Beta Features
|
||||||
|
* Accept CSS strings in `registerPreviewStyle` ([@erquhart](https://github.com/erquhart) in [#1162](https://github.com/decaporg/decap-cms/pull/1162))
|
||||||
|
* Change manual init API to use the same bundle as auto init ([@talves](https://github.com/talves) and @erquhart in [#1173](https://github.com/decaporg/decap-cms/pull/1173))
|
||||||
|
|
||||||
|
### 4 tha devz
|
||||||
|
* Ship source code to npm ([@tech4him1](https://github.com/tech4him1) in [#1095](https://github.com/decaporg/decap-cms/pull/1095))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.3.5 (March 6, 2018) ([demo](https://1-3-5--cms-demo.netlify.com/))
|
||||||
|
Fixes styling issues
|
||||||
|
|
||||||
|
* Revert lockfile update due to breaking changes in css processing deps ([@erquhart](https://github.com/erquhart))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.3.4 (March 6, 2018) ([demo](https://1-3-4--cms-demo.netlify.com/))
|
||||||
|
Fixes editorial workflow entry failure
|
||||||
|
|
||||||
|
* Fix editorial workflow entries not loading ([@erquhart](https://github.com/erquhart))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.3.3 (March 6, 2018) ([demo](https://1-3-3--cms-demo.netlify.com/))
|
||||||
|
Fixes load failure
|
||||||
|
|
||||||
|
* Fix bugs introduced by manual initialization ([@erquhart](https://github.com/erquhart) in [#1157](https://github.com/decaporg/decap-cms/pull/1157))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.3.2 (March 6, 2018) ([demo](https://1-3-2--cms-demo.netlify.com/))
|
||||||
|
Fixes date widget default format, collection load failure when entry fails
|
||||||
|
|
||||||
|
* Fix date widget default format ([@erquhart](https://github.com/erquhart) in [#1143](https://github.com/decaporg/decap-cms/pull/1143))
|
||||||
|
* Fix collection failure when individual entries fail to load ([@tech4him1](https://github.com/tech4him1) in [#1093](https://github.com/decaporg/decap-cms/pull/1093))
|
||||||
|
|
||||||
|
### Beta Features
|
||||||
|
* Allow manual initialization and config injection ([@erquhart](https://github.com/erquhart) in [#1149](https://github.com/decaporg/decap-cms/pull/1149))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.3.1 (March 3, 2018) ([demo](https://1-3-1--cms-demo.netlify.com/))
|
||||||
|
Fixes editorial workflow failure for unknown collections.
|
||||||
|
|
||||||
|
* Report editorial workflow load errors, ignore entries with unknown collections ([@erquhart](https://github.com/erquhart) in [#1153](https://github.com/decaporg/decap-cms/pull/1153))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.3.0 (February 27, 2018) ([demo](https://1-3-0--cms-demo.netlify.com/))
|
||||||
|
Multi-part extensions, e.g. "en.md", a11y improvements in the editor, and bugfixes.
|
||||||
|
|
||||||
|
* Ensure unique id for each editor field ([@xifengjin88](https://github.com/xifengjin88) in [#1087](https://github.com/decaporg/decap-cms/pull/1087))
|
||||||
|
* Fix lists crashing when first value is not a string ([@tech4him1](https://github.com/tech4him1) in [#1115](https://github.com/decaporg/decap-cms/pull/1115))
|
||||||
|
* Support extensions with multiple parts (i.e. `en.md`) ([@tech4him1](https://github.com/tech4him1) in [#1123](https://github.com/decaporg/decap-cms/pull/1123))
|
||||||
|
* Fix lost unsaved changes when updating status or publishing from editor ([@erquhart](https://github.com/erquhart) in [#987](https://github.com/decaporg/decap-cms/pull/987))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.2.2 (February 21, 2018) ([demo](https://1-2-2--cms-demo.netlify.com/))
|
||||||
|
Fixes ES5 transpiling.
|
||||||
|
|
||||||
|
* Remove babel-preset-env, fix ES5 transpiling ([@erquhart](https://github.com/erquhart) in [#1127](https://github.com/decaporg/decap-cms/pull/1127))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.2.1 (February 21, 2018) ([demo](https://1-2-1--cms-demo.netlify.com/))
|
||||||
|
Allows `label_singular` config for collections and lists and distinct frontmatter delimiters.
|
||||||
|
|
||||||
|
* Accept `label_singular` in collection config ([@peduarte](https://github.com/peduarte) in [#1086](https://github.com/decaporg/decap-cms/pull/1086))
|
||||||
|
* Transpile down to ES5 to support older tooling eg. Webpack 1 ([@tech4him1](https://github.com/tech4him1) in [#1107](https://github.com/decaporg/decap-cms/pull/1107))
|
||||||
|
* Allow different opening and closing frontmatter delimiters ([@tech4him1](https://github.com/tech4him1) in [#1094](https://github.com/decaporg/decap-cms/pull/1094))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.2.0 (February 13, 2018) ([demo](https://1-2-0--cms-demo.netlify.com/))
|
||||||
|
Adds support for multiple frontmatter formats and custom delimiters, UI improvements.
|
||||||
|
|
||||||
|
* Use babel-preset-env to transpile for supported environments only ([@tech4him1](https://github.com/tech4him1) in [#765](https://github.com/decaporg/decap-cms/pull/765))
|
||||||
|
* Change direction of collapsed editor widget arrow indicators ([@Doocey](https://github.com/Doocey) in [#1059](https://github.com/decaporg/decap-cms/pull/1059))
|
||||||
|
* Support for writing frontmatter in JSON, TOML, or YAML ([@tech4him1](https://github.com/tech4him1) in [#933](https://github.com/decaporg/decap-cms/pull/933))
|
||||||
|
* Add collection label next to search results ([@solpark](https://github.com/solpark) in [#1068](https://github.com/decaporg/decap-cms/pull/1068))
|
||||||
|
* Support custom delimiters for frontmatter ([@Swieckowski](https://github.com/Swieckowski) in [#1064](https://github.com/decaporg/decap-cms/pull/1064))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.1.0 (January 25, 2018) ([demo](https://1-1-0--cms-demo.netlify.com/))
|
||||||
|
|
||||||
|
* Fix metadata handling for all children of a list field ([@Quicksaver](https://github.com/Quicksaver) in [#719](https://github.com/decaporg/decap-cms/pull/719))
|
||||||
|
* Allow registry of external backends ([@talves](https://github.com/talves) in [#1011](https://github.com/decaporg/decap-cms/pull/1011))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.0.4 (January 23, 2018) ([demo](https://1-0-4--cms-demo.netlify.com/))
|
||||||
|
|
||||||
|
* Fix markdown widget re-rendering after load ([@erquhart](https://github.com/erquhart) in [#955](https://github.com/decaporg/decap-cms/pull/955))
|
||||||
|
* Fix image form not displaying when added as first item in markdown widget ([@Dammmien](https://github.com/Dammmien) in [#926](https://github.com/decaporg/decap-cms/pull/926))
|
||||||
|
* Add collapse all/expand all functionality to List widget ([@drlogout](https://github.com/drlogout) in [#912](https://github.com/decaporg/decap-cms/pull/912))
|
||||||
|
* Add expand/collapse functionality to object widget ([@drlogout](https://github.com/drlogout) in [#927](https://github.com/decaporg/decap-cms/pull/927))
|
||||||
|
* Fix vertically centered icon positioning in Firefox ([@jimmaaay](https://github.com/jimmaaay) in [#976](https://github.com/decaporg/decap-cms/pull/976))
|
||||||
|
* Fix new uploads not showing in media library ([@tech4him1](https://github.com/tech4him1) in [#925](https://github.com/decaporg/decap-cms/pull/925))
|
||||||
|
* Overhaul widgets section in docs ([@hcavalieri](https://github.com/hcavalieri) in [#866](https://github.com/decaporg/decap-cms/pull/866))
|
||||||
|
* Use proper formatting when writing JSON files ([@tech4him1](https://github.com/tech4him1) in [#979](https://github.com/decaporg/decap-cms/pull/979))
|
||||||
|
* Ensure temporary storage is available before attempting to write ([@vencax](https://github.com/vencax) in [#550](https://github.com/decaporg/decap-cms/pull/550))
|
||||||
|
* Show SVG preview images in the media library ([@Jinksi](https://github.com/Jinksi) in [#954](https://github.com/decaporg/decap-cms/pull/954))
|
||||||
|
* Fix failed PR force-merge showing success message ([@tech4him1](https://github.com/tech4him1) in [#1016](https://github.com/decaporg/decap-cms/pull/1016))
|
||||||
|
* Fix false proptype warning for collection view ([@Quicksaver](https://github.com/Quicksaver) in [#998](https://github.com/decaporg/decap-cms/pull/998))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.0.3 (December 19, 2017) ([demo](https://1-0-3--cms-demo.netlify.com/))
|
||||||
|
|
||||||
|
* Fix select widgets with object type options ([@tech4him1](https://github.com/tech4him1) in [#920](https://github.com/decaporg/decap-cms/pull/920))
|
||||||
|
* Warn when uploading asset with same name as existing asset ([@Dammmien](https://github.com/Dammmien) in [#853](https://github.com/decaporg/decap-cms/pull/853))
|
||||||
|
* Fix Slate plugins broken during 0.30 migration ([@Dammmien](https://github.com/Dammmien) in [#856](https://github.com/decaporg/decap-cms/pull/856))
|
||||||
|
* Fix infinite scrolling for collections with integrations ([@erquhart](https://github.com/erquhart) in [#940](https://github.com/decaporg/decap-cms/pull/940))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.0.2 (December 7, 2017) ([demo](https://1-0-2--cms-demo.netlify.com/))
|
||||||
|
|
||||||
|
* Fix position of editor view controls ([@biilmann](https://github.com/biilmann) in [#886](https://github.com/decaporg/decap-cms/pull/886))
|
||||||
|
* Update docs intro to direct to new content ([@verythorough](https://github.com/verythorough) in [#891](https://github.com/decaporg/decap-cms/pull/891))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.0.1 (December 7, 2017) ([demo](https://1-0-1--cms-demo.netlify.com/))
|
||||||
|
|
||||||
|
* Add configuration options doc ([@verythorough](https://github.com/verythorough) in [#885](https://github.com/decaporg/decap-cms/pull/885))
|
||||||
|
* Add new docs website landing page ([@ziburski](https://github.com/ziburski) in [#880](https://github.com/decaporg/decap-cms/pull/880))
|
||||||
|
* Rework Test Drive and Quick Start docs ([@verythorough](https://github.com/verythorough) in [#888](https://github.com/decaporg/decap-cms/pull/888))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.0.0 (December 7, 2017) ([demo](https://1-0-0--cms-demo.netlify.com/))
|
||||||
|
|
||||||
|
The first major release of Netlify CMS!! Here are the big features:
|
||||||
|
|
||||||
|
### All New UI 💫
|
||||||
|
The CMS UI has been completely redesigned from the ground up!
|
||||||
|
|
||||||
|
* All new visuals and reprised UX throughout
|
||||||
|
* List view/grid view option for collections
|
||||||
|
* Deletion now works for editorial workflow
|
||||||
|
* Control publishing and editorial workflow status from the entry editor
|
||||||
|
* Descriptions can now be added for each collection
|
||||||
|
|
||||||
|
## All New Docs 💥
|
||||||
|
The docs at netlifycms.org have been rewritten and vastly improved!
|
||||||
|
|
||||||
|
* Full references with code samples for every configuration option, collection type, and widget
|
||||||
|
* Easier docs contributions with the website built directly in the repo
|
||||||
|
* Updated intro docs with a new Gatsby starter template in addition to the Hugo one
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
* Fix backspace not removing empty block in markdown editor ([@Dammmien](https://github.com/Dammmien) in [#854](https://github.com/decaporg/decap-cms/pull/854))
|
||||||
|
* Add select widget documentation ([@ackushiw](https://github.com/ackushiw) in [#806](https://github.com/decaporg/decap-cms/pull/806))
|
||||||
|
* Migrate netlifycms.org source into this repo ([@verythorough](https://github.com/verythorough) in [#860](https://github.com/decaporg/decap-cms/pull/860))
|
||||||
|
* Fix Slate mark rendering ([@erquhart](https://github.com/erquhart) in [#858](https://github.com/decaporg/decap-cms/pull/858))
|
||||||
|
* Do not infer file format if format specified in config ([@tech4him1](https://github.com/tech4him1) in [#795](https://github.com/decaporg/decap-cms/pull/795))
|
||||||
|
* Infer format from extension for new entries ([@tech4him1](https://github.com/tech4him1) in [#796](https://github.com/decaporg/decap-cms/pull/796))
|
||||||
|
* Throw on unsupported format ([@tech4him1](https://github.com/tech4him1) in [#831](https://github.com/decaporg/decap-cms/pull/831))
|
||||||
|
* Update widget docs ([@verythorough](https://github.com/verythorough) in [#876](https://github.com/decaporg/decap-cms/pull/876))
|
||||||
|
* Implement new UI, restructure/refactor project ([@erquhart](https://github.com/erquhart) and [@neutyp](https://github.com/neutyp) in [#785](https://github.com/decaporg/decap-cms/pull/785))
|
||||||
|
|
||||||
|
|
||||||
|
## 0.7.6 (November 27, 2017) ([demo](https://0-7-6--cms-demo.netlify.com/))
|
||||||
|
|
||||||
|
* Migrate to Slate 0.30.x ([@erquhart](https://github.com/erquhart) in [#826](https://github.com/decaporg/decap-cms/pull/826))
|
||||||
|
* Fix empty image fields saving null or undefined ([@tech4him1](https://github.com/tech4him1) in [#829](https://github.com/decaporg/decap-cms/pull/829))
|
||||||
|
* Add JSON as manually supported format ([@tech4him1](https://github.com/tech4him1) in [#830](https://github.com/decaporg/decap-cms/pull/830))
|
||||||
|
* Enable webpack scope hoisting ([@tech4him1](https://github.com/tech4him1) in [#840](https://github.com/decaporg/decap-cms/pull/840))
|
||||||
|
* Update bundled version of gotrue-js to latest ([@biilmann](https://github.com/biilmann) in [#837](https://github.com/decaporg/decap-cms/pull/837))
|
||||||
|
* Add global error boundary ([@tech4him1](https://github.com/tech4him1) in [#847](https://github.com/decaporg/decap-cms/pull/847))
|
||||||
|
* Fix datetime formatting, allow empty value ([@biilmann](https://github.com/biilmann) in [#842](https://github.com/decaporg/decap-cms/pull/842))
|
||||||
|
|
||||||
|
### Docs
|
||||||
|
|
||||||
|
* Update authentication doc to cover all backends ([@verythorough](https://github.com/verythorough) in [#751](https://github.com/decaporg/decap-cms/pull/751))
|
||||||
|
* Add oauth-provider-go to custom-authentication.md ([@igk1972](https://github.com/igk1972) in [#845](https://github.com/decaporg/decap-cms/pull/845))
|
||||||
|
|
||||||
|
|
||||||
|
## 0.7.5 (November 19, 2017) ([demo](https://0-7-5--cms-demo.netlify.com/))
|
||||||
|
|
||||||
|
* Add private media support for asset integrations ([@erquhart](https://github.com/erquhart) in [#834](https://github.com/decaporg/decap-cms/pull/834))
|
||||||
|
|
||||||
|
|
||||||
|
## 0.7.4 (November 15, 2017) ([demo](https://0-7-4--cms-demo.netlify.com/))
|
||||||
|
|
||||||
|
* Remove trailing slash from directory listing path ([@biilmann](https://github.com/biilmann) in [#817](https://github.com/decaporg/decap-cms/pull/817))
|
||||||
|
* Fix images with non-lowercase extensions not being treated as images ([@erquhart](https://github.com/erquhart) in [#816](https://github.com/decaporg/decap-cms/pull/816))
|
||||||
|
* Prompt before closing window with unsaved changes in the editor ([@benaiah](https://github.com/benaiah) in [#815](https://github.com/decaporg/decap-cms/pull/815))
|
||||||
|
|
||||||
|
|
||||||
|
## 0.7.3 (November 11, 2017) ([demo](https://0-7-3--cms-demo.netlify.com/))
|
||||||
|
|
||||||
|
* Fix persisting files with no body/data files ([@ebello](https://github.com/ebello) in [#808](https://github.com/decaporg/decap-cms/pull/808))
|
||||||
|
* Fix ControlHOC ref for redux container widgets ([@erquhart](https://github.com/erquhart) in [#812](https://github.com/decaporg/decap-cms/pull/812))
|
||||||
|
* Fix entries not saving due to null integrations state ([@erquhart](https://github.com/erquhart) in [#814](https://github.com/decaporg/decap-cms/pull/814))
|
||||||
|
* Fix requestAnimationFrame warnings in tests ([@tech4him1](https://github.com/tech4him1) in [#811](https://github.com/decaporg/decap-cms/pull/811))
|
||||||
|
|
||||||
|
|
||||||
|
## 0.7.2 (November 11, 2017) ([demo](https://0-7-2--cms-demo.netlify.com/))
|
||||||
|
|
||||||
|
* Only rebase editorial workflow pull requests if assets are stored in content repo ([@erquhart](https://github.com/erquhart) in [#804](https://github.com/decaporg/decap-cms/pull/804))
|
||||||
|
* Fix Netlify Identity widget logout method being called after signup redirect ([@tech4him1](https://github.com/tech4him1) in [#805](https://github.com/decaporg/decap-cms/pull/805))
|
||||||
|
|
||||||
|
|
||||||
|
## 0.7.1 (November 11, 2017) ([demo](https://0-7-1--cms-demo.netlify.com/))
|
||||||
|
|
||||||
|
* Enable sourcemaps ([@erquhart](https://github.com/erquhart) in [#803](https://github.com/decaporg/decap-cms/pull/803))
|
||||||
|
* Add unselected option to select widget when no default is set ([@benaiah](https://github.com/benaiah) in [#673](https://github.com/decaporg/decap-cms/pull/673))
|
||||||
|
* Fix image not shown after upload for Git Gateway ([@erquhart](https://github.com/erquhart) in [#790](https://github.com/decaporg/decap-cms/pull/790))
|
||||||
|
* Fix empty media folder loading error ([@erquhart](https://github.com/erquhart) in [#791](https://github.com/decaporg/decap-cms/pull/791))
|
||||||
|
* Fix error for non-markdown files in editorial workflow ([@tech4him1](https://github.com/tech4him1) in [#794](https://github.com/decaporg/decap-cms/pull/794))
|
||||||
|
* Fix login when accept_roles is set ([@tech4him1](https://github.com/tech4him1) in [#801](https://github.com/decaporg/decap-cms/pull/801))
|
||||||
|
* Add error boundary to editor preview iframe ([@erquhart](https://github.com/erquhart) in [#779](https://github.com/decaporg/decap-cms/pull/779))
|
||||||
|
|
||||||
|
|
||||||
|
## 0.7.0 (November 9, 2017) ([demo](https://0-7-0--cms-demo.netlify.com/))
|
||||||
|
|
||||||
|
### Media Library UI
|
||||||
|
The CMS now features a media library UI for browsing, adding, and removing media from your content
|
||||||
|
repo! The library shows assets in from the directory set as `media_library` in the CMS config. The
|
||||||
|
media library is fully backwards compatible for existing CMS installations.
|
||||||
|
|
||||||
|
### All Changes
|
||||||
|
* Add config option to disable deletion for a collection ([@rpullinger](https://github.com/rpullinger) in [#707](https://github.com/decaporg/decap-cms/pull/707))
|
||||||
|
* Fix TOML files not being saved with the correct extension ([@tech4him1](https://github.com/tech4him1) in [#757](https://github.com/decaporg/decap-cms/pull/757))
|
||||||
|
* Clean up file formatters ([@tech4him1](https://github.com/tech4him1) in [#759](https://github.com/decaporg/decap-cms/pull/759))
|
||||||
|
* Add scroll sync toggle to editor ([@Jinksi](https://github.com/Jinksi) in [#693](https://github.com/decaporg/decap-cms/pull/693))
|
||||||
|
* Disable login button while login is in progress ([@tech4him1](https://github.com/tech4him1) in [#741](https://github.com/decaporg/decap-cms/pull/741))
|
||||||
|
* Improve markdown editor active style indicator accuracy ([@pjsier](https://github.com/pjsier) in [#774](https://github.com/decaporg/decap-cms/pull/774))
|
||||||
|
* Add media library UI ([@erquhart](https://github.com/erquhart) in [#554](https://github.com/decaporg/decap-cms/pull/554))
|
||||||
|
* Fix transparent background on list widget ([@Jinksi](https://github.com/Jinksi) in [#768](https://github.com/decaporg/decap-cms/pull/768))
|
||||||
74
source/admin/CODE_OF_CONDUCT.md
Normal file
74
source/admin/CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as
|
||||||
|
contributors and maintainers pledge to making participation in our project and
|
||||||
|
our community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||||
|
nationality, personal appearance, race, religion, or sexual identity and
|
||||||
|
orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment
|
||||||
|
include:
|
||||||
|
|
||||||
|
* Using welcoming and inclusive language
|
||||||
|
* Being respectful of differing viewpoints and experiences
|
||||||
|
* Gracefully accepting constructive criticism
|
||||||
|
* Focusing on what is best for the community
|
||||||
|
* Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||||
|
advances
|
||||||
|
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or electronic
|
||||||
|
address, without explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable
|
||||||
|
behavior and are expected to take appropriate and fair corrective action in
|
||||||
|
response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or
|
||||||
|
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||||
|
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||||
|
permanently any contributor for other behaviors that they deem inappropriate,
|
||||||
|
threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies both within project spaces and in public spaces
|
||||||
|
when an individual is representing the project or its community. Examples of
|
||||||
|
representing a project or community include using an official project e-mail
|
||||||
|
address, posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event. Representation of a project may be
|
||||||
|
further defined and clarified by project maintainers.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported by contacting the project team at decap@p-m.si. All
|
||||||
|
complaints will be reviewed and investigated and will result in a response that
|
||||||
|
is deemed necessary and appropriate to the circumstances. The project team is
|
||||||
|
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||||
|
Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
|
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||||
|
faith may face temporary or permanent repercussions as determined by other
|
||||||
|
members of the project's leadership.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||||
|
available at [http://contributor-covenant.org/version/1/4][version]
|
||||||
|
|
||||||
|
[homepage]: http://contributor-covenant.org
|
||||||
|
[version]: http://contributor-covenant.org/version/1/4/
|
||||||
199
source/admin/CONTRIBUTING.md
Normal file
199
source/admin/CONTRIBUTING.md
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
# CONTRIBUTING
|
||||||
|
|
||||||
|
Contributions are always welcome, no matter how large or small. Before contributing,
|
||||||
|
please read the [code of conduct](CODE_OF_CONDUCT.md).
|
||||||
|
|
||||||
|
For details on contributing to documentation, see [Website Readme](https://github.com/decaporg/decap-website/blob/main/README.md).
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
> Install [Node.js (LTS)](https://nodejs.org/) on your system.
|
||||||
|
|
||||||
|
### Install dependencies
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/decaporg/decap-cms
|
||||||
|
cd decap-cms
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run locally
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run start
|
||||||
|
```
|
||||||
|
|
||||||
|
## Available scripts
|
||||||
|
|
||||||
|
### clean
|
||||||
|
|
||||||
|
Removes all of the CMS package `dist` directories.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run clean
|
||||||
|
```
|
||||||
|
|
||||||
|
### reset
|
||||||
|
|
||||||
|
Runs the `clean` script and removes all the `node_modules` from the CMS packages.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run reset
|
||||||
|
```
|
||||||
|
|
||||||
|
### build
|
||||||
|
|
||||||
|
Runs the `clean` script and builds the CMS packages.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### build-preview
|
||||||
|
|
||||||
|
Runs the `build` and `build-preview` scripts in each package and serves the resulting build locally.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build-preview
|
||||||
|
```
|
||||||
|
|
||||||
|
### test
|
||||||
|
|
||||||
|
Runs linting and Jest tests.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run test
|
||||||
|
```
|
||||||
|
|
||||||
|
### test:all
|
||||||
|
|
||||||
|
Runs linting, Jest, and Cypress tests.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run test:all
|
||||||
|
```
|
||||||
|
|
||||||
|
### test:e2e
|
||||||
|
|
||||||
|
Runs Cypress e2e tests.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run test:e2e
|
||||||
|
```
|
||||||
|
|
||||||
|
### test:e2e:dev
|
||||||
|
|
||||||
|
Runs Cypress e2e tests on watch mode with an open instance of Chrome.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run test:e2e:dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### format
|
||||||
|
|
||||||
|
Formats code and docs according to our style guidelines.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run format
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pull Requests
|
||||||
|
|
||||||
|
We actively welcome your pull requests!
|
||||||
|
|
||||||
|
If you need help with Git or our workflow, please ask in our [community chat](https://decapcms.org/chat). We want your contributions even if you're just learning Git. Our maintainers are happy to help!
|
||||||
|
|
||||||
|
Decap CMS uses the [Forking Workflow](https://www.atlassian.com/git/tutorials/comparing-workflows/forking-workflow) + [Feature Branches](https://www.atlassian.com/git/tutorials/comparing-workflows/feature-branch-workflow). Additionally, PR's should be [rebased](https://www.atlassian.com/git/tutorials/merging-vs-rebasing) on main when opened, and again before merging.
|
||||||
|
|
||||||
|
1. Fork the repo.
|
||||||
|
2. Create a branch from `main`. If you're addressing a specific issue, prefix your branch name with the issue number.
|
||||||
|
3. If you've added code that should be tested, add tests.
|
||||||
|
4. If you've changed APIs, update the documentation.
|
||||||
|
5. Run `npm run test` and ensure the test suite passes.
|
||||||
|
6. Use `npm run format` to format and lint your code.
|
||||||
|
7. PR's must be rebased before merge (feel free to ask for help).
|
||||||
|
8. PR should be reviewed by two maintainers prior to merging.
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
`npm run start` spawns a development server and uses `dev-test/config.yml` and `dev-test/index.html` to serve the CMS.
|
||||||
|
In order to debug a specific issue follow the next steps:
|
||||||
|
|
||||||
|
1. Replace `dev-test/config.yml` with the relevant `config.yml`. If you want to test the backend, make sure that the `backend` property of the config indicates which backend you use (GitHub, Gitlab, Bitbucket etc) and path to the repo.
|
||||||
|
|
||||||
|
```js
|
||||||
|
backend:
|
||||||
|
name: github
|
||||||
|
repo: owner-name/repo-name
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Change the content of `dev-test/index.html` to:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Decap CMS</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script src="dist/decap-cms.js"></script>
|
||||||
|
<!-- <script>
|
||||||
|
// this is the place to add CMS customizations if you need to, e.g.
|
||||||
|
CMS.registerPreviewTemplate('posts', PostPreview);
|
||||||
|
</script> -->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
The most important thing is to make sure that Decap CMS is loaded from the `dist` folder. This way, every time you make changes to the source code, they will be compiled and reflected immediately on `localhost`.
|
||||||
|
|
||||||
|
3. Run `npm run start`
|
||||||
|
4. Open `http://localhost:8080/` in the browser and you should have access to the CMS
|
||||||
|
|
||||||
|
### Debugging Git Gateway
|
||||||
|
|
||||||
|
When debugging the CMS with Git Gateway you must:
|
||||||
|
|
||||||
|
1. Have a Netlify site with [Git Gateway](https://docs.netlify.com/visitor-access/git-gateway/) and [Netlify Identity](https://docs.netlify.com/visitor-access/identity/) enabled. An easy way to create such a site is to use a [template](https://www.decapcms.org/docs/start-with-a-template/), for example the [Gatsby template](https://app.netlify.com/start/deploy?repository=https://github.com/decaporg/gatsby-starter-decap-cms&stack=cms)
|
||||||
|
2. Tell the CMS the URL of your Netlify site using a local storage item. To do so:
|
||||||
|
|
||||||
|
1. Open `http://localhost:8080/` in the browser
|
||||||
|
2. Open the Developer Console. Write the below command and press enter: `localStorage.setItem('netlifySiteURL', 'https://yourwebsiteurl.netlify.app/')`
|
||||||
|
3. To be sure, you can run this command as well: `localStorage.getItem('netlifySiteURL')`
|
||||||
|
4. Refresh the page
|
||||||
|
5. You should be able to log in via your Netlify Identity email/password
|
||||||
|
|
||||||
|
### Fine tune the way you run unit tests
|
||||||
|
|
||||||
|
There are situations where you would want to run a specific test file, or tests that match a certain pattern.
|
||||||
|
|
||||||
|
To run all the tests for a specific file, use this command:
|
||||||
|
|
||||||
|
```
|
||||||
|
npx jest <filename or file path>
|
||||||
|
```
|
||||||
|
|
||||||
|
The first part of the command, `npx jest` means running the locally installed version of `jest`. It is equivalent to running `node_modules/.bin/jest`.
|
||||||
|
|
||||||
|
Example for running all the tests for the file `gitlab.spec.js`: `npx jest gitlab.spec.js`
|
||||||
|
|
||||||
|
Some test files like `API.spec.js` is available in several packages. You can pass a regexp pattern instead of file path to narrow down files.
|
||||||
|
|
||||||
|
Example for running all the tests for the file `API.spec.js` in the `decap-cms-backend-gitlab` package:
|
||||||
|
|
||||||
|
`npx jest ".+backend-gitlab/.+/API.spec.js`
|
||||||
|
|
||||||
|
To run a specific test in a file, add the flag `--testNamePattern`, or `-t` for short followed by a regexp to match your test name.
|
||||||
|
|
||||||
|
Example for running the test "should return true on project access_level >= 30" in the API.spec.js in `decap-cms-backend-gitlab` package:
|
||||||
|
|
||||||
|
```
|
||||||
|
npx jest -t "true on p" ".+backend-gitlab/.+/API.spec.js"
|
||||||
|
```
|
||||||
|
|
||||||
|
For more information about running tests exactly the way you want, check out the official documentation for [Jest CLI](https://jestjs.io/docs/cli).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
By contributing to Decap CMS, you agree that your contributions will be licensed
|
||||||
|
under its [MIT license](LICENSE).
|
||||||
22
source/admin/LICENSE
Normal file
22
source/admin/LICENSE
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
Copyright (c) 2016 Netlify <decap@p-m.si>
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
60
source/admin/README.md
Normal file
60
source/admin/README.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|

|
||||||
|
|
||||||
|
[](https://github.com/decaporg/decap-cms/blob/main/LICENSE) [](https://app.netlify.com/sites/decap-www/deploys) [](https://www.npmjs.com/package/decap-cms) [](https://github.com/decaporg/decap-cms/actions?query=branch%3Amain+workflow%3A%22Node+CI%22) [](https://github.com/decaporg/decap-cms/blob/main/CONTRIBUTING.md)
|
||||||
|
|
||||||
|
[decapcms.org](https://www.decapcms.org/)
|
||||||
|
|
||||||
|
A CMS for static site generators. Give users a simple way to edit
|
||||||
|
and add content to any site built with a static site generator.
|
||||||
|
|
||||||
|
_Decap CMS is the new name of Netlify CMS [since February 2023](https://www.netlify.com/blog/netlify-cms-to-become-decap-cms/)._
|
||||||
|
|
||||||
|
## Community Chat
|
||||||
|
|
||||||
|
<a href="https://decapcms.org/chat">Join us on Discord</a>
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
Decap CMS is a single-page app that you pull into the `/admin` part of your site.
|
||||||
|
|
||||||
|
It presents a clean UI for editing content stored in a Git repository.
|
||||||
|
|
||||||
|
You setup a YAML config to describe the content model of your site, and typically
|
||||||
|
tweak the main layout of the CMS a bit to fit your own site.
|
||||||
|
|
||||||
|
When a user navigates to `/admin/` they'll be prompted to log in, and once authenticated
|
||||||
|
they'll be able to create new content or edit existing content.
|
||||||
|
|
||||||
|
Read more about Decap CMS [Core Concepts](https://www.decapcms.org/docs/intro/).
|
||||||
|
|
||||||
|
## Installation and Configuration
|
||||||
|
|
||||||
|
The Decap CMS can be used in two different ways.
|
||||||
|
|
||||||
|
* A Quick and easy install, that requires you to create a single HTML file and a configuration file. All the CMS JavaScript and CSS are loaded from a CDN.
|
||||||
|
To learn more about this installation method, refer to the [Quick Start Guide](https://www.decapcms.org/docs/quick-start/)
|
||||||
|
* A complete, more complex install, that gives you more flexibility but requires that you use a static site builder with a build system that supports npm packages.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
New contributors are always welcome! Check out [CONTRIBUTING.md](https://github.com/decaporg/decap-cms/blob/main/CONTRIBUTING.md) to get involved.
|
||||||
|
|
||||||
|
## Change Log
|
||||||
|
|
||||||
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
Every release is documented on the GitHub [Releases](https://github.com/decaporg/decap-cms/releases) page.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Decap CMS is released under the [MIT License](LICENSE).
|
||||||
|
Please make sure you understand its [implications and guarantees](https://writing.kemitchell.com/2016/09/21/MIT-License-Line-by-Line.html).
|
||||||
|
|
||||||
|
## Maintainers
|
||||||
|
|
||||||
|
Maintained with care by <a href="https://techhub.p-m.si/">PM TechHub</a> & friends.
|
||||||
|
|
||||||
|
## Professional help
|
||||||
|
|
||||||
|
Our partners offer a range of services that can help you get the most out of Decap CMS. Find onboarding, priority support, and development of custom features.
|
||||||
|
|
||||||
|
[Read more on our professional help page](https://decapcms.org/services/)
|
||||||
1
source/admin/__mocks__/styleMock.js
Normal file
1
source/admin/__mocks__/styleMock.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
module.exports = {};
|
||||||
111
source/admin/babel.config.js
Normal file
111
source/admin/babel.config.js
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const appVersion = require('./packages/decap-cms-app/package.json').version;
|
||||||
|
const coreVersion = require('./packages/decap-cms-core/package.json').version;
|
||||||
|
const isProduction = process.env.NODE_ENV === 'production';
|
||||||
|
const isTest = process.env.NODE_ENV === 'test';
|
||||||
|
const isESM = process.env.NODE_ENV === 'esm';
|
||||||
|
|
||||||
|
console.log('Build Package:', path.basename(process.cwd()));
|
||||||
|
|
||||||
|
// Always enabled plugins
|
||||||
|
const basePlugins = [
|
||||||
|
[
|
||||||
|
'babel-plugin-transform-builtin-extend',
|
||||||
|
{
|
||||||
|
globals: ['Error'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'babel-plugin-inline-json-import',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Legacy transforms for non-ESM builds
|
||||||
|
// REVISIT: We probably don't need any of these since we use preset-env
|
||||||
|
const legacyPlugins = [
|
||||||
|
'transform-export-extensions',
|
||||||
|
'@babel/plugin-proposal-class-properties',
|
||||||
|
'@babel/plugin-proposal-object-rest-spread',
|
||||||
|
'@babel/plugin-proposal-export-default-from',
|
||||||
|
'@babel/plugin-proposal-nullish-coalescing-operator',
|
||||||
|
'@babel/plugin-proposal-optional-chaining',
|
||||||
|
'@babel/plugin-syntax-dynamic-import',
|
||||||
|
];
|
||||||
|
|
||||||
|
const defaultPlugins = [...basePlugins, ...(isESM ? [] : legacyPlugins)];
|
||||||
|
|
||||||
|
const svgo = {
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
name: 'preset-default',
|
||||||
|
params: {
|
||||||
|
overrides: {
|
||||||
|
removeViewBox: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
function presets() {
|
||||||
|
return [
|
||||||
|
'@babel/preset-react',
|
||||||
|
...(!isESM ? [['@babel/preset-env', {}]] : []),
|
||||||
|
[
|
||||||
|
'@emotion/babel-preset-css-prop',
|
||||||
|
{
|
||||||
|
autoLabel: 'always',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@babel/preset-typescript',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function plugins() {
|
||||||
|
if (isESM) {
|
||||||
|
return [
|
||||||
|
...defaultPlugins,
|
||||||
|
[
|
||||||
|
'transform-define',
|
||||||
|
{
|
||||||
|
DECAP_CMS_APP_VERSION: `${appVersion}`,
|
||||||
|
DECAP_CMS_CORE_VERSION: `${coreVersion}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'inline-react-svg',
|
||||||
|
{
|
||||||
|
svgo,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'inline-import',
|
||||||
|
{
|
||||||
|
extensions: ['.css'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTest) {
|
||||||
|
return [
|
||||||
|
...defaultPlugins,
|
||||||
|
[
|
||||||
|
'inline-react-svg',
|
||||||
|
{
|
||||||
|
svgo,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isProduction) {
|
||||||
|
return [...defaultPlugins];
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultPlugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
presets: presets(),
|
||||||
|
plugins: plugins(),
|
||||||
|
};
|
||||||
1
source/admin/commitlint.config.js
Normal file
1
source/admin/commitlint.config.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
module.exports = { extends: ['@commitlint/config-conventional'] };
|
||||||
81
source/admin/config.yml
Normal file
81
source/admin/config.yml
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# https://decapcms.org/docs/configuration-options/
|
||||||
|
backend:
|
||||||
|
name: github
|
||||||
|
repo: bishshi/myblogsource
|
||||||
|
branch: master
|
||||||
|
site_url: "https://blog.biss.click"
|
||||||
|
logo_url: ""
|
||||||
|
locale: "zh_Hans"
|
||||||
|
media_folder: "source/images"
|
||||||
|
public_folder: "/images"
|
||||||
|
common_col_conf: &common_col_conf
|
||||||
|
create: true
|
||||||
|
slug: "{{fields.filename}}"
|
||||||
|
sortable_fields:
|
||||||
|
- "commit_date"
|
||||||
|
- "title"
|
||||||
|
- "date"
|
||||||
|
- "updated"
|
||||||
|
# https://decapcms.org/docs/widgets/
|
||||||
|
fields:
|
||||||
|
- label: "文件名"
|
||||||
|
name: "filename"
|
||||||
|
widget: "string"
|
||||||
|
- label: "标题"
|
||||||
|
name: "title"
|
||||||
|
widget: "string"
|
||||||
|
- label: "发表日期"
|
||||||
|
name: "date"
|
||||||
|
widget: "datetime"
|
||||||
|
format: "YYYY-MM-DD HH:mm:ss"
|
||||||
|
date_format: "YYYY-MM-DD"
|
||||||
|
time_format: "HH:mm:ss"
|
||||||
|
- label: "更新日期"
|
||||||
|
name: "updated"
|
||||||
|
widget: "datetime"
|
||||||
|
format: "YYYY-MM-DD HH:mm:ss"
|
||||||
|
date_format: "YYYY-MM-DD"
|
||||||
|
time_format: "HH:mm:ss"
|
||||||
|
required: false
|
||||||
|
- label: "封面"
|
||||||
|
name: "cover"
|
||||||
|
widget: "image"
|
||||||
|
required: false
|
||||||
|
- label: "标签"
|
||||||
|
name: "tags"
|
||||||
|
widget: "string"
|
||||||
|
required: false
|
||||||
|
- label: "分类"
|
||||||
|
name: "categories"
|
||||||
|
widget: "string"
|
||||||
|
required: false
|
||||||
|
- label: "永久链接"
|
||||||
|
name: "abbrlink"
|
||||||
|
widget: "string"
|
||||||
|
required: false
|
||||||
|
- label: "摘要"
|
||||||
|
name: "summary"
|
||||||
|
widget: "text"
|
||||||
|
required: false
|
||||||
|
- label: "正文"
|
||||||
|
name: "body"
|
||||||
|
widget: "markdown"
|
||||||
|
- label: "原创"
|
||||||
|
name: "toc"
|
||||||
|
widget: "boolean"
|
||||||
|
default: true
|
||||||
|
- label: "评论"
|
||||||
|
name: "comments"
|
||||||
|
widget: "boolean"
|
||||||
|
default: true
|
||||||
|
collections:
|
||||||
|
- name: "202508"
|
||||||
|
label: "2025年8月"
|
||||||
|
folder: "source/_posts/2025.08"
|
||||||
|
preview_path: "2025/{{filename}}/"
|
||||||
|
<<: *common_col_conf
|
||||||
|
- name: "202509"
|
||||||
|
label: "2025年9月"
|
||||||
|
folder: "source/_posts/2025.09"
|
||||||
|
preview_path: "2025/{{filename}}/"
|
||||||
|
<<: *common_col_conf
|
||||||
20
source/admin/cypress.config.ts
Normal file
20
source/admin/cypress.config.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
projectId: '1c35bs',
|
||||||
|
retries: {
|
||||||
|
runMode: 4,
|
||||||
|
openMode: 0,
|
||||||
|
},
|
||||||
|
e2e: {
|
||||||
|
video: false,
|
||||||
|
// We've imported your old cypress plugins here.
|
||||||
|
// You may want to clean this up later by importing these.
|
||||||
|
setupNodeEvents(on, config) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
return require('./cypress/plugins/index.js')(on, config);
|
||||||
|
},
|
||||||
|
baseUrl: 'http://localhost:8080',
|
||||||
|
specPattern: 'cypress/e2e/*spec*.js',
|
||||||
|
},
|
||||||
|
});
|
||||||
83
source/admin/cypress/Readme.md
Normal file
83
source/admin/cypress/Readme.md
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
# Cypress Tests Guide
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
[Cypress](https://www.cypress.io/) is a JavaScript End to End Testing Framework that runs in the browser.
|
||||||
|
|
||||||
|
Cypress tests run with a [local version](../dev-test) of the CMS.
|
||||||
|
|
||||||
|
During the setup of a spec file, the relevant `index.html` and `config.yml` are copied from `dev-test/backends/<backend>` to `dev-test`.
|
||||||
|
|
||||||
|
Tests for the `test` backend use mock data generated in `dev-test/backends/test/index.html`.
|
||||||
|
|
||||||
|
Tests for the other backends use previously [recorded data](fixtures) and stub `fetch` [calls](support/commands.js#L52). See more about recording tests data [here](#recording-tests-data).
|
||||||
|
|
||||||
|
## Run Tests Locally
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run test:e2e # builds the demo site and runs Cypress in headless mode with mock data
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debug Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run develop # starts a local dev server with the demo site
|
||||||
|
npm run test:e2e:exec # runs Cypress in non-headless mode with mock data
|
||||||
|
```
|
||||||
|
|
||||||
|
## Recording Tests Data
|
||||||
|
|
||||||
|
When recording tests, access to the relevant backend API is required, thus one must set up a `.env` file in the root project directory in the following format:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GITHUB_REPO_OWNER=owner
|
||||||
|
GITHUB_REPO_NAME=repo
|
||||||
|
GITHUB_REPO_TOKEN=tokenWithWritePermissions
|
||||||
|
GITHUB_OPEN_AUTHORING_OWNER=forkOwner
|
||||||
|
GITHUB_OPEN_AUTHORING_TOKEN=tokenWithWritePermissions
|
||||||
|
|
||||||
|
GITLAB_REPO_OWNER=owner
|
||||||
|
GITLAB_REPO_NAME=repo
|
||||||
|
GITLAB_REPO_TOKEN=tokenWithWritePermissions
|
||||||
|
|
||||||
|
BITBUCKET_REPO_OWNER=owner
|
||||||
|
BITBUCKET_REPO_NAME=repo
|
||||||
|
BITBUCKET_OUATH_CONSUMER_KEY=ouathConsumerKey
|
||||||
|
BITBUCKET_OUATH_CONSUMER_SECRET=ouathConsumerSecret
|
||||||
|
|
||||||
|
NETLIFY_API_TOKEN=netlifyApiToken
|
||||||
|
NETLIFY_INSTALLATION_ID=netlifyGitHubInstallationId
|
||||||
|
```
|
||||||
|
|
||||||
|
> The structure of the relevant repo should match the settings in [`config.yml`](../dev-test/backends/<backend>/config.yml#L1)
|
||||||
|
|
||||||
|
To start a recording run the following commands:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run develop # starts a local dev server with the demo site
|
||||||
|
npm run mock:server:start # starts the recording proxy
|
||||||
|
npm run test:e2e:record-fixtures:dev # runs Cypress in non-headless and pass data through the recording proxy
|
||||||
|
npm run mock:server:stop # stops the recording proxy
|
||||||
|
```
|
||||||
|
|
||||||
|
> During the recorded process a clone of the relevant repo will be created under `.temp` and reset between tests.
|
||||||
|
|
||||||
|
Recordings are sanitized from any possible sensitive data and [transformed](plugins/common.js#L34) into an easier to process format.
|
||||||
|
|
||||||
|
To avoid recording all the tests over and over again, a recommended process is to:
|
||||||
|
|
||||||
|
1. Mark the specific test as `only` by changing `it("some test...` to `it.only("some test...` for the relevant test.
|
||||||
|
2. Run the test in recording mode.
|
||||||
|
3. Exit Cypress and stop the proxy.
|
||||||
|
4. Run the test normally (with mock data) to verify the recording works.
|
||||||
|
|
||||||
|
## Debugging Playback Failures
|
||||||
|
|
||||||
|
Most common failures are:
|
||||||
|
|
||||||
|
1. The [recorded data](utils/mock-server.js#L17) is not [transformed](plugins/common.js#L34) properly (e.g. sanitization broke something).
|
||||||
|
2. The [stubbed requests and responses](support/commands.js#L82) are not [matched](support/commands.js#L32) properly (e.g. timestamp changes in request body between recording and playback).
|
||||||
|
|
||||||
|
Dumping all recorded data as is to a file [here](utils/mock-server.js#L24) and adding a `debugger;` statement [here](support/commands.js#L52) is useful to gain insights.
|
||||||
|
|
||||||
|
Also comparing console log messages between recording and playback is very useful (ordering of requests, etc.)
|
||||||
93
source/admin/cypress/e2e/common/editorial_workflow.js
Normal file
93
source/admin/cypress/e2e/common/editorial_workflow.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import '../../utils/dismiss-local-backup';
|
||||||
|
import {
|
||||||
|
login,
|
||||||
|
createPost,
|
||||||
|
createPostAndExit,
|
||||||
|
updateExistingPostAndExit,
|
||||||
|
exitEditor,
|
||||||
|
goToWorkflow,
|
||||||
|
goToCollections,
|
||||||
|
updateWorkflowStatus,
|
||||||
|
publishWorkflowEntry,
|
||||||
|
assertWorkflowStatusInEditor,
|
||||||
|
assertPublishedEntry,
|
||||||
|
deleteEntryInEditor,
|
||||||
|
assertOnCollectionsPage,
|
||||||
|
assertEntryDeleted,
|
||||||
|
assertWorkflowStatus,
|
||||||
|
updateWorkflowStatusInEditor,
|
||||||
|
} from '../../utils/steps';
|
||||||
|
import { workflowStatus, editorStatus } from '../../utils/constants';
|
||||||
|
|
||||||
|
export default function({ entries, getUser }) {
|
||||||
|
it('successfully loads', () => {
|
||||||
|
login(getUser());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can create an entry', () => {
|
||||||
|
login(getUser());
|
||||||
|
createPostAndExit(entries[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can update an entry', () => {
|
||||||
|
login(getUser());
|
||||||
|
createPostAndExit(entries[0]);
|
||||||
|
updateExistingPostAndExit(entries[0], entries[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can publish an editorial workflow entry', () => {
|
||||||
|
login(getUser());
|
||||||
|
createPostAndExit(entries[0]);
|
||||||
|
goToWorkflow();
|
||||||
|
updateWorkflowStatus(entries[0], workflowStatus.draft, workflowStatus.ready);
|
||||||
|
publishWorkflowEntry(entries[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can change workflow status', () => {
|
||||||
|
login(getUser());
|
||||||
|
createPostAndExit(entries[0]);
|
||||||
|
goToWorkflow();
|
||||||
|
updateWorkflowStatus(entries[0], workflowStatus.draft, workflowStatus.review);
|
||||||
|
updateWorkflowStatus(entries[0], workflowStatus.review, workflowStatus.ready);
|
||||||
|
updateWorkflowStatus(entries[0], workflowStatus.ready, workflowStatus.review);
|
||||||
|
updateWorkflowStatus(entries[0], workflowStatus.review, workflowStatus.draft);
|
||||||
|
updateWorkflowStatus(entries[0], workflowStatus.draft, workflowStatus.ready);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can change status on and publish multiple entries', () => {
|
||||||
|
login(getUser());
|
||||||
|
createPostAndExit(entries[0]);
|
||||||
|
createPostAndExit(entries[1]);
|
||||||
|
createPostAndExit(entries[2]);
|
||||||
|
goToWorkflow();
|
||||||
|
updateWorkflowStatus(entries[2], workflowStatus.draft, workflowStatus.ready);
|
||||||
|
updateWorkflowStatus(entries[1], workflowStatus.draft, workflowStatus.ready);
|
||||||
|
updateWorkflowStatus(entries[0], workflowStatus.draft, workflowStatus.ready);
|
||||||
|
publishWorkflowEntry(entries[2]);
|
||||||
|
publishWorkflowEntry(entries[1]);
|
||||||
|
publishWorkflowEntry(entries[0]);
|
||||||
|
goToCollections();
|
||||||
|
assertPublishedEntry([entries[2], entries[1], entries[0]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can delete an entry', () => {
|
||||||
|
login(getUser());
|
||||||
|
createPost(entries[0]);
|
||||||
|
deleteEntryInEditor();
|
||||||
|
assertOnCollectionsPage();
|
||||||
|
assertEntryDeleted(entries[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can update workflow status from within the editor', () => {
|
||||||
|
login(getUser());
|
||||||
|
createPost(entries[0]);
|
||||||
|
assertWorkflowStatusInEditor(editorStatus.draft);
|
||||||
|
updateWorkflowStatusInEditor(editorStatus.review);
|
||||||
|
assertWorkflowStatusInEditor(editorStatus.review);
|
||||||
|
updateWorkflowStatusInEditor(editorStatus.ready);
|
||||||
|
assertWorkflowStatusInEditor(editorStatus.ready);
|
||||||
|
exitEditor();
|
||||||
|
goToWorkflow();
|
||||||
|
assertWorkflowStatus(entries[0], workflowStatus.ready);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import '../../utils/dismiss-local-backup';
|
||||||
|
import {
|
||||||
|
login,
|
||||||
|
createPostAndExit,
|
||||||
|
goToWorkflow,
|
||||||
|
goToCollections,
|
||||||
|
updateWorkflowStatus,
|
||||||
|
publishWorkflowEntry,
|
||||||
|
assertPublishedEntry,
|
||||||
|
} from '../../utils/steps';
|
||||||
|
import { workflowStatus } from '../../utils/constants';
|
||||||
|
|
||||||
|
const versions = ['2.9.7', '2.10.24'];
|
||||||
|
|
||||||
|
export default function({ entries, getUser }) {
|
||||||
|
versions.forEach(version => {
|
||||||
|
it(`migrate from ${version} to latest`, () => {
|
||||||
|
cy.task('switchToVersion', {
|
||||||
|
version,
|
||||||
|
});
|
||||||
|
cy.reload();
|
||||||
|
|
||||||
|
login(getUser());
|
||||||
|
createPostAndExit(entries[0]);
|
||||||
|
createPostAndExit(entries[1]);
|
||||||
|
createPostAndExit(entries[2]);
|
||||||
|
goToWorkflow();
|
||||||
|
updateWorkflowStatus(entries[2], workflowStatus.draft, workflowStatus.ready);
|
||||||
|
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||||
|
cy.wait(1500); // older versions of the CMS didn't wait fully for the update to be resolved
|
||||||
|
updateWorkflowStatus(entries[1], workflowStatus.draft, workflowStatus.ready);
|
||||||
|
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||||
|
cy.wait(1500); // older versions of the CMS didn't wait fully for the update to be resolved
|
||||||
|
updateWorkflowStatus(entries[0], workflowStatus.draft, workflowStatus.ready);
|
||||||
|
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||||
|
cy.wait(1500); // older versions of the CMS didn't wait fully for the update to be resolved
|
||||||
|
|
||||||
|
cy.task('switchToVersion', {
|
||||||
|
version: 'latest',
|
||||||
|
});
|
||||||
|
cy.reload();
|
||||||
|
|
||||||
|
// allow migration code to run for 5 minutes
|
||||||
|
publishWorkflowEntry(entries[2], 5 * 60 * 1000);
|
||||||
|
publishWorkflowEntry(entries[1]);
|
||||||
|
publishWorkflowEntry(entries[0]);
|
||||||
|
goToCollections();
|
||||||
|
assertPublishedEntry([entries[2], entries[1], entries[0]]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
21
source/admin/cypress/e2e/common/entries.js
Normal file
21
source/admin/cypress/e2e/common/entries.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
export const entry1 = {
|
||||||
|
title: 'first title',
|
||||||
|
body: 'first body',
|
||||||
|
description: 'first description',
|
||||||
|
category: 'first category',
|
||||||
|
tags: 'tag1',
|
||||||
|
};
|
||||||
|
export const entry2 = {
|
||||||
|
title: 'second title',
|
||||||
|
body: 'second body',
|
||||||
|
description: 'second description',
|
||||||
|
category: 'second category',
|
||||||
|
tags: 'tag2',
|
||||||
|
};
|
||||||
|
export const entry3 = {
|
||||||
|
title: 'third title',
|
||||||
|
body: 'third body',
|
||||||
|
description: 'third description',
|
||||||
|
category: 'third category',
|
||||||
|
tags: 'tag3',
|
||||||
|
};
|
||||||
59
source/admin/cypress/e2e/common/i18n.js
Normal file
59
source/admin/cypress/e2e/common/i18n.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { newPost, populateEntry, publishEntry, flushClockAndSave } from '../../utils/steps';
|
||||||
|
|
||||||
|
const enterTranslation = str => {
|
||||||
|
cy.get(`[id^="title-field"]`)
|
||||||
|
.first()
|
||||||
|
.clear({ force: true });
|
||||||
|
cy.get(`[id^="title-field"]`)
|
||||||
|
.first()
|
||||||
|
.type(str, { force: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
const createAndTranslate = entry => {
|
||||||
|
newPost();
|
||||||
|
// fill the main entry
|
||||||
|
populateEntry(entry, () => undefined);
|
||||||
|
|
||||||
|
// fill the translation
|
||||||
|
cy.get('.Pane2').within(() => {
|
||||||
|
enterTranslation('de');
|
||||||
|
|
||||||
|
cy.contains('span', 'Writing in DE').click();
|
||||||
|
cy.contains('span', 'fr').click();
|
||||||
|
|
||||||
|
enterTranslation('fr');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateTranslation = () => {
|
||||||
|
cy.get('.Pane2').within(() => {
|
||||||
|
enterTranslation('fr fr');
|
||||||
|
|
||||||
|
cy.contains('span', 'Writing in FR').click();
|
||||||
|
cy.contains('span', 'de').click();
|
||||||
|
|
||||||
|
enterTranslation('de de');
|
||||||
|
});
|
||||||
|
cy.contains('button', 'Save').click();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const assertTranslation = () => {
|
||||||
|
cy.get('.Pane2').within(() => {
|
||||||
|
cy.get(`[id^="title-field"]`).should('have.value', 'de');
|
||||||
|
|
||||||
|
cy.contains('span', 'Writing in DE').click();
|
||||||
|
cy.contains('span', 'fr').click();
|
||||||
|
|
||||||
|
cy.get(`[id^="title-field"]`).should('have.value', 'fr');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createEntryTranslateAndPublish = entry => {
|
||||||
|
createAndTranslate(entry);
|
||||||
|
publishEntry();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createEntryTranslateAndSave = entry => {
|
||||||
|
createAndTranslate(entry);
|
||||||
|
flushClockAndSave();
|
||||||
|
};
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import '../../utils/dismiss-local-backup';
|
||||||
|
import {
|
||||||
|
login,
|
||||||
|
goToWorkflow,
|
||||||
|
updateWorkflowStatus,
|
||||||
|
exitEditor,
|
||||||
|
publishWorkflowEntry,
|
||||||
|
goToEntry,
|
||||||
|
updateWorkflowStatusInEditor,
|
||||||
|
publishEntryInEditor,
|
||||||
|
assertPublishedEntryInEditor,
|
||||||
|
assertUnpublishedEntryInEditor,
|
||||||
|
assertUnpublishedChangesInEditor,
|
||||||
|
} from '../../utils/steps';
|
||||||
|
import { createEntryTranslateAndSave, assertTranslation, updateTranslation } from './i18n';
|
||||||
|
import { workflowStatus, editorStatus, publishTypes } from '../../utils/constants';
|
||||||
|
|
||||||
|
export default function({ entry, getUser }) {
|
||||||
|
const structures = ['multiple_folders', 'multiple_files', 'single_file'];
|
||||||
|
structures.forEach(structure => {
|
||||||
|
it(`can create and publish entry with translation in ${structure} mode`, () => {
|
||||||
|
cy.task('updateConfig', { i18n: { structure } });
|
||||||
|
|
||||||
|
login(getUser());
|
||||||
|
|
||||||
|
createEntryTranslateAndSave(entry);
|
||||||
|
assertUnpublishedEntryInEditor();
|
||||||
|
exitEditor();
|
||||||
|
goToWorkflow();
|
||||||
|
updateWorkflowStatus(entry, workflowStatus.draft, workflowStatus.ready);
|
||||||
|
publishWorkflowEntry(entry);
|
||||||
|
goToEntry(entry);
|
||||||
|
assertTranslation();
|
||||||
|
assertPublishedEntryInEditor();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`can update translated entry in ${structure} mode`, () => {
|
||||||
|
cy.task('updateConfig', { i18n: { structure: 'multiple_folders' } });
|
||||||
|
|
||||||
|
login(getUser());
|
||||||
|
|
||||||
|
createEntryTranslateAndSave(entry);
|
||||||
|
assertUnpublishedEntryInEditor();
|
||||||
|
updateWorkflowStatusInEditor(editorStatus.ready);
|
||||||
|
publishEntryInEditor(publishTypes.publishNow);
|
||||||
|
exitEditor();
|
||||||
|
goToEntry(entry);
|
||||||
|
assertTranslation();
|
||||||
|
assertPublishedEntryInEditor();
|
||||||
|
updateTranslation();
|
||||||
|
assertUnpublishedChangesInEditor();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
177
source/admin/cypress/e2e/common/media_library.js
Normal file
177
source/admin/cypress/e2e/common/media_library.js
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
import '../../utils/dismiss-local-backup';
|
||||||
|
import {
|
||||||
|
login,
|
||||||
|
goToMediaLibrary,
|
||||||
|
newPost,
|
||||||
|
populateEntry,
|
||||||
|
exitEditor,
|
||||||
|
goToWorkflow,
|
||||||
|
updateWorkflowStatus,
|
||||||
|
publishWorkflowEntry,
|
||||||
|
goToEntry,
|
||||||
|
goToCollections,
|
||||||
|
} from '../../utils/steps';
|
||||||
|
import { workflowStatus } from '../../utils/constants';
|
||||||
|
|
||||||
|
function uploadMediaFile() {
|
||||||
|
assertNoImagesInLibrary();
|
||||||
|
|
||||||
|
const fixture = 'cypress/fixtures/media/netlify.png';
|
||||||
|
cy.get('input[type="file"]').selectFile(fixture, { force: true });
|
||||||
|
cy.contains('span', 'Uploading...').should('not.exist');
|
||||||
|
|
||||||
|
assertImagesInLibrary();
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertImagesInLibrary() {
|
||||||
|
cy.get('img[class*="CardImage"]').should('exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertNoImagesInLibrary() {
|
||||||
|
cy.get('h1')
|
||||||
|
.contains('Loading...')
|
||||||
|
.should('not.exist');
|
||||||
|
cy.get('img[class*="CardImage"]').should('not.exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteImage() {
|
||||||
|
cy.get('img[class*="CardImage"]').click();
|
||||||
|
cy.contains('button', 'Delete selected').click();
|
||||||
|
assertNoImagesInLibrary();
|
||||||
|
}
|
||||||
|
|
||||||
|
function chooseSelectedMediaFile() {
|
||||||
|
cy.contains('button', 'Choose selected').should('not.be.disabled');
|
||||||
|
cy.contains('button', 'Choose selected').click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function chooseAnImage() {
|
||||||
|
cy.contains('button', 'Choose an image').click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function waitForEntryToLoad() {
|
||||||
|
cy.contains('button', 'Saving...').should('not.exist');
|
||||||
|
cy.clock().tick(5000);
|
||||||
|
cy.contains('div', 'Loading entry...').should('not.exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchImageSnapshot() {
|
||||||
|
// cy.matchImageSnapshot();
|
||||||
|
}
|
||||||
|
|
||||||
|
function newPostAndUploadImage() {
|
||||||
|
newPost();
|
||||||
|
chooseAnImage();
|
||||||
|
uploadMediaFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
function newPostWithImage(entry) {
|
||||||
|
newPostAndUploadImage();
|
||||||
|
chooseSelectedMediaFile();
|
||||||
|
populateEntry(entry);
|
||||||
|
waitForEntryToLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
function publishPostWithImage(entry) {
|
||||||
|
newPostWithImage(entry);
|
||||||
|
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||||
|
cy.wait(500);
|
||||||
|
exitEditor();
|
||||||
|
goToWorkflow();
|
||||||
|
updateWorkflowStatus(entry, workflowStatus.draft, workflowStatus.ready);
|
||||||
|
publishWorkflowEntry(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeMediaLibrary() {
|
||||||
|
cy.get('button[class*="CloseButton"]').click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchToGridView() {
|
||||||
|
cy.get('div[class*="ViewControls"]').within(() => {
|
||||||
|
cy.get('button')
|
||||||
|
.last()
|
||||||
|
.click();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertGridEntryImage(entry) {
|
||||||
|
cy.contains('li', entry.title).within(() => {
|
||||||
|
cy.get('div[class*="CardImage"]').should('be.visible');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function({ entries, getUser }) {
|
||||||
|
beforeEach(() => {
|
||||||
|
login(getUser && getUser());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can upload image from global media library', () => {
|
||||||
|
goToMediaLibrary();
|
||||||
|
uploadMediaFile();
|
||||||
|
matchImageSnapshot();
|
||||||
|
closeMediaLibrary();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can delete image from global media library', () => {
|
||||||
|
goToMediaLibrary();
|
||||||
|
uploadMediaFile();
|
||||||
|
closeMediaLibrary();
|
||||||
|
goToMediaLibrary();
|
||||||
|
deleteImage();
|
||||||
|
matchImageSnapshot();
|
||||||
|
closeMediaLibrary();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can upload image from entry media library', () => {
|
||||||
|
newPostAndUploadImage();
|
||||||
|
matchImageSnapshot();
|
||||||
|
closeMediaLibrary();
|
||||||
|
exitEditor();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can save entry with image', () => {
|
||||||
|
newPostWithImage(entries[0]);
|
||||||
|
matchImageSnapshot();
|
||||||
|
exitEditor();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can publish entry with image', () => {
|
||||||
|
publishPostWithImage(entries[0]);
|
||||||
|
goToEntry(entries[0]);
|
||||||
|
waitForEntryToLoad();
|
||||||
|
matchImageSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not show draft entry image in global media library', () => {
|
||||||
|
newPostWithImage(entries[0]);
|
||||||
|
cy.clock().then(clock => {
|
||||||
|
if (clock) {
|
||||||
|
clock.tick(150);
|
||||||
|
clock.tick(150);
|
||||||
|
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||||
|
cy.wait(500);
|
||||||
|
}
|
||||||
|
exitEditor();
|
||||||
|
goToMediaLibrary();
|
||||||
|
assertNoImagesInLibrary();
|
||||||
|
matchImageSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show published entry image in global media library', () => {
|
||||||
|
publishPostWithImage(entries[0]);
|
||||||
|
cy.clock().tick();
|
||||||
|
goToMediaLibrary();
|
||||||
|
assertImagesInLibrary();
|
||||||
|
matchImageSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show published entry image in grid view', () => {
|
||||||
|
publishPostWithImage(entries[0]);
|
||||||
|
goToCollections();
|
||||||
|
switchToGridView();
|
||||||
|
assertGridEntryImage(entries[0]);
|
||||||
|
|
||||||
|
matchImageSnapshot();
|
||||||
|
});
|
||||||
|
}
|
||||||
76
source/admin/cypress/e2e/common/open_authoring.js
Normal file
76
source/admin/cypress/e2e/common/open_authoring.js
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import '../../utils/dismiss-local-backup';
|
||||||
|
import {
|
||||||
|
login,
|
||||||
|
createPostAndExit,
|
||||||
|
updateExistingPostAndExit,
|
||||||
|
goToWorkflow,
|
||||||
|
deleteWorkflowEntry,
|
||||||
|
updateWorkflowStatus,
|
||||||
|
publishWorkflowEntry,
|
||||||
|
} from '../../utils/steps';
|
||||||
|
import { workflowStatus } from '../../utils/constants';
|
||||||
|
|
||||||
|
export default function({ entries, getUser, getForkUser }) {
|
||||||
|
it('successfully loads', () => {
|
||||||
|
login(getUser());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can create an entry', () => {
|
||||||
|
login(getUser());
|
||||||
|
createPostAndExit(entries[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can update an entry', () => {
|
||||||
|
login(getUser());
|
||||||
|
createPostAndExit(entries[0]);
|
||||||
|
updateExistingPostAndExit(entries[0], entries[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can publish an editorial workflow entry', () => {
|
||||||
|
login(getUser());
|
||||||
|
createPostAndExit(entries[0]);
|
||||||
|
goToWorkflow();
|
||||||
|
updateWorkflowStatus(entries[0], workflowStatus.draft, workflowStatus.ready);
|
||||||
|
publishWorkflowEntry(entries[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('successfully forks repository and loads', () => {
|
||||||
|
login(getForkUser());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can create an entry on fork', () => {
|
||||||
|
login(getForkUser());
|
||||||
|
createPostAndExit(entries[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can update a draft entry on fork', () => {
|
||||||
|
login(getForkUser());
|
||||||
|
createPostAndExit(entries[0]);
|
||||||
|
updateExistingPostAndExit(entries[0], entries[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can change entry status from fork', () => {
|
||||||
|
login(getForkUser());
|
||||||
|
createPostAndExit(entries[0]);
|
||||||
|
goToWorkflow();
|
||||||
|
updateWorkflowStatus(entries[0], workflowStatus.draft, workflowStatus.review);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can delete review entry from fork', () => {
|
||||||
|
login(getForkUser());
|
||||||
|
createPostAndExit(entries[0]);
|
||||||
|
goToWorkflow();
|
||||||
|
updateWorkflowStatus(entries[0], workflowStatus.draft, workflowStatus.review);
|
||||||
|
deleteWorkflowEntry(entries[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can return entry to draft and delete it', () => {
|
||||||
|
login(getForkUser());
|
||||||
|
createPostAndExit(entries[0]);
|
||||||
|
goToWorkflow();
|
||||||
|
updateWorkflowStatus(entries[0], workflowStatus.draft, workflowStatus.review);
|
||||||
|
|
||||||
|
updateWorkflowStatus(entries[0], workflowStatus.review, workflowStatus.draft);
|
||||||
|
deleteWorkflowEntry(entries[0]);
|
||||||
|
});
|
||||||
|
}
|
||||||
14
source/admin/cypress/e2e/common/simple_workflow.js
Normal file
14
source/admin/cypress/e2e/common/simple_workflow.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import '../../utils/dismiss-local-backup';
|
||||||
|
import { login, createPostAndPublish, assertPublishedEntry } from '../../utils/steps';
|
||||||
|
|
||||||
|
export default function({ entries, getUser }) {
|
||||||
|
it('successfully loads', () => {
|
||||||
|
login(getUser());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can create an entry', () => {
|
||||||
|
login(getUser());
|
||||||
|
createPostAndPublish(entries[0]);
|
||||||
|
assertPublishedEntry(entries[0]);
|
||||||
|
});
|
||||||
|
}
|
||||||
65
source/admin/cypress/e2e/common/spec_utils.js
Normal file
65
source/admin/cypress/e2e/common/spec_utils.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
export const before = (taskResult, options, backend) => {
|
||||||
|
Cypress.config('taskTimeout', 7 * 60 * 1000);
|
||||||
|
cy.task('setupBackend', { backend, options }).then(data => {
|
||||||
|
taskResult.data = data;
|
||||||
|
Cypress.config('defaultCommandTimeout', data.mockResponses ? 5 * 1000 : 1 * 60 * 1000);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const after = (taskResult, backend) => {
|
||||||
|
cy.task('teardownBackend', {
|
||||||
|
backend,
|
||||||
|
...taskResult.data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const beforeEach = (taskResult, backend) => {
|
||||||
|
const spec = Cypress.mocha.getRunner().suite.ctx.currentTest.parent.title;
|
||||||
|
const testName = Cypress.mocha.getRunner().suite.ctx.currentTest.title;
|
||||||
|
cy.task('setupBackendTest', {
|
||||||
|
backend,
|
||||||
|
...taskResult.data,
|
||||||
|
spec,
|
||||||
|
testName,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (taskResult.data.mockResponses) {
|
||||||
|
const fixture = `${spec}__${testName}.json`;
|
||||||
|
console.log('loading fixture:', fixture);
|
||||||
|
cy.stubFetch({ fixture });
|
||||||
|
}
|
||||||
|
|
||||||
|
return cy.clock(0, ['Date']);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const afterEach = (taskResult, backend) => {
|
||||||
|
const spec = Cypress.mocha.getRunner().suite.ctx.currentTest.parent.title;
|
||||||
|
const testName = Cypress.mocha.getRunner().suite.ctx.currentTest.title;
|
||||||
|
|
||||||
|
cy.task('teardownBackendTest', {
|
||||||
|
backend,
|
||||||
|
...taskResult.data,
|
||||||
|
spec,
|
||||||
|
testName,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!process.env.RECORD_FIXTURES) {
|
||||||
|
const {
|
||||||
|
suite: {
|
||||||
|
ctx: {
|
||||||
|
currentTest: { state, _retries: retries, _currentRetry: currentRetry },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} = Cypress.mocha.getRunner();
|
||||||
|
if (state === 'failed' && retries === currentRetry) {
|
||||||
|
Cypress.runner.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const seedRepo = (taskResult, backend) => {
|
||||||
|
cy.task('seedRepo', {
|
||||||
|
backend,
|
||||||
|
...taskResult.data,
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import fixture from './common/editorial_workflow';
|
||||||
|
import * as specUtils from './common/spec_utils';
|
||||||
|
import { entry1, entry2, entry3 } from './common/entries';
|
||||||
|
|
||||||
|
const backend = 'bitbucket';
|
||||||
|
|
||||||
|
describe('BitBucket Backend Editorial Workflow', () => {
|
||||||
|
let taskResult = { data: {} };
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
specUtils.before(taskResult, { publish_mode: 'editorial_workflow' }, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
specUtils.after(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
specUtils.beforeEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
specUtils.afterEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture({
|
||||||
|
entries: [entry1, entry2, entry3],
|
||||||
|
getUser: () => taskResult.data.user,
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import fixture from './common/editorial_workflow';
|
||||||
|
import * as specUtils from './common/spec_utils';
|
||||||
|
import { entry1, entry2, entry3 } from './common/entries';
|
||||||
|
|
||||||
|
const backend = 'git-gateway';
|
||||||
|
const provider = 'github';
|
||||||
|
|
||||||
|
describe('Git Gateway (GitHub) Backend Editorial Workflow', () => {
|
||||||
|
let taskResult = { data: {} };
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
specUtils.before(taskResult, { publish_mode: 'editorial_workflow', provider }, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
specUtils.after(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
specUtils.beforeEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
specUtils.afterEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture({
|
||||||
|
entries: [entry1, entry2, entry3],
|
||||||
|
getUser: () => taskResult.data.user,
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import fixture from './common/editorial_workflow';
|
||||||
|
import * as specUtils from './common/spec_utils';
|
||||||
|
import { entry1, entry2, entry3 } from './common/entries';
|
||||||
|
|
||||||
|
const backend = 'git-gateway';
|
||||||
|
const provider = 'gitlab';
|
||||||
|
|
||||||
|
describe('Git Gateway (GitLab) Backend Editorial Workflow', () => {
|
||||||
|
let taskResult = { data: {} };
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
specUtils.before(taskResult, { publish_mode: 'editorial_workflow', provider }, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
specUtils.after(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
specUtils.beforeEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
specUtils.afterEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture({
|
||||||
|
entries: [entry1, entry2, entry3],
|
||||||
|
getUser: () => taskResult.data.user,
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import fixture from './common/editorial_workflow';
|
||||||
|
import * as specUtils from './common/spec_utils';
|
||||||
|
import { entry1, entry2, entry3 } from './common/entries';
|
||||||
|
|
||||||
|
const backend = 'github';
|
||||||
|
|
||||||
|
describe('GitHub Backend Editorial Workflow - GraphQL API', () => {
|
||||||
|
const taskResult = { data: {} };
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
specUtils.before(
|
||||||
|
taskResult,
|
||||||
|
{
|
||||||
|
backend: { use_graphql: true, open_authoring: false },
|
||||||
|
publish_mode: 'editorial_workflow',
|
||||||
|
},
|
||||||
|
backend,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
specUtils.after(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
specUtils.beforeEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
specUtils.afterEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture({
|
||||||
|
entries: [entry1, entry2, entry3],
|
||||||
|
getUser: () => taskResult.data.user,
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import fixture from './common/open_authoring';
|
||||||
|
import * as specUtils from './common/spec_utils';
|
||||||
|
import { entry1, entry2, entry3 } from './common/entries';
|
||||||
|
|
||||||
|
const backend = 'github';
|
||||||
|
|
||||||
|
describe('GitHub Backend Editorial Workflow - GraphQL API - Open Authoring', () => {
|
||||||
|
const taskResult = { data: {} };
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
specUtils.before(
|
||||||
|
taskResult,
|
||||||
|
{
|
||||||
|
backend: { use_graphql: true, open_authoring: true },
|
||||||
|
publish_mode: 'editorial_workflow',
|
||||||
|
},
|
||||||
|
backend,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
specUtils.after(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
specUtils.beforeEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
specUtils.afterEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture({
|
||||||
|
entries: [entry1, entry2, entry3],
|
||||||
|
getUser: () => taskResult.data.user,
|
||||||
|
getForkUser: () => taskResult.data.forkUser,
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import fixture from './common/editorial_workflow';
|
||||||
|
import * as specUtils from './common/spec_utils';
|
||||||
|
import { entry1, entry2, entry3 } from './common/entries';
|
||||||
|
|
||||||
|
const backend = 'github';
|
||||||
|
|
||||||
|
describe('GitHub Backend Editorial Workflow - REST API', () => {
|
||||||
|
let taskResult = { data: {} };
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
specUtils.before(
|
||||||
|
taskResult,
|
||||||
|
{
|
||||||
|
backend: { use_graphql: false, open_authoring: false },
|
||||||
|
publish_mode: 'editorial_workflow',
|
||||||
|
},
|
||||||
|
backend,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
specUtils.after(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
specUtils.beforeEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
specUtils.afterEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture({
|
||||||
|
entries: [entry1, entry2, entry3],
|
||||||
|
getUser: () => taskResult.data.user,
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import fixture from './common/open_authoring';
|
||||||
|
import * as specUtils from './common/spec_utils';
|
||||||
|
import { entry1, entry2, entry3 } from './common/entries';
|
||||||
|
|
||||||
|
const backend = 'github';
|
||||||
|
|
||||||
|
describe('GitHub Backend Editorial Workflow - REST API - Open Authoring', () => {
|
||||||
|
let taskResult = { data: {} };
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
specUtils.before(
|
||||||
|
taskResult,
|
||||||
|
{
|
||||||
|
backend: { use_graphql: false, open_authoring: true },
|
||||||
|
publish_mode: 'editorial_workflow',
|
||||||
|
},
|
||||||
|
backend,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
specUtils.after(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
specUtils.beforeEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
specUtils.afterEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture({
|
||||||
|
entries: [entry1, entry2, entry3],
|
||||||
|
getUser: () => taskResult.data.user,
|
||||||
|
getForkUser: () => taskResult.data.forkUser,
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import fixture from './common/editorial_workflow';
|
||||||
|
import * as specUtils from './common/spec_utils';
|
||||||
|
import { entry1, entry2, entry3 } from './common/entries';
|
||||||
|
|
||||||
|
const backend = 'gitlab';
|
||||||
|
|
||||||
|
describe('GitLab Backend Editorial Workflow', () => {
|
||||||
|
let taskResult = { data: {} };
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
specUtils.before(taskResult, { publish_mode: 'editorial_workflow' }, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
specUtils.after(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
if (
|
||||||
|
Cypress.mocha.getRunner().suite.ctx.currentTest.title ===
|
||||||
|
'can change status on and publish multiple entries'
|
||||||
|
) {
|
||||||
|
Cypress.mocha.getRunner().suite.ctx.currentTest.skip();
|
||||||
|
}
|
||||||
|
specUtils.beforeEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
specUtils.afterEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture({
|
||||||
|
entries: [entry1, entry2, entry3],
|
||||||
|
getUser: () => taskResult.data.user,
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import fixture from './common/editorial_workflow';
|
||||||
|
import * as specUtils from './common/spec_utils';
|
||||||
|
import { entry1, entry2, entry3 } from './common/entries';
|
||||||
|
|
||||||
|
const backend = 'proxy';
|
||||||
|
const mode = 'git';
|
||||||
|
|
||||||
|
describe.skip(`Proxy Backend Editorial Workflow - '${mode}' mode`, () => {
|
||||||
|
let taskResult = { data: {} };
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
specUtils.before(taskResult, { publish_mode: 'editorial_workflow', mode }, backend);
|
||||||
|
Cypress.config('defaultCommandTimeout', 5 * 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
specUtils.after(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
specUtils.beforeEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
specUtils.afterEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture({
|
||||||
|
entries: [entry1, entry2, entry3],
|
||||||
|
getUser: () => taskResult.data.user,
|
||||||
|
});
|
||||||
|
});
|
||||||
312
source/admin/cypress/e2e/editorial_workflow_spec_test_backend.js
Normal file
312
source/admin/cypress/e2e/editorial_workflow_spec_test_backend.js
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
import '../utils/dismiss-local-backup';
|
||||||
|
import {
|
||||||
|
login,
|
||||||
|
createPost,
|
||||||
|
createPostAndExit,
|
||||||
|
exitEditor,
|
||||||
|
goToWorkflow,
|
||||||
|
goToCollections,
|
||||||
|
updateWorkflowStatus,
|
||||||
|
publishWorkflowEntry,
|
||||||
|
assertWorkflowStatusInEditor,
|
||||||
|
assertPublishedEntry,
|
||||||
|
deleteEntryInEditor,
|
||||||
|
assertOnCollectionsPage,
|
||||||
|
assertEntryDeleted,
|
||||||
|
assertWorkflowStatus,
|
||||||
|
updateWorkflowStatusInEditor,
|
||||||
|
unpublishEntry,
|
||||||
|
publishEntryInEditor,
|
||||||
|
duplicateEntry,
|
||||||
|
goToEntry,
|
||||||
|
populateEntry,
|
||||||
|
publishAndCreateNewEntryInEditor,
|
||||||
|
publishAndDuplicateEntryInEditor,
|
||||||
|
assertNotification,
|
||||||
|
assertFieldValidationError,
|
||||||
|
} from '../utils/steps';
|
||||||
|
import { workflowStatus, editorStatus, publishTypes, notifications } from '../utils/constants';
|
||||||
|
|
||||||
|
const entry1 = {
|
||||||
|
title: 'first title',
|
||||||
|
body: 'first body',
|
||||||
|
};
|
||||||
|
const entry2 = {
|
||||||
|
title: 'second title',
|
||||||
|
body: 'second body',
|
||||||
|
};
|
||||||
|
const entry3 = {
|
||||||
|
title: 'third title',
|
||||||
|
body: 'third body',
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Test Backend Editorial Workflow', () => {
|
||||||
|
after(() => {
|
||||||
|
cy.task('teardownBackend', { backend: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
Cypress.config('defaultCommandTimeout', 4000);
|
||||||
|
cy.task('setupBackend', { backend: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.task('updateConfig', { collections: [{ publish: true }] });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('successfully loads', () => {
|
||||||
|
login();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can create an entry', () => {
|
||||||
|
login();
|
||||||
|
createPost(entry1);
|
||||||
|
|
||||||
|
// new entry should show 'Delete unpublished entry'
|
||||||
|
cy.contains('button', 'Delete unpublished entry');
|
||||||
|
cy.url().should(
|
||||||
|
'eq',
|
||||||
|
`http://localhost:8080/#/collections/posts/entries/1970-01-01-${entry1.title
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/\s/, '-')}`,
|
||||||
|
);
|
||||||
|
exitEditor();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can publish an editorial workflow entry', () => {
|
||||||
|
login();
|
||||||
|
createPostAndExit(entry1);
|
||||||
|
goToWorkflow();
|
||||||
|
updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.ready);
|
||||||
|
publishWorkflowEntry(entry1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can update an entry', () => {
|
||||||
|
login();
|
||||||
|
createPostAndExit(entry1);
|
||||||
|
goToWorkflow();
|
||||||
|
updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.ready);
|
||||||
|
publishWorkflowEntry(entry1);
|
||||||
|
|
||||||
|
goToEntry(entry1);
|
||||||
|
populateEntry(entry2);
|
||||||
|
// existing entry should show 'Delete unpublished changes'
|
||||||
|
cy.contains('button', 'Delete unpublished changes');
|
||||||
|
// existing entry slug should remain the same after save'
|
||||||
|
cy.url().should(
|
||||||
|
'eq',
|
||||||
|
`http://localhost:8080/#/collections/posts/entries/1970-01-01-${entry1.title
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/\s/, '-')}`,
|
||||||
|
);
|
||||||
|
exitEditor();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can change workflow status', () => {
|
||||||
|
login();
|
||||||
|
createPostAndExit(entry1);
|
||||||
|
goToWorkflow();
|
||||||
|
updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.review);
|
||||||
|
updateWorkflowStatus(entry1, workflowStatus.review, workflowStatus.ready);
|
||||||
|
updateWorkflowStatus(entry1, workflowStatus.ready, workflowStatus.review);
|
||||||
|
updateWorkflowStatus(entry1, workflowStatus.review, workflowStatus.draft);
|
||||||
|
updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.ready);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can change status on and publish multiple entries', () => {
|
||||||
|
login();
|
||||||
|
createPostAndExit(entry1);
|
||||||
|
createPostAndExit(entry2);
|
||||||
|
createPostAndExit(entry3);
|
||||||
|
goToWorkflow();
|
||||||
|
updateWorkflowStatus(entry3, workflowStatus.draft, workflowStatus.ready);
|
||||||
|
updateWorkflowStatus(entry2, workflowStatus.draft, workflowStatus.ready);
|
||||||
|
updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.ready);
|
||||||
|
publishWorkflowEntry(entry3);
|
||||||
|
publishWorkflowEntry(entry2);
|
||||||
|
publishWorkflowEntry(entry1);
|
||||||
|
goToCollections();
|
||||||
|
assertPublishedEntry([entry3, entry2, entry1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can delete an entry', () => {
|
||||||
|
login();
|
||||||
|
createPost(entry1);
|
||||||
|
deleteEntryInEditor();
|
||||||
|
assertOnCollectionsPage();
|
||||||
|
assertEntryDeleted(entry1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can update workflow status from within the editor', () => {
|
||||||
|
login();
|
||||||
|
createPost(entry1);
|
||||||
|
assertWorkflowStatusInEditor(editorStatus.draft);
|
||||||
|
updateWorkflowStatusInEditor(editorStatus.review);
|
||||||
|
assertWorkflowStatusInEditor(editorStatus.review);
|
||||||
|
updateWorkflowStatusInEditor(editorStatus.ready);
|
||||||
|
assertWorkflowStatusInEditor(editorStatus.ready);
|
||||||
|
exitEditor();
|
||||||
|
goToWorkflow();
|
||||||
|
assertWorkflowStatus(entry1, workflowStatus.ready);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can unpublish an existing entry', () => {
|
||||||
|
// first publish an entry
|
||||||
|
login();
|
||||||
|
createPostAndExit(entry1);
|
||||||
|
goToWorkflow();
|
||||||
|
updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.ready);
|
||||||
|
publishWorkflowEntry(entry1);
|
||||||
|
// then unpublish it
|
||||||
|
unpublishEntry(entry1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can duplicate an existing entry', () => {
|
||||||
|
login();
|
||||||
|
createPost(entry1);
|
||||||
|
updateWorkflowStatusInEditor(editorStatus.ready);
|
||||||
|
publishEntryInEditor(publishTypes.publishNow);
|
||||||
|
duplicateEntry(entry1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cannot publish when "publish" is false', () => {
|
||||||
|
cy.task('updateConfig', { collections: [{ publish: false }] });
|
||||||
|
login();
|
||||||
|
createPost(entry1);
|
||||||
|
cy.contains('span', 'Publish').should('not.exist');
|
||||||
|
exitEditor();
|
||||||
|
goToWorkflow();
|
||||||
|
updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.ready);
|
||||||
|
cy.contains('button', 'Publish new entry').should('not.exist');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can create a new entry, publish and create new', () => {
|
||||||
|
login();
|
||||||
|
createPost(entry1);
|
||||||
|
updateWorkflowStatusInEditor(editorStatus.ready);
|
||||||
|
|
||||||
|
publishAndCreateNewEntryInEditor(entry1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can create a new entry, publish and duplicate', () => {
|
||||||
|
login();
|
||||||
|
createPost(entry1);
|
||||||
|
updateWorkflowStatusInEditor(editorStatus.ready);
|
||||||
|
publishAndDuplicateEntryInEditor(entry1);
|
||||||
|
});
|
||||||
|
|
||||||
|
const inSidebar = func => {
|
||||||
|
cy.get('[class*=SidebarNavList]').within(func);
|
||||||
|
};
|
||||||
|
|
||||||
|
const inGrid = func => {
|
||||||
|
cy.get('[class*=CardsGrid]').within(func);
|
||||||
|
};
|
||||||
|
|
||||||
|
it('can access nested collection items', () => {
|
||||||
|
login();
|
||||||
|
|
||||||
|
inSidebar(() => cy.contains('a', 'Pages').click());
|
||||||
|
inSidebar(() => cy.contains('a', /^Directory$/));
|
||||||
|
inGrid(() => cy.contains('a', 'Root Page'));
|
||||||
|
|
||||||
|
inSidebar(() => cy.contains('a', /^Directory$/).click());
|
||||||
|
|
||||||
|
inSidebar(() => cy.contains('a', /^Sub Directory$/));
|
||||||
|
inSidebar(() => cy.contains('a', 'Another Sub Directory'));
|
||||||
|
|
||||||
|
inSidebar(() => cy.contains('a', /^Sub Directory$/).click());
|
||||||
|
inSidebar(() => cy.contains('a', 'Nested Directory'));
|
||||||
|
cy.url().should(
|
||||||
|
'eq',
|
||||||
|
'http://localhost:8080/#/collections/pages/filter/directory/sub-directory',
|
||||||
|
);
|
||||||
|
|
||||||
|
inSidebar(() => cy.contains('a', 'Pages').click());
|
||||||
|
inSidebar(() => cy.contains('a', 'Pages').click());
|
||||||
|
|
||||||
|
inGrid(() => cy.contains('a', 'Another Sub Directory').should('not.exist'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can navigate to nested entry', () => {
|
||||||
|
login();
|
||||||
|
|
||||||
|
inSidebar(() => cy.contains('a', 'Pages').click());
|
||||||
|
inSidebar(() => cy.contains('a', /^Directory$/).click());
|
||||||
|
inSidebar(() => cy.contains('a', 'Another Sub Directory').click());
|
||||||
|
inGrid(() => cy.contains('a', 'Another Sub Directory'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`can create a new entry with custom path`, () => {
|
||||||
|
login();
|
||||||
|
|
||||||
|
inSidebar(() => cy.contains('a', 'Pages').click());
|
||||||
|
inSidebar(() => cy.contains('a', /^Directory$/).click());
|
||||||
|
inSidebar(() => cy.contains('a', /^Sub Directory$/).click());
|
||||||
|
cy.contains('a', 'New Page').click();
|
||||||
|
|
||||||
|
cy.get('[id^="path-field"]').should('have.value', 'directory/sub-directory');
|
||||||
|
cy.get('[id^="path-field"]').type('/new-path');
|
||||||
|
cy.get('[id^="title-field"]').type('New Path Title');
|
||||||
|
cy.clock().then(clock => {
|
||||||
|
clock.tick(150);
|
||||||
|
});
|
||||||
|
cy.contains('button', 'Save').click();
|
||||||
|
assertNotification(notifications.saved);
|
||||||
|
updateWorkflowStatusInEditor(editorStatus.ready);
|
||||||
|
publishEntryInEditor(publishTypes.publishNow);
|
||||||
|
exitEditor();
|
||||||
|
|
||||||
|
inSidebar(() => cy.contains('a', 'New Path Title'));
|
||||||
|
inSidebar(() => cy.contains('a', /^Directory$/).click());
|
||||||
|
inSidebar(() => cy.contains('a', /^Directory$/).click());
|
||||||
|
inGrid(() => cy.contains('a', 'New Path Title').should('not.exist'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`can't create an entry with an existing path`, () => {
|
||||||
|
login();
|
||||||
|
|
||||||
|
inSidebar(() => cy.contains('a', 'Pages').click());
|
||||||
|
inSidebar(() => cy.contains('a', /^Directory$/).click());
|
||||||
|
inSidebar(() => cy.contains('a', /^Sub Directory$/).click());
|
||||||
|
|
||||||
|
cy.contains('a', 'New Page').click();
|
||||||
|
cy.get('[id^="title-field"]').type('New Path Title');
|
||||||
|
cy.clock().then(clock => {
|
||||||
|
clock.tick(150);
|
||||||
|
});
|
||||||
|
cy.contains('button', 'Save').click();
|
||||||
|
|
||||||
|
assertFieldValidationError({
|
||||||
|
message: `Path 'directory/sub-directory' already exists`,
|
||||||
|
fieldLabel: 'Path',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can move an existing entry to a new path', () => {
|
||||||
|
login();
|
||||||
|
|
||||||
|
inSidebar(() => cy.contains('a', 'Pages').click());
|
||||||
|
inSidebar(() => cy.contains('a', /^Directory$/).click());
|
||||||
|
inGrid(() => cy.contains('a', /^Directory$/).click());
|
||||||
|
|
||||||
|
cy.get('[id^="path-field"]').should('have.value', 'directory');
|
||||||
|
cy.get('[id^="path-field"]').clear();
|
||||||
|
cy.get('[id^="path-field"]').type('new-directory');
|
||||||
|
cy.get('[id^="title-field"]').clear();
|
||||||
|
cy.get('[id^="title-field"]').type('New Directory');
|
||||||
|
cy.clock().then(clock => {
|
||||||
|
clock.tick(150);
|
||||||
|
});
|
||||||
|
cy.contains('button', 'Save').click();
|
||||||
|
assertNotification(notifications.saved);
|
||||||
|
updateWorkflowStatusInEditor(editorStatus.ready);
|
||||||
|
publishEntryInEditor(publishTypes.publishNow);
|
||||||
|
exitEditor();
|
||||||
|
|
||||||
|
inSidebar(() => cy.contains('a', 'New Directory').click());
|
||||||
|
|
||||||
|
inSidebar(() => cy.contains('a', /^Sub Directory$/));
|
||||||
|
inSidebar(() => cy.contains('a', 'Another Sub Directory'));
|
||||||
|
});
|
||||||
|
});
|
||||||
109
source/admin/cypress/e2e/field_validations_spec.js
Normal file
109
source/admin/cypress/e2e/field_validations_spec.js
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import '../utils/dismiss-local-backup';
|
||||||
|
import {
|
||||||
|
login,
|
||||||
|
validateObjectFieldsAndExit,
|
||||||
|
validateNestedObjectFieldsAndExit,
|
||||||
|
validateListFieldsAndExit,
|
||||||
|
validateNestedListFieldsAndExit,
|
||||||
|
} from '../utils/steps';
|
||||||
|
import { setting1, setting2 } from '../utils/constants';
|
||||||
|
|
||||||
|
const nestedListConfig = {
|
||||||
|
collections: [
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
name: 'settings',
|
||||||
|
label: 'Settings',
|
||||||
|
editor: { preview: false },
|
||||||
|
files: [
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
name: 'hotel_locations',
|
||||||
|
label: 'Hotel Locations',
|
||||||
|
file: '_data/hotel_locations.yml',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: 'Country',
|
||||||
|
name: 'country',
|
||||||
|
widget: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Hotel Locations',
|
||||||
|
name: 'hotel_locations',
|
||||||
|
widget: 'list',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: 'Cities',
|
||||||
|
name: 'cities',
|
||||||
|
widget: 'list',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: 'City',
|
||||||
|
name: 'city',
|
||||||
|
widget: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Number of Hotels in City',
|
||||||
|
name: 'number_of_hotels_in_city',
|
||||||
|
widget: 'number',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'City Locations',
|
||||||
|
name: 'city_locations',
|
||||||
|
widget: 'list',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: 'Hotel Name',
|
||||||
|
name: 'hotel_name',
|
||||||
|
widget: 'string',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Test Backend Editorial Workflow', () => {
|
||||||
|
after(() => {
|
||||||
|
cy.task('teardownBackend', { backend: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
Cypress.config('defaultCommandTimeout', 4000);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.task('setupBackend', { backend: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can validate object fields', () => {
|
||||||
|
login();
|
||||||
|
validateObjectFieldsAndExit(setting1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can validate fields nested in an object field', () => {
|
||||||
|
login();
|
||||||
|
validateNestedObjectFieldsAndExit(setting1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can validate list fields', () => {
|
||||||
|
login();
|
||||||
|
validateListFieldsAndExit(setting2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can validate deeply nested list fields', () => {
|
||||||
|
cy.task('updateConfig', nestedListConfig);
|
||||||
|
|
||||||
|
login();
|
||||||
|
validateNestedListFieldsAndExit(setting2);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import fixture from './common/i18n_editorial_workflow_spec';
|
||||||
|
|
||||||
|
const backend = 'test';
|
||||||
|
|
||||||
|
describe(`I18N Test Backend Editorial Workflow`, () => {
|
||||||
|
const taskResult = { data: {} };
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
Cypress.config('defaultCommandTimeout', 4000);
|
||||||
|
cy.task('setupBackend', {
|
||||||
|
backend,
|
||||||
|
options: {
|
||||||
|
publish_mode: 'editorial_workflow',
|
||||||
|
i18n: {
|
||||||
|
locales: ['en', 'de', 'fr'],
|
||||||
|
},
|
||||||
|
collections: [
|
||||||
|
{
|
||||||
|
folder: 'content/i18n',
|
||||||
|
i18n: true,
|
||||||
|
fields: [{ i18n: true }, {}, { i18n: 'duplicate' }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
cy.task('teardownBackend', { backend });
|
||||||
|
});
|
||||||
|
|
||||||
|
const entry = {
|
||||||
|
title: 'first title',
|
||||||
|
body: 'first body',
|
||||||
|
};
|
||||||
|
|
||||||
|
fixture({ entry, getUser: () => taskResult.data.user });
|
||||||
|
});
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
import * as specUtils from './common/spec_utils';
|
||||||
|
import { login } from '../utils/steps';
|
||||||
|
import { createEntryTranslateAndPublish } from './common/i18n';
|
||||||
|
|
||||||
|
const backend = 'proxy';
|
||||||
|
const mode = 'fs';
|
||||||
|
|
||||||
|
const expectedEnContent = `---
|
||||||
|
template: post
|
||||||
|
title: first title
|
||||||
|
date: 1970-01-01T01:00
|
||||||
|
description: first description
|
||||||
|
category: first category
|
||||||
|
tags:
|
||||||
|
- tag1
|
||||||
|
---
|
||||||
|
`;
|
||||||
|
|
||||||
|
const expectedDeContent = `---
|
||||||
|
title: de
|
||||||
|
date: 1970-01-01T01:00
|
||||||
|
---
|
||||||
|
`;
|
||||||
|
|
||||||
|
const expectedFrContent = `---
|
||||||
|
title: fr
|
||||||
|
date: 1970-01-01T01:00
|
||||||
|
---
|
||||||
|
`;
|
||||||
|
|
||||||
|
const contentSingleFile = `---
|
||||||
|
en:
|
||||||
|
template: post
|
||||||
|
date: 1970-01-01T01:00
|
||||||
|
title: first title
|
||||||
|
body: first body
|
||||||
|
description: first description
|
||||||
|
category: first category
|
||||||
|
tags:
|
||||||
|
- tag1
|
||||||
|
de:
|
||||||
|
date: 1970-01-01T01:00
|
||||||
|
title: de
|
||||||
|
fr:
|
||||||
|
date: 1970-01-01T01:00
|
||||||
|
title: fr
|
||||||
|
---
|
||||||
|
`;
|
||||||
|
|
||||||
|
describe(`I18N Proxy Backend Simple Workflow - '${mode}' mode`, () => {
|
||||||
|
const taskResult = { data: {} };
|
||||||
|
|
||||||
|
const entry = {
|
||||||
|
title: 'first title',
|
||||||
|
body: 'first body',
|
||||||
|
description: 'first description',
|
||||||
|
category: 'first category',
|
||||||
|
tags: 'tag1',
|
||||||
|
};
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
specUtils.before(
|
||||||
|
taskResult,
|
||||||
|
{
|
||||||
|
mode,
|
||||||
|
publish_mode: 'simple',
|
||||||
|
i18n: {
|
||||||
|
locales: ['en', 'de', 'fr'],
|
||||||
|
},
|
||||||
|
collections: [{ i18n: true, fields: [{}, { i18n: true }, {}, { i18n: 'duplicate' }] }],
|
||||||
|
},
|
||||||
|
backend,
|
||||||
|
);
|
||||||
|
Cypress.config('taskTimeout', 15 * 1000);
|
||||||
|
Cypress.config('defaultCommandTimeout', 5 * 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
specUtils.after(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
specUtils.beforeEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
specUtils.afterEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can create entry with translation in locale_folders mode', () => {
|
||||||
|
cy.task('updateConfig', { i18n: { structure: 'multiple_folders' } });
|
||||||
|
|
||||||
|
login(taskResult.data.user);
|
||||||
|
|
||||||
|
createEntryTranslateAndPublish(entry);
|
||||||
|
|
||||||
|
cy.readFile(`${taskResult.data.tempDir}/content/posts/en/1970-01-01-first-title.md`).should(
|
||||||
|
'contain',
|
||||||
|
expectedEnContent,
|
||||||
|
);
|
||||||
|
|
||||||
|
cy.readFile(`${taskResult.data.tempDir}/content/posts/de/1970-01-01-first-title.md`).should(
|
||||||
|
'eq',
|
||||||
|
expectedDeContent,
|
||||||
|
);
|
||||||
|
|
||||||
|
cy.readFile(`${taskResult.data.tempDir}/content/posts/fr/1970-01-01-first-title.md`).should(
|
||||||
|
'eq',
|
||||||
|
expectedFrContent,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can create entry with translation in single_file mode', () => {
|
||||||
|
cy.task('updateConfig', { i18n: { structure: 'multiple_files' } });
|
||||||
|
|
||||||
|
login(taskResult.data.user);
|
||||||
|
|
||||||
|
createEntryTranslateAndPublish(entry);
|
||||||
|
|
||||||
|
cy.readFile(`${taskResult.data.tempDir}/content/posts/1970-01-01-first-title.en.md`).should(
|
||||||
|
'contain',
|
||||||
|
expectedEnContent,
|
||||||
|
);
|
||||||
|
|
||||||
|
cy.readFile(`${taskResult.data.tempDir}/content/posts/1970-01-01-first-title.de.md`).should(
|
||||||
|
'eq',
|
||||||
|
expectedDeContent,
|
||||||
|
);
|
||||||
|
|
||||||
|
cy.readFile(`${taskResult.data.tempDir}/content/posts/1970-01-01-first-title.fr.md`).should(
|
||||||
|
'eq',
|
||||||
|
expectedFrContent,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can create entry with translation in locale_file_extensions mode', () => {
|
||||||
|
cy.task('updateConfig', { i18n: { structure: 'single_file' } });
|
||||||
|
|
||||||
|
login(taskResult.data.user);
|
||||||
|
|
||||||
|
createEntryTranslateAndPublish(entry);
|
||||||
|
|
||||||
|
cy.readFile(`${taskResult.data.tempDir}/content/posts/1970-01-01-first-title.md`).should(
|
||||||
|
'eq',
|
||||||
|
contentSingleFile,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
94
source/admin/cypress/e2e/markdown_widget_backspace_spec.js
Normal file
94
source/admin/cypress/e2e/markdown_widget_backspace_spec.js
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import '../utils/dismiss-local-backup';
|
||||||
|
|
||||||
|
describe('Markdown widget', () => {
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
Cypress.config('defaultCommandTimeout', 4000);
|
||||||
|
cy.task('setupBackend', { backend: 'test' });
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.loginAndNewPost();
|
||||||
|
cy.clearMarkdownEditorContent();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
cy.task('teardownBackend', { backend: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
// describe('pressing backspace', () => {
|
||||||
|
it('sets non-default block to default when empty', () => {
|
||||||
|
cy.focused()
|
||||||
|
.clickHeadingOneButton()
|
||||||
|
.backspace()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<p></p>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
it('moves to previous block when no character left to delete', () => {
|
||||||
|
cy.focused()
|
||||||
|
.type('foo')
|
||||||
|
.enter()
|
||||||
|
.clickHeadingOneButton()
|
||||||
|
.type('a')
|
||||||
|
.backspace({times: 2})
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<p>foo</p>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
it('does nothing at start of first block in document when non-empty and non-default', () => {
|
||||||
|
cy.focused()
|
||||||
|
.clickHeadingOneButton()
|
||||||
|
.type('foo')
|
||||||
|
.setCursorBefore('foo')
|
||||||
|
.backspace({ times: 4 })
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<h1>foo</h1>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
it('deletes individual characters in middle of non-empty non-default block in document', () => {
|
||||||
|
cy.focused()
|
||||||
|
.clickHeadingOneButton()
|
||||||
|
.type('foo')
|
||||||
|
.setCursorAfter('fo')
|
||||||
|
.backspace({ times: 3 })
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<h1>o</h1>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
it('at beginning of non-first block, moves default block content to previous block', () => {
|
||||||
|
cy.focused()
|
||||||
|
.clickHeadingOneButton()
|
||||||
|
.type('foo')
|
||||||
|
.enter()
|
||||||
|
.type('bar')
|
||||||
|
.setCursorBefore('bar')
|
||||||
|
.backspace()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<h1>foobar</h1>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
it('at beginning of non-first block, moves non-default block content to previous block', () => {
|
||||||
|
cy.focused()
|
||||||
|
.type('foo')
|
||||||
|
.enter()
|
||||||
|
.clickHeadingOneButton()
|
||||||
|
.type('bar')
|
||||||
|
.enter()
|
||||||
|
.clickHeadingTwoButton()
|
||||||
|
.type('baz')
|
||||||
|
.setCursorBefore('baz')
|
||||||
|
.backspace()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<p>foo</p>
|
||||||
|
<h1>barbaz</h1>
|
||||||
|
`)
|
||||||
|
.setCursorBefore('bar')
|
||||||
|
.backspace()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<p>foobarbaz</p>
|
||||||
|
`);
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
});
|
||||||
136
source/admin/cypress/e2e/markdown_widget_code_block_spec.js
Normal file
136
source/admin/cypress/e2e/markdown_widget_code_block_spec.js
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import { oneLineTrim, stripIndent } from 'common-tags';
|
||||||
|
import '../utils/dismiss-local-backup';
|
||||||
|
|
||||||
|
describe('Markdown widget code block', () => {
|
||||||
|
before(() => {
|
||||||
|
Cypress.config('defaultCommandTimeout', 4000);
|
||||||
|
cy.task('setupBackend', { backend: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.loginAndNewPost();
|
||||||
|
cy.clearMarkdownEditorContent();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
cy.task('teardownBackend', { backend: 'test' });
|
||||||
|
});
|
||||||
|
describe('code block', () => {
|
||||||
|
it('outputs code', () => {
|
||||||
|
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||||
|
cy
|
||||||
|
.insertCodeBlock()
|
||||||
|
.type('foo')
|
||||||
|
.enter()
|
||||||
|
.type('bar')
|
||||||
|
.confirmMarkdownEditorContent(
|
||||||
|
`
|
||||||
|
${codeBlock(`
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
`)}
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.wait(500)
|
||||||
|
.clickModeToggle().confirmMarkdownEditorContent(`
|
||||||
|
${codeBlockRaw(`
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
`)}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function codeBlockRaw(content) {
|
||||||
|
return ['```', ...stripIndent(content).split('\n'), '```']
|
||||||
|
.map(
|
||||||
|
line => oneLineTrim`
|
||||||
|
<div>
|
||||||
|
<span>
|
||||||
|
<span>
|
||||||
|
<span>${line}</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function codeBlock(content) {
|
||||||
|
const lines = stripIndent(content)
|
||||||
|
.split('\n')
|
||||||
|
.map(
|
||||||
|
(line, idx) => `
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div>${idx + 1}</div>
|
||||||
|
</div>
|
||||||
|
<pre><span>${line}</span></pre>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.join('');
|
||||||
|
|
||||||
|
return oneLineTrim`
|
||||||
|
<div>
|
||||||
|
<div></div>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div><label>Code Block</label></div>
|
||||||
|
<div><button><span><svg>
|
||||||
|
<path></path>
|
||||||
|
</svg></span></button>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div><textarea></textarea></div>
|
||||||
|
<div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<pre><span>xxxxxxxxxx</span></pre>
|
||||||
|
</div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div>
|
||||||
|
<div> </div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
${lines}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div></div>
|
||||||
|
<div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>
|
||||||
|
<span>
|
||||||
|
<span></span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
109
source/admin/cypress/e2e/markdown_widget_enter_spec.js
Normal file
109
source/admin/cypress/e2e/markdown_widget_enter_spec.js
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import '../utils/dismiss-local-backup';
|
||||||
|
|
||||||
|
describe('Markdown widget breaks', () => {
|
||||||
|
before(() => {
|
||||||
|
Cypress.config('defaultCommandTimeout', 4000);
|
||||||
|
cy.task('setupBackend', { backend: 'test' });
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.loginAndNewPost();
|
||||||
|
cy.clearMarkdownEditorContent();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
cy.task('teardownBackend', { backend: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('pressing enter', () => {
|
||||||
|
it('creates new default block from empty block', () => {
|
||||||
|
cy.focused()
|
||||||
|
.enter()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<p></p>
|
||||||
|
<p></p>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
it('creates new default block when selection collapsed at end of block', () => {
|
||||||
|
cy.focused()
|
||||||
|
.type('foo')
|
||||||
|
.enter()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<p>foo</p>
|
||||||
|
<p></p>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
it('creates new default block when selection collapsed at end of non-default block', () => {
|
||||||
|
cy.clickHeadingOneButton()
|
||||||
|
.type('foo')
|
||||||
|
.enter()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<h1>foo</h1>
|
||||||
|
<p></p>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
it('creates new default block when selection collapsed in empty non-default block', () => {
|
||||||
|
cy.clickHeadingOneButton()
|
||||||
|
.enter()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<h1></h1>
|
||||||
|
<p></p>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
it('splits block into two same-type blocks when collapsed selection at block start', () => {
|
||||||
|
cy.clickHeadingOneButton()
|
||||||
|
.type('foo')
|
||||||
|
.setCursorBefore('foo')
|
||||||
|
.enter()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<h1></h1>
|
||||||
|
<h1>foo</h1>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
it('splits block into two same-type blocks when collapsed in middle of selection at block start', () => {
|
||||||
|
cy.clickHeadingOneButton()
|
||||||
|
.type('foo')
|
||||||
|
.setCursorBefore('oo')
|
||||||
|
.enter()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<h1>f</h1>
|
||||||
|
<h1>oo</h1>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
it('deletes selected content and splits to same-type block when selection is expanded', () => {
|
||||||
|
cy.clickHeadingOneButton()
|
||||||
|
.type('foo bar')
|
||||||
|
.setSelection('o b')
|
||||||
|
.enter()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<h1>fo</h1>
|
||||||
|
<h1>ar</h1>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('pressing shift+enter', () => {
|
||||||
|
it('creates line break', () => {
|
||||||
|
cy.focused()
|
||||||
|
.enter({ shift: true })
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<p>
|
||||||
|
<br>
|
||||||
|
</p>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
it('creates consecutive line break', () => {
|
||||||
|
cy.focused()
|
||||||
|
.enter({ shift: true, times: 4 })
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<p>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
</p>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
106
source/admin/cypress/e2e/markdown_widget_hotkeys_spec.js
Normal file
106
source/admin/cypress/e2e/markdown_widget_hotkeys_spec.js
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import '../utils/dismiss-local-backup';
|
||||||
|
import {HOT_KEY_MAP} from "../utils/constants";
|
||||||
|
const headingNumberToWord = ['', 'one', 'two', 'three', 'four', 'five', 'six'];
|
||||||
|
const isMac = Cypress.platform === 'darwin';
|
||||||
|
const modifierKey = isMac ? '{meta}' : '{ctrl}';
|
||||||
|
// eslint-disable-next-line func-style
|
||||||
|
const replaceMod = (str) => str.replace(/mod\+/g, modifierKey).replace(/shift\+/g, '{shift}');
|
||||||
|
|
||||||
|
describe('Markdown widget hotkeys', () => {
|
||||||
|
describe('hot keys', () => {
|
||||||
|
before(() => {
|
||||||
|
Cypress.config('defaultCommandTimeout', 4000);
|
||||||
|
cy.task('setupBackend', { backend: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.loginAndNewPost();
|
||||||
|
cy.clearMarkdownEditorContent();
|
||||||
|
cy.focused()
|
||||||
|
.type('foo')
|
||||||
|
.setSelection('foo').as('selection');
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
cy.task('teardownBackend', { backend: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('bold', () => {
|
||||||
|
it('pressing mod+b bolds the text', () => {
|
||||||
|
cy.get('@selection')
|
||||||
|
.type(replaceMod(HOT_KEY_MAP['bold']))
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<p>
|
||||||
|
<strong>foo</strong>
|
||||||
|
</p>
|
||||||
|
`)
|
||||||
|
.type(replaceMod(HOT_KEY_MAP['bold']));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('italic', () => {
|
||||||
|
it('pressing mod+i italicizes the text', () => {
|
||||||
|
cy.get('@selection')
|
||||||
|
.type(replaceMod(HOT_KEY_MAP['italic']))
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<p>
|
||||||
|
<em>foo</em>
|
||||||
|
</p>
|
||||||
|
`)
|
||||||
|
.type(replaceMod(HOT_KEY_MAP['italic']));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('strikethrough', () => {
|
||||||
|
it('pressing mod+shift+s displays a strike through the text', () => {
|
||||||
|
cy.get('@selection')
|
||||||
|
.type(replaceMod(HOT_KEY_MAP['strikethrough']))
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<p>
|
||||||
|
<s>foo</s>
|
||||||
|
</p>
|
||||||
|
`).type(replaceMod(HOT_KEY_MAP['strikethrough']));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('code', () => {
|
||||||
|
it('pressing mod+shift+c displays a code block around the text', () => {
|
||||||
|
cy.get('@selection')
|
||||||
|
.type(replaceMod(HOT_KEY_MAP['code']))
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<p>
|
||||||
|
<code>foo</code>
|
||||||
|
</p>
|
||||||
|
`).type(replaceMod(HOT_KEY_MAP['code']));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('link', () => {
|
||||||
|
before(() => {
|
||||||
|
|
||||||
|
});
|
||||||
|
it('pressing mod+k transforms the text to a link', () => {
|
||||||
|
cy.window().then((win) => {
|
||||||
|
cy.get('@selection')
|
||||||
|
.type(replaceMod(HOT_KEY_MAP['link']))
|
||||||
|
cy.stub(win, 'prompt').returns('https://google.com');
|
||||||
|
cy.confirmMarkdownEditorContent('<p><a>foo</a></p>')
|
||||||
|
.type(replaceMod(HOT_KEY_MAP['link']));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('headings', () => {
|
||||||
|
for (let i = 1; i <= 6; i++) {
|
||||||
|
it(`pressing mod+${i} transforms the text to a heading`, () => {
|
||||||
|
cy.get('@selection')
|
||||||
|
.type(replaceMod(HOT_KEY_MAP[`heading-${headingNumberToWord[i]}`]))
|
||||||
|
.confirmMarkdownEditorContent(`<h${i}>foo</h${i}>`)
|
||||||
|
.type(replaceMod(HOT_KEY_MAP[`heading-${headingNumberToWord[i]}`]))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
70
source/admin/cypress/e2e/markdown_widget_link_spec.js
Normal file
70
source/admin/cypress/e2e/markdown_widget_link_spec.js
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import '../utils/dismiss-local-backup';
|
||||||
|
|
||||||
|
describe('Markdown widget link', () => {
|
||||||
|
before(() => {
|
||||||
|
Cypress.config('defaultCommandTimeout', 4000);
|
||||||
|
cy.task('setupBackend', { backend: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.loginAndNewPost();
|
||||||
|
cy.clearMarkdownEditorContent();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
cy.task('teardownBackend', { backend: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('link', () => {
|
||||||
|
it('can add a new valid link', () => {
|
||||||
|
const link = 'https://www.decapcms.org/';
|
||||||
|
cy.window().then(win => {
|
||||||
|
cy.stub(win, 'prompt').returns(link);
|
||||||
|
});
|
||||||
|
cy.focused().clickLinkButton();
|
||||||
|
|
||||||
|
cy.confirmMarkdownEditorContent(`<p><a>${link}</a></p>`);
|
||||||
|
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||||
|
cy.wait(300);
|
||||||
|
cy.clickModeToggle();
|
||||||
|
|
||||||
|
cy.confirmRawEditorContent(`<${link}>`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can add a new invalid link', () => {
|
||||||
|
const link = 'www.decapcms.org';
|
||||||
|
cy.window().then(win => {
|
||||||
|
cy.stub(win, 'prompt').returns(link);
|
||||||
|
});
|
||||||
|
cy.focused().clickLinkButton();
|
||||||
|
|
||||||
|
cy.confirmMarkdownEditorContent(`<p><a>${link}</a></p>`);
|
||||||
|
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||||
|
cy.wait(300);
|
||||||
|
cy.clickModeToggle();
|
||||||
|
|
||||||
|
cy.confirmRawEditorContent(`[${link}](${link})`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can select existing text as link', () => {
|
||||||
|
const link = 'https://www.decapcms.org';
|
||||||
|
cy.window().then(win => {
|
||||||
|
cy.stub(win, 'prompt').returns(link);
|
||||||
|
});
|
||||||
|
|
||||||
|
const text = 'Decap CMS';
|
||||||
|
cy.focused()
|
||||||
|
.getMarkdownEditor()
|
||||||
|
.type(text)
|
||||||
|
.setSelection(text)
|
||||||
|
.clickLinkButton();
|
||||||
|
|
||||||
|
cy.confirmMarkdownEditorContent(`<p><a>${text}</a></p>`);
|
||||||
|
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||||
|
cy.wait(300);
|
||||||
|
cy.clickModeToggle();
|
||||||
|
|
||||||
|
cy.confirmRawEditorContent(`[${text}](${link})`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
736
source/admin/cypress/e2e/markdown_widget_list_spec.js
Normal file
736
source/admin/cypress/e2e/markdown_widget_list_spec.js
Normal file
@@ -0,0 +1,736 @@
|
|||||||
|
import '../utils/dismiss-local-backup';
|
||||||
|
|
||||||
|
describe('Markdown widget', () => {
|
||||||
|
describe('list', () => {
|
||||||
|
before(() => {
|
||||||
|
Cypress.config('defaultCommandTimeout', 4000);
|
||||||
|
cy.task('setupBackend', { backend: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.loginAndNewPost();
|
||||||
|
cy.clearMarkdownEditorContent();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
cy.task('teardownBackend', { backend: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
// describe('toolbar buttons', () => {
|
||||||
|
it('creates and focuses empty list', () => {
|
||||||
|
cy.clickUnorderedListButton().confirmMarkdownEditorContent(`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p></p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes list', () => {
|
||||||
|
cy.clickUnorderedListButton().clickUnorderedListButton().confirmMarkdownEditorContent(`
|
||||||
|
<p></p>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('converts a list item to a paragraph block which is a sibling of the parent list', () => {
|
||||||
|
cy.clickUnorderedListButton().type('foo').enter().clickUnorderedListButton()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>foo</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p></p>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('converts empty nested list item to empty paragraph block in parent list item', () => {
|
||||||
|
cy
|
||||||
|
.clickUnorderedListButton()
|
||||||
|
.type('foo')
|
||||||
|
.enter()
|
||||||
|
.tabkey()
|
||||||
|
.type('bar')
|
||||||
|
.enter()
|
||||||
|
.tabkey()
|
||||||
|
.confirmMarkdownEditorContent(
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>foo</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>bar</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p></p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.clickUnorderedListButton()
|
||||||
|
.confirmMarkdownEditorContent(
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>foo</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>bar</p>
|
||||||
|
<p></p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.backspace({ times: 4 })
|
||||||
|
.clickUnorderedListButton().confirmMarkdownEditorContent(`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>foo</p>
|
||||||
|
<p></p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('moves nested list item content to parent list item when in first block', () => {
|
||||||
|
cy
|
||||||
|
.clickUnorderedListButton()
|
||||||
|
.type('foo')
|
||||||
|
.enter()
|
||||||
|
.tabkey()
|
||||||
|
.type('bar')
|
||||||
|
.enter()
|
||||||
|
.tabkey()
|
||||||
|
.type('baz')
|
||||||
|
.clickUnorderedListButton()
|
||||||
|
.confirmMarkdownEditorContent(
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>foo</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>bar</p>
|
||||||
|
<p>baz</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.up()
|
||||||
|
.clickUnorderedListButton()
|
||||||
|
.confirmMarkdownEditorContent(
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>foo</p>
|
||||||
|
<p>bar</p>
|
||||||
|
<p>baz</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.up()
|
||||||
|
.clickUnorderedListButton().confirmMarkdownEditorContent(`
|
||||||
|
<p>foo</p>
|
||||||
|
<p>bar</p>
|
||||||
|
<p>baz</p>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('affects only the current block with collapsed selection', () => {
|
||||||
|
cy
|
||||||
|
.focused()
|
||||||
|
.type('foo')
|
||||||
|
.enter()
|
||||||
|
.type('bar')
|
||||||
|
.enter()
|
||||||
|
.type('baz')
|
||||||
|
.up()
|
||||||
|
.clickUnorderedListButton().confirmMarkdownEditorContent(`
|
||||||
|
<p>foo</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>bar</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>baz</p>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('wrap each bottom-most block in a selection with a list item block', () => {
|
||||||
|
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||||
|
cy
|
||||||
|
.focused()
|
||||||
|
.type('foo')
|
||||||
|
.enter()
|
||||||
|
.type('bar')
|
||||||
|
.enter()
|
||||||
|
.type('baz')
|
||||||
|
.setSelection('foo', 'baz')
|
||||||
|
.wait(500)
|
||||||
|
.clickUnorderedListButton().confirmMarkdownEditorContent(`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>foo</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>bar</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>baz</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('unwraps list item block from each selected list item and unwraps all of them from the outer list block', () => {
|
||||||
|
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||||
|
cy
|
||||||
|
.clickUnorderedListButton()
|
||||||
|
.type('foo')
|
||||||
|
.enter()
|
||||||
|
.type('bar')
|
||||||
|
.enter()
|
||||||
|
.type('baz')
|
||||||
|
.setSelection('foo', 'baz')
|
||||||
|
.wait(500)
|
||||||
|
.clickUnorderedListButton().confirmMarkdownEditorContent(`
|
||||||
|
<p>foo</p>
|
||||||
|
<p>bar</p>
|
||||||
|
<p>baz</p>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('combines adjacent same-typed lists, not differently typed lists', () => {
|
||||||
|
cy.focused()
|
||||||
|
.type('foo')
|
||||||
|
.enter()
|
||||||
|
.type('bar')
|
||||||
|
.enter()
|
||||||
|
.type('baz')
|
||||||
|
.up()
|
||||||
|
.clickUnorderedListButton()
|
||||||
|
.up()
|
||||||
|
.clickUnorderedListButton()
|
||||||
|
.confirmMarkdownEditorContent(
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>foo</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>bar</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>baz</p>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.down({ times: 2 })
|
||||||
|
.focused()
|
||||||
|
.clickUnorderedListButton()
|
||||||
|
.confirmMarkdownEditorContent(
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>foo</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>bar</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>baz</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.up()
|
||||||
|
.enter()
|
||||||
|
.type('qux')
|
||||||
|
.tabkey()
|
||||||
|
.confirmMarkdownEditorContent(
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>foo</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>bar</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>qux</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>baz</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.up()
|
||||||
|
.enter()
|
||||||
|
.type('quux')
|
||||||
|
.confirmMarkdownEditorContent(
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>foo</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>bar</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>quux</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>qux</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>baz</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.clickOrderedListButton()
|
||||||
|
.confirmMarkdownEditorContent(
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>foo</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>bar</p>
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
<p>quux</p>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>qux</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>baz</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.setSelection({
|
||||||
|
anchorQuery: 'ul > li > ol p',
|
||||||
|
anchorOffset: 1,
|
||||||
|
focusQuery: 'ul > li > ul:last-child p',
|
||||||
|
focusOffset: 2,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// while this works on dev environment, it will always fail in cypress - has something to do with text selection
|
||||||
|
// it('affects only selected list items', () => {
|
||||||
|
// cy
|
||||||
|
// .clickUnorderedListButton()
|
||||||
|
// .type('foo')
|
||||||
|
// .enter()
|
||||||
|
// .type('bar')
|
||||||
|
// .enter()
|
||||||
|
// .type('baz')
|
||||||
|
// .setSelection('bar')
|
||||||
|
// .clickUnorderedListButton()
|
||||||
|
// .confirmMarkdownEditorContent(
|
||||||
|
// `
|
||||||
|
// <ul>
|
||||||
|
// <li>
|
||||||
|
// <p>foo</p>
|
||||||
|
// </li>
|
||||||
|
// </ul>
|
||||||
|
// <p>bar</p>
|
||||||
|
// <ul>
|
||||||
|
// <li>
|
||||||
|
// <p>baz</p>
|
||||||
|
// </li>
|
||||||
|
// </ul>
|
||||||
|
// `,
|
||||||
|
// )
|
||||||
|
// .clickUnorderedListButton()
|
||||||
|
// .setSelection('bar', 'baz')
|
||||||
|
// .clickUnorderedListButton()
|
||||||
|
// .confirmMarkdownEditorContent(
|
||||||
|
// `
|
||||||
|
// <ul>
|
||||||
|
// <li>
|
||||||
|
// <p>foo</p>
|
||||||
|
// </li>
|
||||||
|
// </ul>
|
||||||
|
// <p>bar</p>
|
||||||
|
// <p>baz</p>
|
||||||
|
// `,
|
||||||
|
// )
|
||||||
|
// .clickUnorderedListButton()
|
||||||
|
// .confirmMarkdownEditorContent(
|
||||||
|
// `
|
||||||
|
// <ul>
|
||||||
|
// <li>
|
||||||
|
// <p>foo</p>
|
||||||
|
// </li>
|
||||||
|
// <li>
|
||||||
|
// <p>bar</p>
|
||||||
|
// </li>
|
||||||
|
// <li>
|
||||||
|
// <p>baz</p>
|
||||||
|
// </li>
|
||||||
|
// </ul>
|
||||||
|
// `,
|
||||||
|
// )
|
||||||
|
// .setSelection('baz')
|
||||||
|
// .clickUnorderedListButton()
|
||||||
|
// .confirmMarkdownEditorContent(
|
||||||
|
// `
|
||||||
|
// <ul>
|
||||||
|
// <li>
|
||||||
|
// <p>foo</p>
|
||||||
|
// </li>
|
||||||
|
// <li>
|
||||||
|
// <p>bar</p>
|
||||||
|
// </li>
|
||||||
|
// </ul>
|
||||||
|
// <p>baz</p>
|
||||||
|
// `,
|
||||||
|
// )
|
||||||
|
// .clickUnorderedListButton()
|
||||||
|
// .tabkey()
|
||||||
|
// .setCursorAfter('baz')
|
||||||
|
// .enter()
|
||||||
|
// .tabkey()
|
||||||
|
// .type('qux')
|
||||||
|
// .confirmMarkdownEditorContent(
|
||||||
|
// `
|
||||||
|
// <ul>
|
||||||
|
// <li>
|
||||||
|
// <p>foo</p>
|
||||||
|
// </li>
|
||||||
|
// <li>
|
||||||
|
// <p>bar</p>
|
||||||
|
// <ul>
|
||||||
|
// <li>
|
||||||
|
// <p>baz</p>
|
||||||
|
// <ul>
|
||||||
|
// <li>
|
||||||
|
// <p>qux</p>
|
||||||
|
// </li>
|
||||||
|
// </ul>
|
||||||
|
// </li>
|
||||||
|
// </ul>
|
||||||
|
// </li>
|
||||||
|
// </ul>
|
||||||
|
// `,
|
||||||
|
// )
|
||||||
|
// .setSelection('baz')
|
||||||
|
// .clickOrderedListButton()
|
||||||
|
// .confirmMarkdownEditorContent(
|
||||||
|
// `
|
||||||
|
// <ul>
|
||||||
|
// <li>
|
||||||
|
// <p>foo</p>
|
||||||
|
// </li>
|
||||||
|
// <li>
|
||||||
|
// <p>bar</p>
|
||||||
|
// <ol>
|
||||||
|
// <li>
|
||||||
|
// <p>baz</p>
|
||||||
|
// <ul>
|
||||||
|
// <li>
|
||||||
|
// <p>qux</p>
|
||||||
|
// </li>
|
||||||
|
// </ul>
|
||||||
|
// </li>
|
||||||
|
// </ol>
|
||||||
|
// </li>
|
||||||
|
// </ul>
|
||||||
|
// `,
|
||||||
|
// )
|
||||||
|
// .setCursorAfter('qux')
|
||||||
|
// .enter({ times: 2 })
|
||||||
|
// .clickUnorderedListButton()
|
||||||
|
// .confirmMarkdownEditorContent(`
|
||||||
|
// <ul>
|
||||||
|
// <li>
|
||||||
|
// <p>foo</p>
|
||||||
|
// </li>
|
||||||
|
// <li>
|
||||||
|
// <p>bar</p>
|
||||||
|
// <ol>
|
||||||
|
// <li>
|
||||||
|
// <p>baz</p>
|
||||||
|
// <ul>
|
||||||
|
// <li>
|
||||||
|
// <p>qux</p>
|
||||||
|
// </li>
|
||||||
|
// </ul>
|
||||||
|
// </li>
|
||||||
|
// </ol>
|
||||||
|
// <ul>
|
||||||
|
// <li>
|
||||||
|
// <p></p>
|
||||||
|
// </li>
|
||||||
|
// </ul>
|
||||||
|
// </li>
|
||||||
|
// </ul>
|
||||||
|
// `);
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
// describe('on Enter', () => {
|
||||||
|
it('removes the list item and list if empty', () => {
|
||||||
|
cy.clickUnorderedListButton().enter().confirmMarkdownEditorContent(`
|
||||||
|
<p></p>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates a new list item in a non-empty list', () => {
|
||||||
|
cy
|
||||||
|
.clickUnorderedListButton()
|
||||||
|
.type('foo')
|
||||||
|
.enter()
|
||||||
|
.confirmMarkdownEditorContent(
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>foo</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p></p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.type('bar')
|
||||||
|
.enter().confirmMarkdownEditorContent(`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>foo</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>bar</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p></p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates a new default block below a list when hitting Enter twice on an empty list item of the list', () => {
|
||||||
|
cy.clickUnorderedListButton().type('foo').enter({ times: 2 }).confirmMarkdownEditorContent(`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>foo</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p></p>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
// });
|
||||||
|
|
||||||
|
// describe('on Backspace', () => {
|
||||||
|
it('removes the list item and list if empty', () => {
|
||||||
|
cy.clickUnorderedListButton().backspace().confirmMarkdownEditorContent(`
|
||||||
|
<p></p>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes the list item if list not empty', () => {
|
||||||
|
cy.clickUnorderedListButton().type('foo').enter().backspace().confirmMarkdownEditorContent(`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>foo</p>
|
||||||
|
<p></p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not remove list item if empty with non-default block', () => {
|
||||||
|
cy.clickUnorderedListButton().clickHeadingOneButton().backspace()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p></p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
// });
|
||||||
|
|
||||||
|
// describe('on Tab', () => {
|
||||||
|
it('does nothing in top level list', () => {
|
||||||
|
cy
|
||||||
|
.clickUnorderedListButton()
|
||||||
|
.tabkey()
|
||||||
|
.confirmMarkdownEditorContent(
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p></p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.type('foo')
|
||||||
|
.tabkey().confirmMarkdownEditorContent(`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>foo</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('indents nested list items', () => {
|
||||||
|
cy
|
||||||
|
.clickUnorderedListButton()
|
||||||
|
.type('foo')
|
||||||
|
.enter()
|
||||||
|
.type('bar')
|
||||||
|
.tabkey()
|
||||||
|
.confirmMarkdownEditorContent(
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>foo</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>bar</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.enter()
|
||||||
|
.tabkey().confirmMarkdownEditorContent(`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>foo</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>bar</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p></p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('only nests up to one level down from the parent list', () => {
|
||||||
|
cy.clickUnorderedListButton().type('foo').enter().tabkey().confirmMarkdownEditorContent(`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>foo</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p></p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('unindents nested list items with shift', () => {
|
||||||
|
cy.clickUnorderedListButton().type('foo').enter().tabkey().tabkey({ shift: true })
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>foo</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p></p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('indents and unindents from one level below parent back to document root', () => {
|
||||||
|
cy
|
||||||
|
.clickUnorderedListButton()
|
||||||
|
.type('foo')
|
||||||
|
.enter()
|
||||||
|
.tabkey()
|
||||||
|
.type('bar')
|
||||||
|
.enter()
|
||||||
|
.tabkey()
|
||||||
|
.type('baz')
|
||||||
|
.confirmMarkdownEditorContent(
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>foo</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>bar</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>baz</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.tabkey({ shift: true })
|
||||||
|
.confirmMarkdownEditorContent(
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>foo</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>bar</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>baz</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.tabkey({ shift: true }).confirmMarkdownEditorContent(`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>foo</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>bar</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>baz</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
});
|
||||||
37
source/admin/cypress/e2e/markdown_widget_marks_spec.js
Normal file
37
source/admin/cypress/e2e/markdown_widget_marks_spec.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import '../utils/dismiss-local-backup';
|
||||||
|
|
||||||
|
describe('Markdown widget', () => {
|
||||||
|
describe('code mark', () => {
|
||||||
|
before(() => {
|
||||||
|
Cypress.config('defaultCommandTimeout', 4000);
|
||||||
|
cy.task('setupBackend', { backend: 'test' });
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.loginAndNewPost();
|
||||||
|
cy.clearMarkdownEditorContent();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
cy.task('teardownBackend', { backend: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toolbar button', () => {
|
||||||
|
it('can combine code mark with other marks', () => {
|
||||||
|
cy.clickItalicButton()
|
||||||
|
.type('foo')
|
||||||
|
.setSelection('oo')
|
||||||
|
.clickCodeButton()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<p>
|
||||||
|
<em>f</em>
|
||||||
|
<code>
|
||||||
|
<em>oo</em>
|
||||||
|
</code>
|
||||||
|
</p>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
360
source/admin/cypress/e2e/markdown_widget_quote_spec.js
Normal file
360
source/admin/cypress/e2e/markdown_widget_quote_spec.js
Normal file
@@ -0,0 +1,360 @@
|
|||||||
|
import '../utils/dismiss-local-backup';
|
||||||
|
|
||||||
|
describe('Markdown widget', () => {
|
||||||
|
describe('quote block', () => {
|
||||||
|
before(() => {
|
||||||
|
Cypress.config('defaultCommandTimeout', 4000);
|
||||||
|
cy.task('setupBackend', { backend: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.loginAndNewPost();
|
||||||
|
cy.clearMarkdownEditorContent();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
cy.task('teardownBackend', { backend: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
// describe('toggle quote', () => {
|
||||||
|
it('toggles empty quote block on and off in empty editor', () => {
|
||||||
|
cy.clickQuoteButton()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<blockquote>
|
||||||
|
<p></p>
|
||||||
|
</blockquote>
|
||||||
|
`)
|
||||||
|
.clickQuoteButton()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<p></p>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
it('toggles empty quote block on and off for current block', () => {
|
||||||
|
cy.focused()
|
||||||
|
.type('foo')
|
||||||
|
.clickQuoteButton()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<blockquote>
|
||||||
|
<p>foo</p>
|
||||||
|
</blockquote>
|
||||||
|
`)
|
||||||
|
.clickQuoteButton()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<p>foo</p>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
it('toggles entire quote block without expanded selection', () => {
|
||||||
|
cy.clickQuoteButton()
|
||||||
|
.type('foo')
|
||||||
|
.enter()
|
||||||
|
.type('bar')
|
||||||
|
.clickQuoteButton()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<p>foo</p>
|
||||||
|
<p>bar</p>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
it('toggles entire quote block with complex content', () => {
|
||||||
|
cy.clickQuoteButton()
|
||||||
|
.clickUnorderedListButton()
|
||||||
|
.clickHeadingOneButton()
|
||||||
|
.type('foo')
|
||||||
|
.enter({ times: 2 }) // First Enter creates new list item. Second Enter turns that list item into a default block.
|
||||||
|
.clickQuoteButton() // Unwrap the quote block.
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<h1>foo</h1>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p></p>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
it('toggles empty quote block on and off for selected blocks', () => {
|
||||||
|
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||||
|
cy.focused()
|
||||||
|
.type('foo')
|
||||||
|
.enter()
|
||||||
|
.type('bar')
|
||||||
|
.setSelection('foo', 'bar')
|
||||||
|
.wait(500)
|
||||||
|
.clickQuoteButton()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<blockquote>
|
||||||
|
<p>foo</p>
|
||||||
|
<p>bar</p>
|
||||||
|
</blockquote>
|
||||||
|
`)
|
||||||
|
.clickQuoteButton()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<p>foo</p>
|
||||||
|
<p>bar</p>
|
||||||
|
`)
|
||||||
|
.clickQuoteButton()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<blockquote>
|
||||||
|
<p>foo</p>
|
||||||
|
<p>bar</p>
|
||||||
|
</blockquote>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
it('toggles empty quote block on and off for partially selected blocks', () => {
|
||||||
|
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||||
|
cy.focused()
|
||||||
|
.type('foo')
|
||||||
|
.enter()
|
||||||
|
.type('bar')
|
||||||
|
.setSelection('oo', 'ba')
|
||||||
|
.wait(500)
|
||||||
|
.clickQuoteButton()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<blockquote>
|
||||||
|
<p>foo</p>
|
||||||
|
<p>bar</p>
|
||||||
|
</blockquote>
|
||||||
|
`)
|
||||||
|
.clickQuoteButton()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<p>foo</p>
|
||||||
|
<p>bar</p>
|
||||||
|
`)
|
||||||
|
.clickQuoteButton()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<blockquote>
|
||||||
|
<p>foo</p>
|
||||||
|
<p>bar</p>
|
||||||
|
</blockquote>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
it('toggles quote block on and off for multiple selected list items', () => {
|
||||||
|
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||||
|
cy.focused()
|
||||||
|
.clickUnorderedListButton()
|
||||||
|
.type('foo')
|
||||||
|
.enter()
|
||||||
|
.type('bar')
|
||||||
|
.setSelection('foo', 'bar')
|
||||||
|
.wait(500)
|
||||||
|
.clickQuoteButton()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<blockquote>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>foo</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>bar</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</blockquote>
|
||||||
|
`)
|
||||||
|
.clickQuoteButton()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>foo</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>bar</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`)
|
||||||
|
.setCursorAfter('bar')
|
||||||
|
.wait(500)
|
||||||
|
.enter()
|
||||||
|
.type('baz')
|
||||||
|
.setSelection('bar', 'baz')
|
||||||
|
.wait(500)
|
||||||
|
.clickQuoteButton()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>foo</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<blockquote>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>bar</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>baz</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</blockquote>
|
||||||
|
`)
|
||||||
|
});
|
||||||
|
it('creates new quote block if parent is not a quote, can deeply nest', () => {
|
||||||
|
cy.clickQuoteButton()
|
||||||
|
.clickUnorderedListButton()
|
||||||
|
.clickQuoteButton()
|
||||||
|
.clickUnorderedListButton()
|
||||||
|
.clickQuoteButton()
|
||||||
|
.clickUnorderedListButton()
|
||||||
|
.clickQuoteButton()
|
||||||
|
.type('foo')
|
||||||
|
// Content should contains 4 <blockquote> tags and 3 <ul> tags
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<blockquote>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<blockquote>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<blockquote>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<blockquote>
|
||||||
|
<p>foo</p>
|
||||||
|
</blockquote>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</blockquote>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</blockquote>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</blockquote>
|
||||||
|
`)
|
||||||
|
/*
|
||||||
|
* First Enter creates new paragraph within the innermost block quote.
|
||||||
|
* Second Enter moves that paragraph one level up to become sibling of the previous quote block and direct child of a list item.
|
||||||
|
* Third Enter to turn that paragraph into a list item and move it one level up.
|
||||||
|
* Repeat the circle for three more times to reach the second list item of the outermost list block.
|
||||||
|
* Then Enter again to turn that list item into a paragraph and move it one level up to become sibling of the outermost list and
|
||||||
|
* direct child of the outermost block quote.
|
||||||
|
*/
|
||||||
|
.enter({ times: 10 })
|
||||||
|
.type('bar')
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<blockquote>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<blockquote>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<blockquote>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<blockquote>
|
||||||
|
<p>foo</p>
|
||||||
|
</blockquote>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</blockquote>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</blockquote>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>bar</p>
|
||||||
|
</blockquote>
|
||||||
|
`)
|
||||||
|
/* The GOAL is to delete all the text content inside this deeply nested block quote and turn it into a default paragraph block on top level.
|
||||||
|
* We need:
|
||||||
|
* 3 Backspace to delete the word “bar”.
|
||||||
|
* 1 Backspace to remove the paragraph that contains bar and bring cursor to the end of the unordered list which is direct child of the outermost block quote.
|
||||||
|
* 3 Backspace to remove the word “foo”.
|
||||||
|
* 1 Backspace to remove the current block quote that the cursor is on, 1 Backspace to remove the list that wraps the block quote. Repeat this step for three times for a total of 6 Backspace until the cursor is on the outermost block quote.
|
||||||
|
* 1 Backspace to remove to toggle off the outermost block quote and turn it into a default paragraph.
|
||||||
|
* Total Backspaces required: 3 + 1 + 3 + ((1 + 1) * 3) + 1 = 14
|
||||||
|
*/
|
||||||
|
.backspace({ times: 14 })
|
||||||
|
});
|
||||||
|
// });
|
||||||
|
|
||||||
|
// describe('backspace inside quote', () => {
|
||||||
|
it('joins two paragraphs', () => {
|
||||||
|
cy.clickQuoteButton()
|
||||||
|
.type('foo')
|
||||||
|
.enter()
|
||||||
|
.type('bar')
|
||||||
|
.setCursorBefore('bar')
|
||||||
|
.backspace()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<blockquote>
|
||||||
|
<p>foobar</p>
|
||||||
|
</blockquote>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
it('joins quote with previous quote', () => {
|
||||||
|
cy.clickQuoteButton()
|
||||||
|
.type('foo')
|
||||||
|
.enter({ times: 2 })
|
||||||
|
.clickQuoteButton()
|
||||||
|
.type('bar')
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<blockquote>
|
||||||
|
<p>foo</p>
|
||||||
|
</blockquote>
|
||||||
|
<blockquote>
|
||||||
|
<p>bar</p>
|
||||||
|
</blockquote>
|
||||||
|
`)
|
||||||
|
.setCursorBefore('bar')
|
||||||
|
.backspace()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<blockquote>
|
||||||
|
<p>foo</p>
|
||||||
|
<p>bar</p>
|
||||||
|
</blockquote>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
it('removes first block from quote when focused at first block at start', () => {
|
||||||
|
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||||
|
cy.clickQuoteButton()
|
||||||
|
.type('foo')
|
||||||
|
.enter()
|
||||||
|
.type('bar')
|
||||||
|
.setCursorBefore('foo')
|
||||||
|
.wait(500)
|
||||||
|
.backspace()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<p>foo</p>
|
||||||
|
<blockquote>
|
||||||
|
<p>bar</p>
|
||||||
|
</blockquote>
|
||||||
|
`)
|
||||||
|
});
|
||||||
|
// });
|
||||||
|
|
||||||
|
// describe('enter inside quote', () => {
|
||||||
|
it('creates new block inside quote', () => {
|
||||||
|
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||||
|
cy.clickQuoteButton()
|
||||||
|
.type('foo')
|
||||||
|
.enter()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<blockquote>
|
||||||
|
<p>foo</p>
|
||||||
|
<p></p>
|
||||||
|
</blockquote>
|
||||||
|
`)
|
||||||
|
.type('bar')
|
||||||
|
.setCursorAfter('ba')
|
||||||
|
.wait(500)
|
||||||
|
.enter()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<blockquote>
|
||||||
|
<p>foo</p>
|
||||||
|
<p>ba</p>
|
||||||
|
<p>r</p>
|
||||||
|
</blockquote>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
it('creates new block after quote from empty last block', () => {
|
||||||
|
cy.clickQuoteButton()
|
||||||
|
.type('foo')
|
||||||
|
.enter()
|
||||||
|
.enter()
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<blockquote>
|
||||||
|
<p>foo</p>
|
||||||
|
</blockquote>
|
||||||
|
<p></p>
|
||||||
|
`)
|
||||||
|
});
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import fixture from './common/media_library';
|
||||||
|
import { entry1 } from './common/entries';
|
||||||
|
import * as specUtils from './common/spec_utils';
|
||||||
|
|
||||||
|
const backend = 'bitbucket';
|
||||||
|
|
||||||
|
describe('BitBucket Backend Media Library - REST API', () => {
|
||||||
|
let taskResult = { data: {} };
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
specUtils.before(taskResult, {}, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
specUtils.after(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
specUtils.beforeEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
specUtils.afterEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture({ entries: [entry1], getUser: () => taskResult.data.user });
|
||||||
|
});
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import fixture from './common/media_library';
|
||||||
|
import { entry1 } from './common/entries';
|
||||||
|
import * as specUtils from './common/spec_utils';
|
||||||
|
|
||||||
|
const backend = 'bitbucket';
|
||||||
|
const lfs = true;
|
||||||
|
|
||||||
|
describe('BitBucket Backend Media Library - Large Media', () => {
|
||||||
|
let taskResult = { data: {} };
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
specUtils.before(taskResult, { lfs }, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
specUtils.after(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
specUtils.beforeEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
specUtils.afterEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture({ entries: [entry1], getUser: () => taskResult.data.user });
|
||||||
|
});
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import fixture from './common/media_library';
|
||||||
|
import { entry1 } from './common/entries';
|
||||||
|
import * as specUtils from './common/spec_utils';
|
||||||
|
|
||||||
|
const backend = 'git-gateway';
|
||||||
|
const provider = 'github';
|
||||||
|
|
||||||
|
describe('Git Gateway (GitHub) Backend Media Library - Large Media', () => {
|
||||||
|
let taskResult = { data: {} };
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
specUtils.before(taskResult, { publish_mode: 'editorial_workflow', provider }, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
specUtils.after(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
specUtils.beforeEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
specUtils.afterEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture({ entries: [entry1], getUser: () => taskResult.data.user });
|
||||||
|
});
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import fixture from './common/media_library';
|
||||||
|
import { entry1 } from './common/entries';
|
||||||
|
import * as specUtils from './common/spec_utils';
|
||||||
|
|
||||||
|
const backend = 'git-gateway';
|
||||||
|
const provider = 'gitlab';
|
||||||
|
|
||||||
|
describe('Git Gateway (GitLab) Backend Media Library - Large Media', () => {
|
||||||
|
let taskResult = { data: {} };
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
specUtils.before(taskResult, { publish_mode: 'editorial_workflow', provider }, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
specUtils.after(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
specUtils.beforeEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
specUtils.afterEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture({ entries: [entry1], getUser: () => taskResult.data.user });
|
||||||
|
});
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import fixture from './common/media_library';
|
||||||
|
import { entry1 } from './common/entries';
|
||||||
|
import * as specUtils from './common/spec_utils';
|
||||||
|
|
||||||
|
const backend = 'github';
|
||||||
|
|
||||||
|
describe('GitHub Backend Media Library - GraphQL API', () => {
|
||||||
|
const taskResult = { data: {} };
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
specUtils.before(
|
||||||
|
taskResult,
|
||||||
|
{
|
||||||
|
backend: { use_graphql: true },
|
||||||
|
publish_mode: 'editorial_workflow',
|
||||||
|
},
|
||||||
|
backend,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
specUtils.after(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
specUtils.beforeEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
specUtils.afterEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture({ entries: [entry1], getUser: () => taskResult.data.user });
|
||||||
|
});
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import fixture from './common/media_library';
|
||||||
|
import { entry1 } from './common/entries';
|
||||||
|
import * as specUtils from './common/spec_utils';
|
||||||
|
|
||||||
|
const backend = 'github';
|
||||||
|
|
||||||
|
describe('GitHub Backend Media Library - REST API', () => {
|
||||||
|
let taskResult = { data: {} };
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
specUtils.before(
|
||||||
|
taskResult,
|
||||||
|
{
|
||||||
|
backend: { use_graphql: false },
|
||||||
|
publish_mode: 'editorial_workflow',
|
||||||
|
},
|
||||||
|
backend,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
specUtils.after(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
specUtils.beforeEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
specUtils.afterEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture({ entries: [entry1], getUser: () => taskResult.data.user });
|
||||||
|
});
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import fixture from './common/media_library';
|
||||||
|
import { entry1 } from './common/entries';
|
||||||
|
import * as specUtils from './common/spec_utils';
|
||||||
|
|
||||||
|
const backend = 'gitlab';
|
||||||
|
|
||||||
|
describe('GitLab Backend Media Library - REST API', () => {
|
||||||
|
let taskResult = { data: {} };
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
specUtils.before(taskResult, { publish_mode: 'editorial_workflow' }, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
specUtils.after(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
specUtils.beforeEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
specUtils.afterEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture({ entries: [entry1], getUser: () => taskResult.data.user });
|
||||||
|
});
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import fixture from './common/media_library';
|
||||||
|
import * as specUtils from './common/spec_utils';
|
||||||
|
import { entry1 } from './common/entries';
|
||||||
|
|
||||||
|
const backend = 'proxy';
|
||||||
|
const mode = 'git';
|
||||||
|
|
||||||
|
describe(`Proxy Backend Media Library - '${mode}' mode`, () => {
|
||||||
|
let taskResult = { data: {} };
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
specUtils.before(taskResult, { publish_mode: 'editorial_workflow', mode }, backend);
|
||||||
|
Cypress.config('defaultCommandTimeout', 5 * 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
specUtils.after(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
specUtils.beforeEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
specUtils.afterEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture({ entries: [entry1], getUser: () => taskResult.data.user });
|
||||||
|
});
|
||||||
21
source/admin/cypress/e2e/media_library_spec_test_backend.js
Normal file
21
source/admin/cypress/e2e/media_library_spec_test_backend.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import fixture from './common/media_library';
|
||||||
|
|
||||||
|
const entries = [
|
||||||
|
{
|
||||||
|
title: 'first title',
|
||||||
|
body: 'first body',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('Test Backend Media Library', () => {
|
||||||
|
after(() => {
|
||||||
|
cy.task('teardownBackend', { backend: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
Cypress.config('defaultCommandTimeout', 4000);
|
||||||
|
cy.task('setupBackend', { backend: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture({ entries });
|
||||||
|
});
|
||||||
71
source/admin/cypress/e2e/search_suggestion_spec.js
Normal file
71
source/admin/cypress/e2e/search_suggestion_spec.js
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import { login } from '../utils/steps';
|
||||||
|
|
||||||
|
const search = (term, collection) => {
|
||||||
|
cy.get('[class*=SearchInput]').clear({ force: true });
|
||||||
|
cy.get('[class*=SearchInput]').type(term, { force: true });
|
||||||
|
cy.get('[class*=SuggestionsContainer]').within(() => {
|
||||||
|
cy.contains(collection).click();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const assertSearchHeading = title => {
|
||||||
|
cy.get('[class*=SearchResultHeading]').should('have.text', title);
|
||||||
|
};
|
||||||
|
|
||||||
|
const assertSearchResult = (text, collection) => {
|
||||||
|
cy.get('[class*=ListCardLink] h2').contains(collection ?? text)
|
||||||
|
};
|
||||||
|
|
||||||
|
const assertNotInSearch = text => {
|
||||||
|
cy.get('[class*=ListCardLink] h2').contains(text).should('not.exist');
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Search Suggestion', () => {
|
||||||
|
before(() => {
|
||||||
|
Cypress.config('defaultCommandTimeout', 4000);
|
||||||
|
cy.task('setupBackend', { backend: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
cy.task('teardownBackend', { backend: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
login();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can search in all collections', () => {
|
||||||
|
search('this', 'All Collections');
|
||||||
|
|
||||||
|
assertSearchHeading('Search Results for "this"');
|
||||||
|
|
||||||
|
assertSearchResult('This is post # 20', 'Posts');
|
||||||
|
assertSearchResult('This is a TOML front matter post', 'Posts');
|
||||||
|
assertSearchResult('This is a JSON front matter post', 'Posts');
|
||||||
|
assertSearchResult('This is a YAML front matter post', 'Posts');
|
||||||
|
assertSearchResult('This FAQ item # 5', 'FAQ');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can search in posts collection', () => {
|
||||||
|
search('this', 'Posts');
|
||||||
|
|
||||||
|
assertSearchHeading('Search Results for "this" in Posts');
|
||||||
|
|
||||||
|
assertSearchResult('This is post # 20');
|
||||||
|
assertSearchResult('This is a TOML front matter post');
|
||||||
|
assertSearchResult('This is a JSON front matter post');
|
||||||
|
assertSearchResult('This is a YAML front matter post');
|
||||||
|
|
||||||
|
assertNotInSearch('This FAQ item # 5');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can search in faq collection', () => {
|
||||||
|
search('this', 'FAQ');
|
||||||
|
|
||||||
|
assertSearchHeading('Search Results for "this" in FAQ');
|
||||||
|
|
||||||
|
assertSearchResult('This FAQ item # 5');
|
||||||
|
|
||||||
|
assertNotInSearch('This is post # 20');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import fixture from './common/simple_workflow';
|
||||||
|
import * as specUtils from './common/spec_utils';
|
||||||
|
import { entry1, entry2, entry3 } from './common/entries';
|
||||||
|
|
||||||
|
const backend = 'bitbucket';
|
||||||
|
|
||||||
|
describe('BitBucket Backend Simple Workflow', () => {
|
||||||
|
let taskResult = { data: {} };
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
specUtils.before(taskResult, { publish_mode: 'simple' }, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
specUtils.after(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
specUtils.beforeEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
specUtils.afterEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture({
|
||||||
|
entries: [entry1, entry2, entry3],
|
||||||
|
getUser: () => taskResult.data.user,
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import fixture from './common/simple_workflow';
|
||||||
|
import * as specUtils from './common/spec_utils';
|
||||||
|
import { entry1, entry2, entry3 } from './common/entries';
|
||||||
|
|
||||||
|
const backend = 'git-gateway';
|
||||||
|
const provider = 'github';
|
||||||
|
|
||||||
|
describe('Git Gateway (GitHub) Backend Simple Workflow', () => {
|
||||||
|
let taskResult = { data: {} };
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
specUtils.before(taskResult, { publish_mode: 'simple', provider }, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
specUtils.after(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
specUtils.beforeEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
specUtils.afterEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture({
|
||||||
|
entries: [entry1, entry2, entry3],
|
||||||
|
getUser: () => taskResult.data.user,
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import fixture from './common/simple_workflow';
|
||||||
|
import * as specUtils from './common/spec_utils';
|
||||||
|
import { entry1, entry2, entry3 } from './common/entries';
|
||||||
|
|
||||||
|
const backend = 'git-gateway';
|
||||||
|
const provider = 'gitlab';
|
||||||
|
|
||||||
|
describe('Git Gateway (GitLab) Backend Simple Workflow', () => {
|
||||||
|
let taskResult = { data: {} };
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
specUtils.before(taskResult, { publish_mode: 'simple', provider }, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
specUtils.after(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
specUtils.beforeEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
specUtils.afterEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture({
|
||||||
|
entries: [entry1, entry2, entry3],
|
||||||
|
getUser: () => taskResult.data.user,
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import fixture from './common/simple_workflow';
|
||||||
|
import * as specUtils from './common/spec_utils';
|
||||||
|
import { entry1, entry2, entry3 } from './common/entries';
|
||||||
|
|
||||||
|
const backend = 'github';
|
||||||
|
|
||||||
|
describe('GitHub Backend Simple Workflow - GraphQL API', () => {
|
||||||
|
const taskResult = { data: {} };
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
specUtils.before(
|
||||||
|
taskResult,
|
||||||
|
{
|
||||||
|
backend: { use_graphql: true },
|
||||||
|
publish_mode: 'simple',
|
||||||
|
},
|
||||||
|
backend,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
specUtils.after(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
specUtils.beforeEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
specUtils.afterEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture({
|
||||||
|
entries: [entry1, entry2, entry3],
|
||||||
|
getUser: () => taskResult.data.user,
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import fixture from './common/simple_workflow';
|
||||||
|
import * as specUtils from './common/spec_utils';
|
||||||
|
import { entry1, entry2, entry3 } from './common/entries';
|
||||||
|
|
||||||
|
const backend = 'github';
|
||||||
|
|
||||||
|
describe('GitHub Backend Simple Workflow - REST API', () => {
|
||||||
|
let taskResult = { data: {} };
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
specUtils.before(
|
||||||
|
taskResult,
|
||||||
|
{
|
||||||
|
backend: { use_graphql: false },
|
||||||
|
publish_mode: 'simple',
|
||||||
|
},
|
||||||
|
backend,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
specUtils.after(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
specUtils.beforeEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
specUtils.afterEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture({
|
||||||
|
entries: [entry1, entry2, entry3],
|
||||||
|
getUser: () => taskResult.data.user,
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import fixture from './common/simple_workflow';
|
||||||
|
import * as specUtils from './common/spec_utils';
|
||||||
|
import { entry1, entry2, entry3 } from './common/entries';
|
||||||
|
|
||||||
|
const backend = 'gitlab';
|
||||||
|
|
||||||
|
describe('GitLab Backend Simple Workflow', () => {
|
||||||
|
let taskResult = { data: {} };
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
specUtils.before(taskResult, { publish_mode: 'simple' }, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
specUtils.after(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
specUtils.beforeEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
specUtils.afterEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture({
|
||||||
|
entries: [entry1, entry2, entry3],
|
||||||
|
getUser: () => taskResult.data.user,
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import fixture from './common/simple_workflow';
|
||||||
|
import * as specUtils from './common/spec_utils';
|
||||||
|
import { entry1, entry2, entry3 } from './common/entries';
|
||||||
|
|
||||||
|
const backend = 'proxy';
|
||||||
|
const mode = 'fs';
|
||||||
|
|
||||||
|
describe(`Proxy Backend Simple Workflow - '${mode}' mode`, () => {
|
||||||
|
const taskResult = { data: {} };
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
specUtils.before(taskResult, { publish_mode: 'simple', mode }, backend);
|
||||||
|
Cypress.config('defaultCommandTimeout', 5 * 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
specUtils.after(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
specUtils.beforeEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
specUtils.afterEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture({
|
||||||
|
entries: [entry1, entry2, entry3],
|
||||||
|
getUser: () => taskResult.data.user,
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import fixture from './common/simple_workflow';
|
||||||
|
import * as specUtils from './common/spec_utils';
|
||||||
|
import { entry1, entry2, entry3 } from './common/entries';
|
||||||
|
|
||||||
|
const backend = 'proxy';
|
||||||
|
const mode = 'git';
|
||||||
|
|
||||||
|
describe(`Proxy Backend Simple Workflow - '${mode}' mode`, () => {
|
||||||
|
let taskResult = { data: {} };
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
specUtils.before(taskResult, { publish_mode: 'simple', mode }, backend);
|
||||||
|
Cypress.config('defaultCommandTimeout', 5 * 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
specUtils.after(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
if (Cypress.mocha.getRunner().suite.ctx.currentTest.title === 'can create an entry') {
|
||||||
|
Cypress.mocha.getRunner().suite.ctx.currentTest.skip();
|
||||||
|
}
|
||||||
|
specUtils.beforeEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
specUtils.afterEach(taskResult, backend);
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture({
|
||||||
|
entries: [entry1, entry2, entry3],
|
||||||
|
getUser: () => taskResult.data.user,
|
||||||
|
});
|
||||||
|
});
|
||||||
102
source/admin/cypress/e2e/simple_workflow_spec_test_backend.js
Normal file
102
source/admin/cypress/e2e/simple_workflow_spec_test_backend.js
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import '../utils/dismiss-local-backup';
|
||||||
|
import {
|
||||||
|
login,
|
||||||
|
newPost,
|
||||||
|
populateEntry,
|
||||||
|
exitEditor,
|
||||||
|
createPostAndPublish,
|
||||||
|
assertPublishedEntry,
|
||||||
|
editPostAndPublish,
|
||||||
|
createPostPublishAndCreateNew,
|
||||||
|
createPostPublishAndDuplicate,
|
||||||
|
editPostPublishAndCreateNew,
|
||||||
|
editPostPublishAndDuplicate,
|
||||||
|
duplicatePostAndPublish,
|
||||||
|
} from '../utils/steps';
|
||||||
|
|
||||||
|
const entry1 = {
|
||||||
|
title: 'first title',
|
||||||
|
body: 'first body',
|
||||||
|
};
|
||||||
|
const entry2 = {
|
||||||
|
title: 'second title',
|
||||||
|
body: 'second body',
|
||||||
|
};
|
||||||
|
|
||||||
|
const backend = 'test';
|
||||||
|
|
||||||
|
describe('Test Backend Simple Workflow', () => {
|
||||||
|
before(() => {
|
||||||
|
Cypress.config('defaultCommandTimeout', 4000);
|
||||||
|
cy.task('setupBackend', { backend, options: { publish_mode: 'simple' } });
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
cy.task('teardownBackend', { backend });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('successfully loads', () => {
|
||||||
|
login();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can create a new entry', () => {
|
||||||
|
login();
|
||||||
|
newPost();
|
||||||
|
populateEntry(entry1, () => {});
|
||||||
|
|
||||||
|
// new entry should show 'Unsaved changes'
|
||||||
|
cy.contains('div', 'Unsaved Changes');
|
||||||
|
cy.url().should('eq', `http://localhost:8080/#/collections/posts/new`);
|
||||||
|
exitEditor();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can publish a new entry', () => {
|
||||||
|
login();
|
||||||
|
createPostAndPublish(entry1);
|
||||||
|
assertPublishedEntry(entry1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can publish a new entry and create new', () => {
|
||||||
|
login();
|
||||||
|
createPostPublishAndCreateNew(entry1);
|
||||||
|
assertPublishedEntry(entry1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can publish a new entry and duplicate', () => {
|
||||||
|
login();
|
||||||
|
createPostPublishAndDuplicate(entry1);
|
||||||
|
assertPublishedEntry(entry1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can edit an existing entry and publish', () => {
|
||||||
|
login();
|
||||||
|
createPostAndPublish(entry1);
|
||||||
|
assertPublishedEntry(entry1);
|
||||||
|
|
||||||
|
editPostAndPublish(entry1, entry2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can edit an existing entry, publish and create new', () => {
|
||||||
|
login();
|
||||||
|
createPostAndPublish(entry1);
|
||||||
|
assertPublishedEntry(entry1);
|
||||||
|
|
||||||
|
editPostPublishAndCreateNew(entry1, entry2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can edit an existing entry, publish and duplicate', () => {
|
||||||
|
login();
|
||||||
|
createPostAndPublish(entry1);
|
||||||
|
assertPublishedEntry(entry1);
|
||||||
|
|
||||||
|
editPostPublishAndDuplicate(entry1, entry2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can duplicate an existing entry', () => {
|
||||||
|
login();
|
||||||
|
createPostAndPublish(entry1);
|
||||||
|
assertPublishedEntry(entry1);
|
||||||
|
|
||||||
|
duplicatePostAndPublish(entry1);
|
||||||
|
});
|
||||||
|
});
|
||||||
102
source/admin/cypress/e2e/view_filters_spec.js
Normal file
102
source/admin/cypress/e2e/view_filters_spec.js
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import { login } from '../utils/steps';
|
||||||
|
|
||||||
|
const filter = term => {
|
||||||
|
cy.contains('span', 'Filter by').click();
|
||||||
|
cy.contains(term).click();
|
||||||
|
cy.contains('Contents').click();
|
||||||
|
};
|
||||||
|
|
||||||
|
const assertEntriesCount = count => {
|
||||||
|
cy.get('[class*=ListCardLink]').should('have.length', count);
|
||||||
|
};
|
||||||
|
|
||||||
|
const assertInEntries = text => {
|
||||||
|
cy.get('[class*=ListCardLink] h2').contains(text);
|
||||||
|
};
|
||||||
|
|
||||||
|
const assertNotInEntries = text => {
|
||||||
|
cy.get('[class*=ListCardLink] h2').contains(text).should('not.exist');
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('View Filter', () => {
|
||||||
|
before(() => {
|
||||||
|
Cypress.config('defaultCommandTimeout', 4000);
|
||||||
|
cy.task('setupBackend', { backend: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
cy.task('teardownBackend', { backend: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
login();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can apply string filter', () => {
|
||||||
|
// enable filter
|
||||||
|
filter('Posts With Index');
|
||||||
|
|
||||||
|
assertEntriesCount(20);
|
||||||
|
for (let i = 1; i <= 20; i++) {
|
||||||
|
assertInEntries(`This is post # ${i} --`);
|
||||||
|
}
|
||||||
|
assertNotInEntries('This is a YAML front matter post');
|
||||||
|
assertNotInEntries('This is a JSON front matter post');
|
||||||
|
assertNotInEntries('This is a TOML front matter post');
|
||||||
|
|
||||||
|
// disable filter
|
||||||
|
filter('Posts With Index');
|
||||||
|
assertEntriesCount(23);
|
||||||
|
for (let i = 1; i <= 20; i++) {
|
||||||
|
assertInEntries(`This is post # ${i} --`);
|
||||||
|
}
|
||||||
|
assertInEntries('This is a YAML front matter post');
|
||||||
|
assertInEntries('This is a JSON front matter post');
|
||||||
|
assertInEntries('This is a TOML front matter post');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can apply boolean filter', () => {
|
||||||
|
// enable filter
|
||||||
|
filter('Drafts');
|
||||||
|
|
||||||
|
assertEntriesCount(10);
|
||||||
|
for (let i = 1; i <= 20; i++) {
|
||||||
|
const draft = i % 2 === 0;
|
||||||
|
if (draft) {
|
||||||
|
assertInEntries(`This is post # ${i} --`);
|
||||||
|
} else {
|
||||||
|
assertNotInEntries(`This is post # ${i} --`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertNotInEntries('This is a YAML front matter post');
|
||||||
|
assertNotInEntries('This is a JSON front matter post');
|
||||||
|
assertNotInEntries('This is a TOML front matter post');
|
||||||
|
|
||||||
|
// disable filter
|
||||||
|
filter('Drafts');
|
||||||
|
assertEntriesCount(23);
|
||||||
|
for (let i = 1; i <= 20; i++) {
|
||||||
|
assertInEntries(`This is post # ${i} --`);
|
||||||
|
}
|
||||||
|
assertInEntries('This is a YAML front matter post');
|
||||||
|
assertInEntries('This is a JSON front matter post');
|
||||||
|
assertInEntries('This is a TOML front matter post');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can apply multiple filters', () => {
|
||||||
|
// enable filter
|
||||||
|
filter('Posts Without Index');
|
||||||
|
|
||||||
|
assertEntriesCount(3);
|
||||||
|
|
||||||
|
assertInEntries('This is a YAML front matter post');
|
||||||
|
assertInEntries('This is a JSON front matter post');
|
||||||
|
assertInEntries('This is a TOML front matter post');
|
||||||
|
|
||||||
|
filter('Drafts');
|
||||||
|
|
||||||
|
assertEntriesCount(0);
|
||||||
|
|
||||||
|
cy.contains('div', 'No Entries');
|
||||||
|
});
|
||||||
|
});
|
||||||
70
source/admin/cypress/e2e/view_groups_spec.js
Normal file
70
source/admin/cypress/e2e/view_groups_spec.js
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import { login } from '../utils/steps';
|
||||||
|
|
||||||
|
const group = term => {
|
||||||
|
cy.contains('span', 'Group by').click();
|
||||||
|
cy.contains(term).click();
|
||||||
|
cy.contains('Contents').click();
|
||||||
|
};
|
||||||
|
|
||||||
|
const assertGroupsCount = count => {
|
||||||
|
cy.get('[class*=GroupContainer]').should('have.length', count);
|
||||||
|
};
|
||||||
|
|
||||||
|
const assertEachGroupCount = (id, count) => {
|
||||||
|
cy.get(`[id='${id}']`).within(() => {
|
||||||
|
assertEntriesCount(count);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const assertEntriesCount = count => {
|
||||||
|
cy.get('[class*=ListCardLink]').should('have.length', count);
|
||||||
|
};
|
||||||
|
|
||||||
|
const assertInEntries = text => {
|
||||||
|
cy.get('[class*=ListCardLink] h2').contains('h2', text);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('View Group', () => {
|
||||||
|
before(() => {
|
||||||
|
Cypress.config('defaultCommandTimeout', 4000);
|
||||||
|
cy.task('setupBackend', { backend: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
cy.task('teardownBackend', { backend: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
login();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can apply string group', () => {
|
||||||
|
// enable group
|
||||||
|
group('Year');
|
||||||
|
|
||||||
|
assertGroupsCount(2);
|
||||||
|
const year = new Date().getFullYear();
|
||||||
|
assertEachGroupCount(`Year${year}`, 20);
|
||||||
|
assertEachGroupCount('Year2015', 3);
|
||||||
|
|
||||||
|
//disable group
|
||||||
|
group('Year');
|
||||||
|
|
||||||
|
assertEntriesCount(23);
|
||||||
|
for (let i = 1; i <= 20; i++) {
|
||||||
|
assertInEntries(`This is post # ${i} --`);
|
||||||
|
}
|
||||||
|
assertInEntries('This is a YAML front matter post');
|
||||||
|
assertInEntries('This is a JSON front matter post');
|
||||||
|
assertInEntries('This is a TOML front matter post');
|
||||||
|
|
||||||
|
//enable group
|
||||||
|
group('Drafts');
|
||||||
|
|
||||||
|
assertEntriesCount(23);
|
||||||
|
assertGroupsCount(3);
|
||||||
|
assertEachGroupCount('Draftstrue', 10);
|
||||||
|
assertEachGroupCount('Draftsfalse', 10);
|
||||||
|
assertEachGroupCount('missing_value', 3);
|
||||||
|
});
|
||||||
|
});
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user