Taichi Masuyama 4 лет назад
Родитель
Сommit
49280129c2
23 измененных файлов с 381 добавлено и 437 удалено
  1. 2 2
      .github/workflows/reusable-app-prod.yml
  2. 7 1
      packages/app/resource/locales/en_US/translation.json
  3. 7 1
      packages/app/resource/locales/ja_JP/translation.json
  4. 7 1
      packages/app/resource/locales/zh_CN/translation.json
  5. 23 4
      packages/app/src/client/services/AdminGeneralSecurityContainer.js
  6. 95 30
      packages/app/src/components/Admin/Security/SecuritySetting.jsx
  7. 6 4
      packages/app/src/components/SavePageControls/GrantSelector.jsx
  8. 11 0
      packages/app/src/migrations/20220311011114-convert-page-delete-config.js
  9. 0 34
      packages/app/test/cypress/integration/2-basic-features/access-to-me-page.spec.ts
  10. 61 12
      packages/app/test/cypress/integration/2-basic-features/access-to-page.spec.ts
  11. 0 44
      packages/app/test/cypress/integration/2-basic-features/access-to-special-page.spec.ts
  12. 0 37
      packages/app/test/cypress/integration/2-basic-features/open-page-create-modal.spec.ts
  13. 0 36
      packages/app/test/cypress/integration/2-basic-features/open-page-delete-modal.spec.ts
  14. 0 34
      packages/app/test/cypress/integration/2-basic-features/open-page-duplicate-modal.spec.ts
  15. 0 35
      packages/app/test/cypress/integration/2-basic-features/open-page-move-rename-modal.spec.ts
  16. 0 61
      packages/app/test/cypress/integration/2-basic-features/open-presentation-modal.spec.ts
  17. 0 31
      packages/app/test/cypress/integration/2-basic-features/switch-sidebar-contents.spec.ts
  18. 121 0
      packages/app/test/cypress/integration/2-basic-features/use-tools.spec.ts
  19. 0 32
      packages/app/test/cypress/integration/3-search/access-to-private-legacy-pages-directly.spec.ts
  20. 26 13
      packages/app/test/cypress/integration/3-search/search.spec.ts
  21. 1 13
      packages/app/test/cypress/integration/4-admin/access-to-admin-page.spec.ts
  22. 11 3
      packages/app/test/cypress/support/commands.ts
  23. 3 9
      yarn.lock

+ 2 - 2
.github/workflows/reusable-app-prod.yml

@@ -185,7 +185,7 @@ jobs:
       fail-fast: false
       fail-fast: false
       matrix:
       matrix:
         # List string expressions that is comma separated ids of tests in "test/cypress/integration"
         # List string expressions that is comma separated ids of tests in "test/cypress/integration"
-        spec-group: ['1', '2', '3']
+        spec-group: ['1', '2', '3', '4']
 
 
     services:
     services:
       mongodb:
       mongodb:
@@ -250,7 +250,7 @@ jobs:
         cat config/ci/.env.local.for-auto-install >> .env.production.local
         cat config/ci/.env.local.for-auto-install >> .env.production.local
 
 
     - name: Cypress Run
     - name: Cypress Run
-      uses: cypress-io/github-action@v2
+      uses: cypress-io/github-action@v3
       with:
       with:
         working-directory: ./packages/app
         working-directory: ./packages/app
         install: false
         install: false

+ 7 - 1
packages/app/resource/locales/en_US/translation.json

@@ -686,7 +686,8 @@
     "max_age": "Max age (msec)",
     "max_age": "Max age (msec)",
     "max_age_desc": "Specifies the number (in milliseconds) to expire users session.<br>Default: 2592000000 (30days)",
     "max_age_desc": "Specifies the number (in milliseconds) to expire users session.<br>Default: 2592000000 (30days)",
     "max_age_caution": "Restarting the server is required after you modify this value.",
     "max_age_caution": "Restarting the server is required after you modify this value.",
-    "page_delete_rights_caution": "The \"operation including the descendants\" setting is forced to be stronger than the \"operation for only the selected page\" setting.",
+    "forced_update_desc": "Settings have been forcibly changed. Previous setting: ",
+    "page_delete_rights_caution": "The \"Delete / Delete All\" permission (including descendant pages) is forced to be stronger than the \"Delete / Completely Delete\" permission. <br> <br> Anyone > Admin and autor > Admin only",
     "Authentication mechanism settings": "Authentication Mechanism Settings",
     "Authentication mechanism settings": "Authentication Mechanism Settings",
     "setup_is_not_yet_complete": "Setup is not yet complete",
     "setup_is_not_yet_complete": "Setup is not yet complete",
     "alert_siteUrl_is_not_set": "'Site URL' is NOT set. Set it from the {{link}}",
     "alert_siteUrl_is_not_set": "'Site URL' is NOT set. Set it from the {{link}}",
@@ -1018,5 +1019,10 @@
     "same_page_name_exists": "Same page name exits as「{{pageName}}」",
     "same_page_name_exists": "Same page name exits as「{{pageName}}」",
     "same_page_name_exists_at_path" : "Same page name as {{pageName}} exists at {{path}} ",
     "same_page_name_exists_at_path" : "Same page name as {{pageName}} exists at {{path}} ",
     "select_page_to_see" : "Select a page to see"
     "select_page_to_see" : "Select a page to see"
+  },
+  "user_group": {
+    "select_group": "Select group",
+    "belonging_to_no_group": "Could not find the groups you belong to.",
+    "manage_user_groups": "Manage user groups"
   }
   }
 }
 }

+ 7 - 1
packages/app/resource/locales/ja_JP/translation.json

@@ -685,7 +685,8 @@
     "max_age": "有効期間 (ミリ秒)",
     "max_age": "有効期間 (ミリ秒)",
     "max_age_desc": "ユーザーのセッション情報の有効期間をミリ秒で指定できます。<br>デフォルト値: 2592000000 (30日間)",
     "max_age_desc": "ユーザーのセッション情報の有効期間をミリ秒で指定できます。<br>デフォルト値: 2592000000 (30日間)",
     "max_age_caution": "この値を変更した後は、サーバーを再起動する必要があります。",
     "max_age_caution": "この値を変更した後は、サーバーを再起動する必要があります。",
-    "page_delete_rights_caution": "「子孫を含む操作」の設定値は、「単体のみの操作」の設定値よりも強いものに強制されます。",
+    "forced_update_desc": "設定が強制変更されました。前回の設定: ",
+    "page_delete_rights_caution": "「(子孫ページを含む)ゴミ箱に入れる操作 / 完全に削除する」の権限は、「ゴミ箱に入れる操作 / 完全に削除する」よりも強い権限になるように強制されます。 <br><br> 誰でも可能 > 管理者とページ作者が可能 > 管理者のみ可能",
     "Authentication mechanism settings": "認証機構設定",
     "Authentication mechanism settings": "認証機構設定",
     "setup_is_not_yet_complete":"セットアップはまだ完了してません",
     "setup_is_not_yet_complete":"セットアップはまだ完了してません",
     "alert_siteUrl_is_not_set": "'サイトURL' が設定されていません。{{link}} から設定してください。",
     "alert_siteUrl_is_not_set": "'サイトURL' が設定されていません。{{link}} から設定してください。",
@@ -1010,5 +1011,10 @@
     "same_page_name_exists": "ページ名 「{{pageName}}」が重複しています",
     "same_page_name_exists": "ページ名 「{{pageName}}」が重複しています",
     "same_page_name_exists_at_path" : "”{{path}}” において ”{{pageName}}”というページは複数存在しています。",
     "same_page_name_exists_at_path" : "”{{path}}” において ”{{pageName}}”というページは複数存在しています。",
     "select_page_to_see" : "以下から遷移するページを選択してください。"
     "select_page_to_see" : "以下から遷移するページを選択してください。"
+  },
+  "user_group": {
+    "select_group": "グループを選ぶ",
+    "belonging_to_no_group": "所属しているグループが見つかりませんでした。",
+    "manage_user_groups": "グループ管理"
   }
   }
 }
 }

+ 7 - 1
packages/app/resource/locales/zh_CN/translation.json

@@ -644,7 +644,8 @@
     "max_age": "有效期间  (msec)",
     "max_age": "有效期间  (msec)",
     "max_age_desc": "指定使用户会话过期的数量(以毫秒为单位)。<br>默认值: 2592000000 (30天)",
     "max_age_desc": "指定使用户会话过期的数量(以毫秒为单位)。<br>默认值: 2592000000 (30天)",
     "max_age_caution": "修改该值后需要重启服务器。",
     "max_age_caution": "修改该值后需要重启服务器。",
-    "page_delete_rights_caution": "\"包括后代的操作\" 的设置被迫强于 \"只对选定的页面进行操作\" 的设置。",
+    "forced_update_desc": "设置已被强行更改。以前的设置: ",
+    "page_delete_rights_caution": "\"删除/全部删除\"权限(包括后代页面)被强制强于\"删除/完全删除\"权限。 <br> <br> 任何人 > 管理员|作者 > 仅管理员",
 		"Authentication mechanism settings": "身份验证机制设置",
 		"Authentication mechanism settings": "身份验证机制设置",
 		"setup_is_not_yet_complete": "安装尚未完成",
 		"setup_is_not_yet_complete": "安装尚未完成",
 		"alert_siteUrl_is_not_set": "主页URL未设置,通过 {{link}} 设置",
 		"alert_siteUrl_is_not_set": "主页URL未设置,通过 {{link}} 设置",
@@ -1020,5 +1021,10 @@
     "same_page_name_exists": "页面名称「{{pageName}}」是重复的",
     "same_page_name_exists": "页面名称「{{pageName}}」是重复的",
     "same_page_name_exists_at_path" : "在”{{path}}” 中,有不止一个名为”{{pageName}}”的页面",
     "same_page_name_exists_at_path" : "在”{{path}}” 中,有不止一个名为”{{pageName}}”的页面",
     "select_page_to_see" : "请在下面选择你想去的页面。"
     "select_page_to_see" : "请在下面选择你想去的页面。"
+  },
+  "user_group": {
+    "select_group": "选择组别",
+    "belonging_to_no_group": "无法找到你所属的团体。",
+    "manage_user_groups": "管理用户组"
   }
   }
 }
 }

+ 23 - 4
packages/app/src/client/services/AdminGeneralSecurityContainer.js

@@ -30,6 +30,8 @@ export default class AdminGeneralSecurityContainer extends Container {
       currentPageRecursiveDeletionAuthority: PageRecursiveDeleteConfigValue.Inherit,
       currentPageRecursiveDeletionAuthority: PageRecursiveDeleteConfigValue.Inherit,
       currentPageCompleteDeletionAuthority: PageSingleDeleteCompConfigValue.AdminOnly,
       currentPageCompleteDeletionAuthority: PageSingleDeleteCompConfigValue.AdminOnly,
       currentPageRecursiveCompleteDeletionAuthority: PageRecursiveDeleteCompConfigValue.Inherit,
       currentPageRecursiveCompleteDeletionAuthority: PageRecursiveDeleteCompConfigValue.Inherit,
+      previousPageRecursiveDeletionAuthority: null,
+      previousPageRecursiveCompleteDeletionAuthority: null,
       expandOtherOptionsForDeletion: false,
       expandOtherOptionsForDeletion: false,
       expandOtherOptionsForCompleteDeletion: false,
       expandOtherOptionsForCompleteDeletion: false,
       isShowRestrictedByOwner: false,
       isShowRestrictedByOwner: false,
@@ -55,6 +57,8 @@ export default class AdminGeneralSecurityContainer extends Container {
     this.changePageCompleteDeletionAuthority = this.changePageCompleteDeletionAuthority.bind(this);
     this.changePageCompleteDeletionAuthority = this.changePageCompleteDeletionAuthority.bind(this);
     this.changePageRecursiveDeletionAuthority = this.changePageRecursiveDeletionAuthority.bind(this);
     this.changePageRecursiveDeletionAuthority = this.changePageRecursiveDeletionAuthority.bind(this);
     this.changePageRecursiveCompleteDeletionAuthority = this.changePageRecursiveCompleteDeletionAuthority.bind(this);
     this.changePageRecursiveCompleteDeletionAuthority = this.changePageRecursiveCompleteDeletionAuthority.bind(this);
+    this.changePreviousPageRecursiveDeletionAuthority = this.changePreviousPageRecursiveDeletionAuthority.bind(this);
+    this.changePreviousPageRecursiveCompleteDeletionAuthority = this.changePreviousPageRecursiveCompleteDeletionAuthority.bind(this);
 
 
   }
   }
 
 
@@ -149,18 +153,33 @@ export default class AdminGeneralSecurityContainer extends Container {
     this.setState({ currentPageRecursiveCompleteDeletionAuthority: val });
     this.setState({ currentPageRecursiveCompleteDeletionAuthority: val });
   }
   }
 
 
+  /**
+   * Change previousPageRecursiveDeletionAuthority
+   */
+  changePreviousPageRecursiveDeletionAuthority(val) {
+    this.setState({ previousPageRecursiveDeletionAuthority: val });
+  }
+
+
+  /**
+   * Change previousPageRecursiveCompleteDeletionAuthority
+   */
+  changePreviousPageRecursiveCompleteDeletionAuthority(val) {
+    this.setState({ previousPageRecursiveCompleteDeletionAuthority: val });
+  }
+
   /**
   /**
    * Switch ExpandOtherOptionsForDeletion
    * Switch ExpandOtherOptionsForDeletion
    */
    */
-  switchExpandOtherOptionsForDeletion() {
-    this.setState({ expandOtherOptionsForDeletion:  !this.state.expandOtherOptionsForDeletion });
+  switchExpandOtherOptionsForDeletion(bool) {
+    this.setState({ expandOtherOptionsForDeletion: bool });
   }
   }
 
 
   /**
   /**
    * Switch ExpandOtherOptionsForDeletion
    * Switch ExpandOtherOptionsForDeletion
    */
    */
-  switchExpandOtherOptionsForCompleteDeletion() {
-    this.setState({ expandOtherOptionsForCompleteDeletion:  !this.state.expandOtherOptionsForCompleteDeletion });
+  switchExpandOtherOptionsForCompleteDeletion(bool) {
+    this.setState({ expandOtherOptionsForCompleteDeletion: bool });
   }
   }
 
 
   /**
   /**

+ 95 - 30
packages/app/src/components/Admin/Security/SecuritySetting.jsx

@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
 import { Collapse } from 'reactstrap';
 import { Collapse } from 'reactstrap';
 import { withTranslation } from 'react-i18next';
 import { withTranslation } from 'react-i18next';
 
 
-import { validateDeleteConfigs } from '~/utils/page-delete-config';
+import { validateDeleteConfigs, prepareDeleteConfigValuesForCalc } from '~/utils/page-delete-config';
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { PageDeleteConfigValue } from '~/interfaces/page-delete-config';
 import { PageDeleteConfigValue } from '~/interfaces/page-delete-config';
@@ -39,6 +39,20 @@ const getDeletionTypeForT = (deletionType) => {
   }
   }
 };
 };
 
 
+const getDeleteConfigValueForT = (DeleteConfigValue) => {
+  switch (DeleteConfigValue) {
+    case PageDeleteConfigValue.Anyone:
+    case null:
+      return 'security_setting.anyone';
+    case PageDeleteConfigValue.Inherit:
+      return 'security_setting.inherit';
+    case PageDeleteConfigValue.AdminOnly:
+      return 'security_setting.admin_only';
+    case PageDeleteConfigValue.AdminAndAuthor:
+      return 'security_setting.admin_and_author';
+  }
+};
+
 /**
 /**
  * Return true if "deletionType" is DeletionType.RecursiveDeletion or DeletionType.RecursiveCompleteDeletion.
  * Return true if "deletionType" is DeletionType.RecursiveDeletion or DeletionType.RecursiveCompleteDeletion.
  * @param deletionType Deletion type
  * @param deletionType Deletion type
@@ -62,9 +76,16 @@ class SecuritySetting extends React.Component {
   constructor(props) {
   constructor(props) {
     super(props);
     super(props);
 
 
+    // functions
     this.putSecuritySetting = this.putSecuritySetting.bind(this);
     this.putSecuritySetting = this.putSecuritySetting.bind(this);
     this.getRecursiveDeletionConfigState = this.getRecursiveDeletionConfigState.bind(this);
     this.getRecursiveDeletionConfigState = this.getRecursiveDeletionConfigState.bind(this);
+    this.previousPageRecursiveAuthorityState = this.previousPageRecursiveAuthorityState.bind(this);
+    this.setPagePreviousRecursiveAuthorityState = this.setPagePreviousRecursiveAuthorityState.bind(this);
+    this.expantDeleteOptionsState = this.expantDeleteOptionsState.bind(this);
+    this.setExpantOtherDeleteOptionsState = this.setExpantOtherDeleteOptionsState.bind(this);
     this.setDeletionConfigState = this.setDeletionConfigState.bind(this);
     this.setDeletionConfigState = this.setDeletionConfigState.bind(this);
+
+    // render
     this.renderPageDeletePermission = this.renderPageDeletePermission.bind(this);
     this.renderPageDeletePermission = this.renderPageDeletePermission.bind(this);
     this.renderPageDeletePermissionDropdown = this.renderPageDeletePermissionDropdown.bind(this);
     this.renderPageDeletePermissionDropdown = this.renderPageDeletePermissionDropdown.bind(this);
   }
   }
@@ -96,25 +117,67 @@ class SecuritySetting extends React.Component {
     ];
     ];
   }
   }
 
 
+  previousPageRecursiveAuthorityState(deletionType) {
+    const { adminGeneralSecurityContainer } = this.props;
+
+    return isTypeDeletion(deletionType)
+      ? adminGeneralSecurityContainer.state.previousPageRecursiveDeletionAuthority
+      : adminGeneralSecurityContainer.state.previousPageRecursiveCompleteDeletionAuthority;
+  }
+
+  setPagePreviousRecursiveAuthorityState(deletionType, previousState) {
+    const { adminGeneralSecurityContainer } = this.props;
+
+    if (isTypeDeletion(deletionType)) {
+      adminGeneralSecurityContainer.changePreviousPageRecursiveDeletionAuthority(previousState);
+      return;
+    }
+
+    adminGeneralSecurityContainer.changePreviousPageRecursiveCompleteDeletionAuthority(previousState);
+  }
+
+  expantDeleteOptionsState(deletionType) {
+    const { adminGeneralSecurityContainer } = this.props;
+
+    return isTypeDeletion(deletionType)
+      ? adminGeneralSecurityContainer.state.expandOtherOptionsForDeletion
+      : adminGeneralSecurityContainer.state.expandOtherOptionsForCompleteDeletion;
+  }
+
+  setExpantOtherDeleteOptionsState(deletionType, bool) {
+    const { adminGeneralSecurityContainer } = this.props;
+
+    if (isTypeDeletion(deletionType)) {
+      adminGeneralSecurityContainer.switchExpandOtherOptionsForDeletion(bool);
+      return;
+    }
+    adminGeneralSecurityContainer.switchExpandOtherOptionsForCompleteDeletion(bool);
+    return;
+  }
+
   /**
   /**
    * Force update deletion config for recursive operation when the deletion config for general operation is updated.
    * Force update deletion config for recursive operation when the deletion config for general operation is updated.
    * @param deletionType Deletion type
    * @param deletionType Deletion type
    */
    */
   setDeletionConfigState(newState, setState, deletionType) {
   setDeletionConfigState(newState, setState, deletionType) {
-    if (isRecursiveDeletion(deletionType)) {
-      setState(newState);
+    setState(newState);
 
 
+    if (this.previousPageRecursiveAuthorityState(deletionType) !== null) {
+      this.setPagePreviousRecursiveAuthorityState(deletionType, null);
+    }
+
+    if (isRecursiveDeletion(deletionType)) {
       return;
       return;
     }
     }
 
 
     const [recursiveState, setRecursiveState] = this.getRecursiveDeletionConfigState(deletionType);
     const [recursiveState, setRecursiveState] = this.getRecursiveDeletionConfigState(deletionType);
-    const shouldForceUpdate = !validateDeleteConfigs(newState, recursiveState);
+
+    const calculableValue = prepareDeleteConfigValuesForCalc(newState, recursiveState);
+    const shouldForceUpdate = !validateDeleteConfigs(calculableValue[0], calculableValue[1]);
     if (shouldForceUpdate) {
     if (shouldForceUpdate) {
-      setState(newState);
       setRecursiveState(newState);
       setRecursiveState(newState);
-    }
-    else {
-      setState(newState);
+      this.setPagePreviousRecursiveAuthorityState(deletionType, recursiveState);
+      this.setExpantOtherDeleteOptionsState(deletionType, true);
     }
     }
 
 
     return;
     return;
@@ -133,10 +196,7 @@ class SecuritySetting extends React.Component {
           aria-expanded="true"
           aria-expanded="true"
         >
         >
           <span className="float-left">
           <span className="float-left">
-            {currentState === PageDeleteConfigValue.Inherit && t('security_setting.inherit')}
-            {(currentState === PageDeleteConfigValue.Anyone || currentState == null) && t('security_setting.anyone')}
-            {currentState === PageDeleteConfigValue.AdminOnly && t('security_setting.admin_only')}
-            {currentState === PageDeleteConfigValue.AdminAndAuthor && t('security_setting.admin_and_author')}
+            {t(getDeleteConfigValueForT(currentState))}
           </span>
           </span>
         </button>
         </button>
         <div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
         <div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
@@ -184,20 +244,9 @@ class SecuritySetting extends React.Component {
   }
   }
 
 
   renderPageDeletePermission(currentState, setState, deletionType, isButtonDisabled) {
   renderPageDeletePermission(currentState, setState, deletionType, isButtonDisabled) {
-    const { t, adminGeneralSecurityContainer } = this.props;
-
-    const expandOtherOptions = isTypeDeletion(deletionType)
-      ? adminGeneralSecurityContainer.state.expandOtherOptionsForDeletion
-      : adminGeneralSecurityContainer.state.expandOtherOptionsForCompleteDeletion;
+    const { t } = this.props;
 
 
-    const setExpantOtherOptions = () => {
-      if (isTypeDeletion(deletionType)) {
-        adminGeneralSecurityContainer.switchExpandOtherOptionsForDeletion();
-        return;
-      }
-      adminGeneralSecurityContainer.switchExpandOtherOptionsForCompleteDeletion();
-      return;
-    };
+    const expantDeleteOptionsState = this.expantDeleteOptionsState(deletionType);
 
 
     return (
     return (
       <div key={`page-delete-permission-dropdown-${deletionType}`} className="row">
       <div key={`page-delete-permission-dropdown-${deletionType}`} className="row">
@@ -223,13 +272,30 @@ class SecuritySetting extends React.Component {
                     type="button"
                     type="button"
                     className="btn btn-link p-0 mb-4"
                     className="btn btn-link p-0 mb-4"
                     aria-expanded="false"
                     aria-expanded="false"
-                    onClick={() => setExpantOtherOptions()}
+                    onClick={() => this.setExpantOtherDeleteOptionsState(deletionType, !expantDeleteOptionsState)}
                   >
                   >
-                    <i className={`fa fa-fw fa-arrow-right ${expandOtherOptions ? 'fa-rotate-90' : ''}`}></i>
+                    <i className={`fa fa-fw fa-arrow-right ${expantDeleteOptionsState ? 'fa-rotate-90' : ''}`}></i>
                     { t('security_setting.other_options') }
                     { t('security_setting.other_options') }
                   </button>
                   </button>
-                  <Collapse isOpen={expandOtherOptions}>
+                  <Collapse isOpen={expantDeleteOptionsState}>
                     <div className="pb-4">
                     <div className="pb-4">
+                      <p className="card well">
+                        <span className="text-warning">
+                          <i className="icon-info"></i>
+                          {/* eslint-disable-next-line react/no-danger */}
+                          <span dangerouslySetInnerHTML={{ __html: t('security_setting.page_delete_rights_caution') }} />
+                        </span>
+                      </p>
+                      { this.previousPageRecursiveAuthorityState(deletionType) !== null && (
+                        <div className="mb-3">
+                          <strong>
+                            {t('security_setting.forced_update_desc')}
+                          </strong>
+                          <code>
+                            {t(getDeleteConfigValueForT(this.previousPageRecursiveAuthorityState(deletionType)))}
+                          </code>
+                        </div>
+                      )}
                       {this.renderPageDeletePermissionDropdown(currentState, setState, deletionType, isButtonDisabled)}
                       {this.renderPageDeletePermissionDropdown(currentState, setState, deletionType, isButtonDisabled)}
                     </div>
                     </div>
                   </Collapse>
                   </Collapse>
@@ -368,8 +434,7 @@ class SecuritySetting extends React.Component {
         </div>
         </div>
 
 
         <h4>{t('security_setting.page_delete_rights')}</h4>
         <h4>{t('security_setting.page_delete_rights')}</h4>
-        <div className="row mb-4"></div>
-        {/* Render PageDeletePermissionDropdown */}
+        {/* Render PageDeletePermission */}
         {
         {
           [
           [
             [currentPageDeletionAuthority, adminGeneralSecurityContainer.changePageDeletionAuthority, DeletionType.Deletion, false],
             [currentPageDeletionAuthority, adminGeneralSecurityContainer.changePageDeletionAuthority, DeletionType.Deletion, false],

+ 6 - 4
packages/app/src/components/SavePageControls/GrantSelector.jsx

@@ -174,12 +174,14 @@ class GrantSelector extends React.Component {
    * @memberof GrantSelector
    * @memberof GrantSelector
    */
    */
   renderSelectGroupModal() {
   renderSelectGroupModal() {
+    const { t } = this.props;
+
     const generateGroupListItems = () => {
     const generateGroupListItems = () => {
       return this.state.userRelatedGroups.map((group) => {
       return this.state.userRelatedGroups.map((group) => {
         return (
         return (
           <button key={group._id} type="button" className="list-group-item list-group-item-action" onClick={() => { this.groupListItemClickHandler(group) }}>
           <button key={group._id} type="button" className="list-group-item list-group-item-action" onClick={() => { this.groupListItemClickHandler(group) }}>
             <h5>{group.name}</h5>
             <h5>{group.name}</h5>
-            <div className="small">(TBD) List group members</div>
+            {/* TODO: Replace <div className="small">(TBD) List group members</div> */}
           </button>
           </button>
         );
         );
       });
       });
@@ -188,9 +190,9 @@ class GrantSelector extends React.Component {
     const content = this.state.userRelatedGroups.length === 0
     const content = this.state.userRelatedGroups.length === 0
       ? (
       ? (
         <div>
         <div>
-          <h4>There is no group to which you belong.</h4>
+          <h4>{t('user_group.belonging_to_no_group')}</h4>
           { this.props.appContainer.isAdmin
           { this.props.appContainer.isAdmin
-            && <p><a href="/admin/user-groups"><i className="icon icon-fw icon-login"></i> Manage Groups</a></p>
+            && <p><a href="/admin/user-groups"><i className="icon icon-fw icon-login"></i>{t('user_group.manage_user_groups')}</a></p>
           }
           }
         </div>
         </div>
       )
       )
@@ -207,7 +209,7 @@ class GrantSelector extends React.Component {
         toggle={this.hideSelectGroupModal}
         toggle={this.hideSelectGroupModal}
       >
       >
         <ModalHeader tag="h4" toggle={this.hideSelectGroupModal} className="bg-purple text-light">
         <ModalHeader tag="h4" toggle={this.hideSelectGroupModal} className="bg-purple text-light">
-          Select a Group
+          {t('user_group.select_group')}
         </ModalHeader>
         </ModalHeader>
         <ModalBody>
         <ModalBody>
           {content}
           {content}

+ 11 - 0
packages/app/src/migrations/20220311011114-convert-page-delete-config.js

@@ -16,6 +16,17 @@ module.exports = {
     mongoose.connect(getMongoUri(), mongoOptions);
     mongoose.connect(getMongoUri(), mongoOptions);
     const Config = getModelSafely('Config') || ConfigModel;
     const Config = getModelSafely('Config') || ConfigModel;
 
 
+    const isNewConfigExists = await Config.count({
+      ns: 'crowi',
+      key: 'security:pageDeletionAuthority',
+    }) > 0;
+
+    if (isNewConfigExists) {
+      logger.info('This migration is skipped because new configs are existed.');
+      logger.info('Migration has successfully applied');
+      return;
+    }
+
     const oldConfig = await Config.findOne({
     const oldConfig = await Config.findOne({
       ns: 'crowi',
       ns: 'crowi',
       key: 'security:pageCompleteDeletionAuthority',
       key: 'security:pageCompleteDeletionAuthority',

+ 0 - 34
packages/app/test/cypress/integration/2-basic-features/access-to-me-page.spec.ts

@@ -1,34 +0,0 @@
-context('Access to /me page', () => {
-  const ssPrefix = 'access-to-me-page-';
-
-  let connectSid: string | undefined;
-
-  before(() => {
-    // login
-    cy.fixture("user-admin.json").then(user => {
-      cy.login(user.username, user.password);
-    });
-    cy.getCookie('connect.sid').then(cookie => {
-      connectSid = cookie?.value;
-    });
-    // collapse sidebar
-    cy.collapseSidebar(true);
-  });
-
-  beforeEach(() => {
-    if (connectSid != null) {
-      cy.setCookie('connect.sid', connectSid);
-    }
-  });
-
-  it('/me is successfully loaded', () => {
-    cy.visit('/me', {  });
-    cy.screenshot(`${ssPrefix}-me`);
-  });
-
-  it('Draft page is successfully shown', () => {
-    cy.visit('/me/drafts');
-    cy.screenshot(`${ssPrefix}-draft-page`);
-  });
-
-});

+ 61 - 12
packages/app/test/cypress/integration/2-basic-features/access-to-page.spec.ts

@@ -2,26 +2,15 @@
 context('Access to page', () => {
 context('Access to page', () => {
   const ssPrefix = 'access-to-page-';
   const ssPrefix = 'access-to-page-';
 
 
-  let connectSid: string | undefined;
-
-  before(() => {
+  beforeEach(() => {
     // login
     // login
     cy.fixture("user-admin.json").then(user => {
     cy.fixture("user-admin.json").then(user => {
       cy.login(user.username, user.password);
       cy.login(user.username, user.password);
     });
     });
-    cy.getCookie('connect.sid').then(cookie => {
-      connectSid = cookie?.value;
-    });
     // collapse sidebar
     // collapse sidebar
     cy.collapseSidebar(true);
     cy.collapseSidebar(true);
   });
   });
 
 
-  beforeEach(() => {
-    if (connectSid != null) {
-      cy.setCookie('connect.sid', connectSid);
-    }
-  });
-
   it('/Sandbox is successfully loaded', () => {
   it('/Sandbox is successfully loaded', () => {
     cy.visit('/Sandbox', {  });
     cy.visit('/Sandbox', {  });
     cy.screenshot(`${ssPrefix}-sandbox`);
     cy.screenshot(`${ssPrefix}-sandbox`);
@@ -50,3 +39,63 @@ context('Access to page', () => {
   });
   });
 
 
 });
 });
+
+
+context('Access to /me page', () => {
+  const ssPrefix = 'access-to-me-page-';
+
+  beforeEach(() => {
+    // login
+    cy.fixture("user-admin.json").then(user => {
+      cy.login(user.username, user.password);
+    });
+    // collapse sidebar
+    cy.collapseSidebar(true);
+  });
+
+  it('/me is successfully loaded', () => {
+    cy.visit('/me', {  });
+    cy.screenshot(`${ssPrefix}-me`);
+  });
+
+  it('Draft page is successfully shown', () => {
+    cy.visit('/me/drafts');
+    cy.screenshot(`${ssPrefix}-draft-page`);
+  });
+
+});
+
+
+
+context('Access to special pages', () => {
+  const ssPrefix = 'access-to-special-pages-';
+
+  beforeEach(() => {
+    // login
+    cy.fixture("user-admin.json").then(user => {
+      cy.login(user.username, user.password);
+    });
+    // collapse sidebar
+    cy.collapseSidebar(true);
+  });
+
+  it('/trash is successfully loaded', () => {
+    cy.visit('/trash', {  });
+    cy.getByTestid('trash-page-list').should('be.visible');
+    cy.screenshot(`${ssPrefix}-trash`);
+  });
+
+  it('/tags is successfully loaded', () => {
+    cy.visit('/tags');
+
+    // open sidebar
+    cy.collapseSidebar(false);
+    // select tags
+    cy.getByTestid('grw-sidebar-nav-primary-tags').click();
+    cy.getByTestid('grw-sidebar-content-tags').should('be.visible');
+
+    cy.getByTestid('tags-page').should('be.visible');
+    cy.screenshot(`${ssPrefix}-tags`);
+  });
+
+});

+ 0 - 44
packages/app/test/cypress/integration/2-basic-features/access-to-special-page.spec.ts

@@ -1,44 +0,0 @@
-
-context('Access to special pages', () => {
-  const ssPrefix = 'access-to-special-pages-';
-
-  let connectSid: string | undefined;
-
-  before(() => {
-    // login
-    cy.fixture("user-admin.json").then(user => {
-      cy.login(user.username, user.password);
-    });
-    cy.getCookie('connect.sid').then(cookie => {
-      connectSid = cookie?.value;
-    });
-    // collapse sidebar
-    cy.collapseSidebar(true);
-  });
-
-  beforeEach(() => {
-    if (connectSid != null) {
-      cy.setCookie('connect.sid', connectSid);
-    }
-  });
-
-  it('/trash is successfully loaded', () => {
-    cy.visit('/trash', {  });
-    cy.getByTestid('trash-page-list').should('be.visible');
-    cy.screenshot(`${ssPrefix}-trash`);
-  });
-
-  it('/tags is successfully loaded', () => {
-    cy.visit('/tags');
-
-    // open sidebar
-    cy.collapseSidebar(false);
-    // select tags
-    cy.getByTestid('grw-sidebar-nav-primary-tags').click();
-    cy.getByTestid('grw-sidebar-content-tags').should('be.visible');
-
-    cy.getByTestid('tags-page').should('be.visible');
-    cy.screenshot(`${ssPrefix}-tags`);
-  });
-
-});

+ 0 - 37
packages/app/test/cypress/integration/2-basic-features/open-page-create-modal.spec.ts

@@ -1,37 +0,0 @@
-context('Open PageCreateModal', () => {
-
-  const ssPrefix = 'open-page-create-modal-';
-
-  let connectSid: string | undefined;
-
-  before(() => {
-    // login
-    cy.fixture("user-admin.json").then(user => {
-      cy.login(user.username, user.password);
-    });
-    cy.getCookie('connect.sid').then(cookie => {
-      connectSid = cookie?.value;
-    });
-    // collapse sidebar
-    cy.collapseSidebar(true);
-  });
-
-  beforeEach(() => {
-    if (connectSid != null) {
-      cy.setCookie('connect.sid', connectSid);
-    }
-  });
-
-  it("PageCreateModal is shown successfully", () => {
-    cy.getByTestid('newPageBtn').click();
-
-    cy.getByTestid('page-create-modal').should('be.visible').screenshot(`${ssPrefix}-open`);
-
-    cy.getByTestid('row-create-page-under-below').find('input.form-control').clear().type('/new-page');
-    cy.getByTestid('btn-create-page-under-below').click();
-
-    cy.getByTestid('page-editor').should('be.visible');
-    cy.screenshot(`${ssPrefix}-create-clicked`, {capture: 'viewport'});
-  });
-
-});

+ 0 - 36
packages/app/test/cypress/integration/2-basic-features/open-page-delete-modal.spec.ts

@@ -1,36 +0,0 @@
-context('Open Page Delete Modal', () => {
-
-  const ssPrefix = 'access-to-page-delete-modal-';
-
-  let connectSid: string | undefined;
-
-  before(() => {
-    // login
-    cy.fixture("user-admin.json").then(user => {
-      cy.login(user.username, user.password);
-    });
-    cy.getCookie('connect.sid').then(cookie => {
-      connectSid = cookie?.value;
-    });
-    // collapse sidebar
-    cy.collapseSidebar(true);
-  });
-
-  beforeEach(() => {
-    if (connectSid != null) {
-      cy.setCookie('connect.sid', connectSid);
-    }
-  });
-
-  it('PageDeleteModal is shown successfully', () => {
-     cy.visit('/Sandbox/Bootstrap4', {  });
-     cy.get('#grw-subnav-container').within(() => {
-       cy.getByTestid('open-page-item-control-btn').click();
-       cy.getByTestid('open-page-delete-modal-btn').click();
-    });
-
-     cy.getByTestid('page-delete-modal').should('be.visible').screenshot(`${ssPrefix}-open-bootstrap4`);
-  });
-
-});
-

+ 0 - 34
packages/app/test/cypress/integration/2-basic-features/open-page-duplicate-modal.spec.ts

@@ -1,34 +0,0 @@
-context('Open Page Duplicate Modal', () => {
-
-  const ssPrefix = 'access-to-page-duplicate-modal-';
-
-  let connectSid: string | undefined;
-
-  before(() => {
-    // login
-    cy.fixture("user-admin.json").then(user => {
-      cy.login(user.username, user.password);
-    });
-    cy.getCookie('connect.sid').then(cookie => {
-      connectSid = cookie?.value;
-    });
-    // collapse sidebar
-    cy.collapseSidebar(true);
-  });
-
-  beforeEach(() => {
-    if (connectSid != null) {
-      cy.setCookie('connect.sid', connectSid);
-    }
-  });
-
-  it('PageDuplicateModal is shown successfully', () => {
-     cy.visit('/Sandbox/Bootstrap4', {  });
-     cy.get('#grw-subnav-container').within(() => {
-       cy.getByTestid('open-page-item-control-btn').click();
-       cy.getByTestid('open-page-duplicate-modal-btn').click();
-    });
-     cy.getByTestid('page-duplicate-modal').should('be.visible').screenshot(`${ssPrefix}-open-bootstrap4`);
-  });
-
-});

+ 0 - 35
packages/app/test/cypress/integration/2-basic-features/open-page-move-rename-modal.spec.ts

@@ -1,35 +0,0 @@
-context('Open Page Move Rename Modal', () => {
-
-  const ssPrefix = 'access-to-page-move-rename-modal';
-
-  let connectSid: string | undefined;
-
-  before(() => {
-    // login
-    cy.fixture("user-admin.json").then(user => {
-      cy.login(user.username, user.password);
-    });
-    cy.getCookie('connect.sid').then(cookie => {
-      connectSid = cookie?.value;
-    });
-    // collapse sidebar
-    cy.collapseSidebar(true);
-  });
-
-  beforeEach(() => {
-    if (connectSid != null) {
-      cy.setCookie('connect.sid', connectSid);
-    }
-  });
-
-  it('PageMoveRenameModal is shown successfully', () => {
-     cy.visit('/Sandbox/Bootstrap4', {  });
-     cy.get('#grw-subnav-container').within(() => {
-       cy.getByTestid('open-page-item-control-btn').click();
-       cy.getByTestid('open-page-move-rename-modal-btn').click();
-    });
-
-     cy.getByTestid('page-rename-modal').should('be.visible').screenshot(`${ssPrefix}-open-bootstrap4`);
-  });
-
-});

+ 0 - 61
packages/app/test/cypress/integration/2-basic-features/open-presentation-modal.spec.ts

@@ -1,61 +0,0 @@
-context('Open presentation modal', () => {
-
-  const ssPrefix = 'access-to-presentation-modal-';
-
-  let connectSid: string | undefined;
-
-  before(() => {
-    // login
-    cy.fixture("user-admin.json").then(user => {
-      cy.login(user.username, user.password);
-    });
-    cy.getCookie('connect.sid').then(cookie => {
-      connectSid = cookie?.value;
-    });
-  });
-
-  beforeEach(() => {
-    if (connectSid != null) {
-      cy.setCookie('connect.sid', connectSid);
-    }
-  });
-
-  it('PresentationModal for "/" is shown successfully', () => {
-    cy.visit('/');
-
-    cy.get('#grw-subnav-container').within(() => {
-      cy.getByTestid('open-page-item-control-btn').click({force: true});
-      cy.getByTestid('open-presentation-modal-btn').click({force: true});
-    });
-
-    // eslint-disable-next-line cypress/no-unnecessary-waiting
-    cy.wait(1500);
-    cy.screenshot(`${ssPrefix}-opne-top`);
-  });
-
-  it('PresentationModal for "/Sandbox/Bootstrap4" is shown successfully', () => {
-    cy.visit('/Sandbox/Bootstrap4');
-
-    cy.get('#grw-subnav-container').within(() => {
-      cy.getByTestid('open-page-item-control-btn').click({force: true});
-      cy.getByTestid('open-presentation-modal-btn').click({force: true});
-    });
-
-    // eslint-disable-next-line cypress/no-unnecessary-waiting
-    cy.wait(1500);
-    cy.screenshot(`${ssPrefix}-open-bootstrap4`);
-  });
-
-  it('PresentationModal for /Sandbox/Bootstrap4#Cards" is shown successfully', () => {
-    cy.visit('/Sandbox/Bootstrap4#Cards');
-
-    cy.get('#grw-subnav-container').within(() => {
-      cy.getByTestid('open-page-item-control-btn').click({force: true});
-      cy.getByTestid('open-presentation-modal-btn').click({force: true});
-    });
-
-    // eslint-disable-next-line cypress/no-unnecessary-waiting
-    cy.wait(1500);
-    cy.screenshot(`${ssPrefix}-open-bootstrap4-with-ancker-link`);
-  });
-});

+ 0 - 31
packages/app/test/cypress/integration/2-basic-features/switch-sidebar-contents.spec.ts

@@ -1,31 +0,0 @@
-context('Access to page', () => {
-  const ssPrefix = 'switch-sidebar-content';
-
-  let connectSid: string | undefined;
-
-  before(() => {
-    // login
-    cy.fixture("user-admin.json").then(user => {
-      cy.login(user.username, user.password);
-    });
-    cy.getCookie('connect.sid').then(cookie => {
-      connectSid = cookie?.value;
-    });
-  });
-
-  beforeEach(() => {
-    if (connectSid != null) {
-      cy.setCookie('connect.sid', connectSid);
-    }
-  });
-
-  it('PageTree is successfully shown', () => {
-    cy.visit('/');
-    cy.getByTestid('grw-sidebar-nav-primary-page-tree').click();
-    cy.screenshot(`${ssPrefix}-pagetree-before-load`, { capture: 'viewport' });
-    // eslint-disable-next-line cypress/no-unnecessary-waiting
-    cy.wait(1500);
-    cy.screenshot(`${ssPrefix}-pagetree-after-load`, { capture: 'viewport' });
-  });
-
-});

+ 121 - 0
packages/app/test/cypress/integration/2-basic-features/use-tools.spec.ts

@@ -0,0 +1,121 @@
+context('Switch Sidebar content', () => {
+  const ssPrefix = 'switch-sidebar-content';
+
+  beforeEach(() => {
+    // login
+    cy.fixture("user-admin.json").then(user => {
+      cy.login(user.username, user.password);
+    });
+  });
+
+  it('PageTree is successfully shown', () => {
+    cy.visit('/page');
+    cy.getByTestid('grw-sidebar-nav-primary-page-tree').click();
+    cy.screenshot(`${ssPrefix}-pagetree-before-load`, { capture: 'viewport' });
+    // eslint-disable-next-line cypress/no-unnecessary-waiting
+    cy.wait(1500);
+    cy.screenshot(`${ssPrefix}-pagetree-after-load`, { capture: 'viewport' });
+  });
+
+});
+
+
+context('Modal for page operation', () => {
+
+  const ssPrefix = 'modal-for-page-operation-';
+
+  beforeEach(() => {
+    // login
+    cy.fixture("user-admin.json").then(user => {
+      cy.login(user.username, user.password);
+    });
+  });
+
+  it("PageCreateModal is shown successfully", () => {
+    cy.visit('/me');
+
+    cy.getByTestid('newPageBtn').click();
+
+    cy.getByTestid('page-create-modal').should('be.visible').screenshot(`${ssPrefix}-open`);
+
+    cy.getByTestid('row-create-page-under-below').find('input.form-control').clear().type('/new-page');
+    cy.getByTestid('btn-create-page-under-below').click();
+
+    cy.getByTestid('page-editor').should('be.visible');
+    cy.screenshot(`${ssPrefix}-create-clicked`, {capture: 'viewport'});
+  });
+
+  it('PageDeleteModal is shown successfully', () => {
+    cy.visit('/Sandbox/Bootstrap4');
+
+     cy.get('#grw-subnav-container').within(() => {
+       cy.getByTestid('open-page-item-control-btn').click();
+       cy.getByTestid('open-page-delete-modal-btn').click();
+    });
+
+     cy.getByTestid('page-delete-modal').should('be.visible').screenshot(`${ssPrefix}-delete-bootstrap4`);
+  });
+
+  it('PageDuplicateModal is shown successfully', () => {
+    cy.visit('/Sandbox/Bootstrap4', {  });
+
+    cy.get('#grw-subnav-container').within(() => {
+      cy.getByTestid('open-page-item-control-btn').click();
+      cy.getByTestid('open-page-duplicate-modal-btn').click();
+    });
+
+    cy.getByTestid('page-duplicate-modal').should('be.visible').screenshot(`${ssPrefix}-duplicate-bootstrap4`);
+  });
+
+  it('PageMoveRenameModal is shown successfully', () => {
+    cy.visit('/Sandbox/Bootstrap4', {  });
+
+    cy.get('#grw-subnav-container').within(() => {
+      cy.getByTestid('open-page-item-control-btn').click();
+      cy.getByTestid('open-page-move-rename-modal-btn').click();
+    });
+
+    cy.getByTestid('page-rename-modal').should('be.visible').screenshot(`${ssPrefix}-rename-bootstrap4`);
+  });
+
+});
+
+
+context('Open presentation modal', () => {
+
+  const ssPrefix = 'access-to-presentation-modal-';
+
+  beforeEach(() => {
+    // login
+    cy.fixture("user-admin.json").then(user => {
+      cy.login(user.username, user.password);
+    });
+  });
+
+  it('PresentationModal for "/" is shown successfully', () => {
+    cy.visit('/');
+
+    cy.get('#grw-subnav-container').within(() => {
+      cy.getByTestid('open-page-item-control-btn').click({force: true});
+      cy.getByTestid('open-presentation-modal-btn').click({force: true});
+    });
+
+    // eslint-disable-next-line cypress/no-unnecessary-waiting
+    cy.wait(1500);
+    cy.screenshot(`${ssPrefix}-open-top`);
+  });
+
+  it('PresentationModal for "/Sandbox/Bootstrap4" is shown successfully', () => {
+    cy.visit('/Sandbox/Bootstrap4');
+
+    cy.get('#grw-subnav-container').within(() => {
+      cy.getByTestid('open-page-item-control-btn').click({force: true});
+      cy.getByTestid('open-presentation-modal-btn').click({force: true});
+    });
+
+    // eslint-disable-next-line cypress/no-unnecessary-waiting
+    cy.wait(1500);
+    cy.screenshot(`${ssPrefix}-open-bootstrap4`);
+  });
+
+});

+ 0 - 32
packages/app/test/cypress/integration/3-search/access-to-private-legacy-pages-directly.spec.ts

@@ -1,32 +0,0 @@
-context('Access to legacy private pages directly', () => {
-  const ssPrefix = 'access-to-legacy-private-pages-directly-';
-
-  let connectSid: string | undefined;
-
-  before(() => {
-    // login
-    cy.fixture("user-admin.json").then(user => {
-      cy.login(user.username, user.password);
-    });
-    cy.getCookie('connect.sid').then(cookie => {
-      connectSid = cookie?.value;
-    });
-    // collapse sidebar
-    cy.collapseSidebar(true);
-  });
-
-  beforeEach(() => {
-    if (connectSid != null) {
-      cy.setCookie('connect.sid', connectSid);
-    }
-  });
-
-  it('/_private-legacy-pages is successfully loaded', () => {
-    cy.visit('/_private-legacy-pages');
-
-    cy.getByTestid('search-result-base').should('be.visible');
-
-    cy.screenshot(`${ssPrefix}-shown`);
-  });
-
-});

+ 26 - 13
packages/app/test/cypress/integration/3-search/access-to-result-page-directly.spec.ts → packages/app/test/cypress/integration/3-search/search.spec.ts

@@ -1,26 +1,15 @@
-context('Access to search result page directly', () => {
+context('Access to search result page', () => {
   const ssPrefix = 'access-to-result-page-directly-';
   const ssPrefix = 'access-to-result-page-directly-';
 
 
-  let connectSid: string | undefined;
-
-  before(() => {
+  beforeEach(() => {
     // login
     // login
     cy.fixture("user-admin.json").then(user => {
     cy.fixture("user-admin.json").then(user => {
       cy.login(user.username, user.password);
       cy.login(user.username, user.password);
     });
     });
-    cy.getCookie('connect.sid').then(cookie => {
-      connectSid = cookie?.value;
-    });
     // collapse sidebar
     // collapse sidebar
     cy.collapseSidebar(true);
     cy.collapseSidebar(true);
   });
   });
 
 
-  beforeEach(() => {
-    if (connectSid != null) {
-      cy.setCookie('connect.sid', connectSid);
-    }
-  });
-
   it('/_search with "q" param is successfully loaded', () => {
   it('/_search with "q" param is successfully loaded', () => {
     cy.visit('/_search', { qs: { q: 'labels alerts cards blocks' } });
     cy.visit('/_search', { qs: { q: 'labels alerts cards blocks' } });
 
 
@@ -53,3 +42,27 @@ context('Access to search result page directly', () => {
   });
   });
 
 
 });
 });
+
+
+
+context('Access to legacy private pages', () => {
+  const ssPrefix = 'access-to-legacy-private-pages-directly-';
+
+  beforeEach(() => {
+    // login
+    cy.fixture("user-admin.json").then(user => {
+      cy.login(user.username, user.password);
+    });
+    // collapse sidebar
+    cy.collapseSidebar(true);
+  });
+
+  it('/_private-legacy-pages is successfully loaded', () => {
+    cy.visit('/_private-legacy-pages');
+
+    cy.getByTestid('search-result-base').should('be.visible');
+
+    cy.screenshot(`${ssPrefix}-shown`);
+  });
+
+});

+ 1 - 13
packages/app/test/cypress/integration/2-basic-features/access-to-admin-page.spec.ts → packages/app/test/cypress/integration/4-admin/access-to-admin-page.spec.ts

@@ -15,25 +15,13 @@ const adminMenues = [
 context('Access to Admin page', () => {
 context('Access to Admin page', () => {
   const ssPrefix = 'access-to-admin-page-';
   const ssPrefix = 'access-to-admin-page-';
 
 
-  let connectSid: string | undefined;
-
-  before(() => {
+  beforeEach(() => {
     // login
     // login
     cy.fixture("user-admin.json").then(user => {
     cy.fixture("user-admin.json").then(user => {
       cy.login(user.username, user.password);
       cy.login(user.username, user.password);
-
-    });
-    cy.getCookie('connect.sid').then(cookie => {
-      connectSid = cookie?.value;
     });
     });
   });
   });
 
 
-  beforeEach(() => {
-    if (connectSid != null) {
-      cy.setCookie('connect.sid', connectSid);
-    }
-  });
-
   it('/admin is successfully loaded', () => {
   it('/admin is successfully loaded', () => {
     cy.visit('/admin');
     cy.visit('/admin');
     cy.getByTestid('admin-home').should('be.visible');
     cy.getByTestid('admin-home').should('be.visible');

+ 11 - 3
packages/app/test/cypress/support/commands.ts

@@ -31,18 +31,24 @@ Cypress.Commands.add('getByTestid', (selector, options?) => {
 
 
 Cypress.Commands.add('login', (username, password) => {
 Cypress.Commands.add('login', (username, password) => {
   cy.session(username, () => {
   cy.session(username, () => {
-    cy.visit('/login');
+    cy.visit('/page-to-return-after-login');
     cy.getByTestid('tiUsernameForLogin').type(username);
     cy.getByTestid('tiUsernameForLogin').type(username);
     cy.getByTestid('tiPasswordForLogin').type(password);
     cy.getByTestid('tiPasswordForLogin').type(password);
     cy.getByTestid('btnSubmitForLogin').click();
     cy.getByTestid('btnSubmitForLogin').click();
   });
   });
 });
 });
 
 
+let isSidebarCollapsed: boolean | undefined;
+
 Cypress.Commands.add('collapseSidebar', (isCollapsed) => {
 Cypress.Commands.add('collapseSidebar', (isCollapsed) => {
-  const isGrowiPage = Cypress.$('body.growi').length > 0;
 
 
+  if (isSidebarCollapsed === isCollapsed) {
+    return;
+  }
+
+  const isGrowiPage = Cypress.$('body.growi').length > 0;
   if (!isGrowiPage) {
   if (!isGrowiPage) {
-    cy.visit('/');
+    cy.visit('/page-to-toggle-sidebar-collapsed');
   }
   }
 
 
   cy.getByTestid('grw-contextual-navigation-sub').then(($contents) => {
   cy.getByTestid('grw-contextual-navigation-sub').then(($contents) => {
@@ -56,4 +62,6 @@ Cypress.Commands.add('collapseSidebar', (isCollapsed) => {
       cy.wait(1500);
       cy.wait(1500);
     }
     }
   });
   });
+
+  isSidebarCollapsed = isCollapsed;
 });
 });

+ 3 - 9
yarn.lock

@@ -15176,10 +15176,6 @@ pacote@^11.2.6:
     ssri "^8.0.1"
     ssri "^8.0.1"
     tar "^6.1.0"
     tar "^6.1.0"
 
 
-pako@1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.3.tgz#5f515b0c6722e1982920ae8005eacb0b7ca73ccf"
-
 pako@^1.0.6:
 pako@^1.0.6:
   version "1.0.11"
   version "1.0.11"
   resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
   resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
@@ -15711,11 +15707,9 @@ pkg-up@^2.0.0:
     find-up "^2.1.0"
     find-up "^2.1.0"
 
 
 plantuml-encoder@^1.2.5:
 plantuml-encoder@^1.2.5:
-  version "1.2.5"
-  resolved "https://registry.yarnpkg.com/plantuml-encoder/-/plantuml-encoder-1.2.5.tgz#6b8e5b9e1a1dbd88b3fd9fb46f734eec7d44b548"
-  dependencies:
-    pako "1.0.3"
-    utf8-bytes "0.0.1"
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/plantuml-encoder/-/plantuml-encoder-1.4.0.tgz#7899302cf785de956bf1a167e15420feee5975f7"
+  integrity sha512-sxMwpDw/ySY1WB2CE3+IdMuEcWibJ72DDOsXLkSmEaSzwEUaYBT6DWgOfBiHGCux4q433X6+OEFWjlVqp7gL6g==
 
 
 pluralize@8.0.0, pluralize@^8.0.0:
 pluralize@8.0.0, pluralize@^8.0.0:
   version "8.0.0"
   version "8.0.0"