Ver Fonte

Merge branch 'master' into imprv/tune-socket-conn-performance

Haku Mizuki há 4 anos atrás
pai
commit
657c74236a
24 ficheiros alterados com 149 adições e 91 exclusões
  1. 1 0
      packages/app/resource/locales/en_US/translation.json
  2. 1 0
      packages/app/resource/locales/ja_JP/translation.json
  3. 2 1
      packages/app/resource/locales/zh_CN/translation.json
  4. 3 1
      packages/app/src/components/Common/ClosableTextInput.tsx
  5. 20 3
      packages/app/src/components/DescendantsPageList.tsx
  6. 4 1
      packages/app/src/components/Page/TrashPageAlert.jsx
  7. 4 2
      packages/app/src/components/PageDeleteModal.tsx
  8. 4 2
      packages/app/src/components/PageList/PageList.tsx
  9. 7 4
      packages/app/src/components/PageList/PageListItemL.tsx
  10. 12 19
      packages/app/src/components/PutbackPageModal.jsx
  11. 1 0
      packages/app/src/interfaces/ui.ts
  12. 2 0
      packages/app/src/server/routes/apiv3/pages.js
  13. 13 4
      packages/app/src/server/service/page.ts
  14. 27 8
      packages/app/src/stores/modal.tsx
  15. 3 1
      packages/app/test/cypress/integration/1-install/install.spec.ts
  16. 13 13
      packages/app/test/cypress/integration/2-basic-features/access-to-admin-page.spec.ts
  17. 2 2
      packages/app/test/cypress/integration/2-basic-features/access-to-me-page.spec.ts
  18. 9 7
      packages/app/test/cypress/integration/2-basic-features/access-to-page.spec.ts
  19. 2 2
      packages/app/test/cypress/integration/2-basic-features/access-to-special-page.spec.ts
  20. 3 3
      packages/app/test/cypress/integration/2-basic-features/open-presentation-modal.spec.ts
  21. 1 1
      packages/app/test/cypress/integration/3-search/access-to-private-legacy-pages-directly.spec.ts
  22. 9 9
      packages/app/test/cypress/integration/3-search/access-to-result-page-directly.spec.ts
  23. 2 1
      packages/app/test/cypress/support/screenshot.ts
  24. 4 7
      packages/app/test/integration/service/page.test.js

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

@@ -424,6 +424,7 @@
   },
   "Put Back": "Put back",
   "Delete Completely": "Delete completely",
+  "page_has_been_reverted": "{{path}} has been reverted",
   "modal_delete": {
     "delete_page": "Delete page",
     "deleting_page": "Deleting page",

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

@@ -423,6 +423,7 @@
   },
   "Put Back": "元に戻す",
   "Delete Completely": "完全削除",
+  "page_has_been_reverted": "{{path}} を元に戻しました",
   "modal_delete": {
     "delete_page": "ページを削除する",
     "deleting_page": "ページパス",

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

@@ -401,7 +401,8 @@
 		}
 	},
 	"Put Back": "Put back",
-	"Delete Completely": "Delete completely",
+  "Delete Completely": "Delete completely",
+  "page_has_been_reverted": "{{path}} 已还原",
 	"modal_delete": {
 		"delete_page": "Delete page",
 		"deleting_page": "Deleting page",

+ 3 - 1
packages/app/src/components/Common/ClosableTextInput.tsx

@@ -30,6 +30,7 @@ const ClosableTextInput: FC<ClosableTextInputProps> = memo((props: ClosableTextI
 
   const [inputText, setInputText] = useState(props.value);
   const [currentAlertInfo, setAlertInfo] = useState<AlertInfo | null>(null);
+  const [isAbleToShowAlert, setIsAbleToShowAlert] = useState<boolean>(false);
 
   const createValidation = async(inputText: string) => {
     if (props.inputValidator != null) {
@@ -42,6 +43,7 @@ const ClosableTextInput: FC<ClosableTextInputProps> = memo((props: ClosableTextI
     const inputText = e.target.value;
     createValidation(inputText);
     setInputText(inputText);
+    setIsAbleToShowAlert(true);
   };
 
   const onFocusHandler = async(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -119,7 +121,7 @@ const ClosableTextInput: FC<ClosableTextInputProps> = memo((props: ClosableTextI
         onBlur={onBlurHandler}
         autoFocus={false}
       />
-      <AlertInfo />
+      {isAbleToShowAlert && <AlertInfo />}
     </div>
   );
 });

+ 20 - 3
packages/app/src/components/DescendantsPageList.tsx

@@ -7,10 +7,12 @@ import {
   IPageInfoForOperation,
 } from '~/interfaces/page';
 import { IPagingResult } from '~/interfaces/paging-result';
-import { OnDeletedFunction } from '~/interfaces/ui';
+import { OnDeletedFunction, OnPutBackedFunction } from '~/interfaces/ui';
 import { useIsGuestUser, useIsSharedUser, useIsTrashPage } from '~/stores/context';
 
-import { useSWRxDescendantsPageListForCurrrentPath, useSWRxPageInfoForList, useSWRxPageList } from '~/stores/page';
+import {
+  useSWRxDescendantsPageListForCurrrentPath, useSWRxPageInfoForList, useSWRxPageList, useDescendantsPageListForCurrentPathTermManager,
+} from '~/stores/page';
 import { usePageTreeTermManager } from '~/stores/page-listing';
 import { ForceHideMenuItems, MenuItemType } from './Common/Dropdown/PageItemControl';
 
@@ -24,6 +26,7 @@ type SubstanceProps = {
   setActivePage: (activePage: number) => void,
   forceHideMenuItems?: ForceHideMenuItems,
   onPagesDeleted?: OnDeletedFunction,
+  onPagePutBacked?: OnPutBackedFunction,
 }
 
 const convertToIDataWithMeta = (page: IPageHasId): IDataWithMeta<IPageHasId> => {
@@ -35,7 +38,7 @@ export const DescendantsPageListSubstance = (props: SubstanceProps): JSX.Element
   const { t } = useTranslation();
 
   const {
-    pagingResult, activePage, setActivePage, forceHideMenuItems, onPagesDeleted,
+    pagingResult, activePage, setActivePage, forceHideMenuItems, onPagesDeleted, onPagePutBacked,
   } = props;
 
   const { data: isGuestUser } = useIsGuestUser();
@@ -47,6 +50,7 @@ export const DescendantsPageListSubstance = (props: SubstanceProps): JSX.Element
 
   // for mutation
   const { advance: advancePt } = usePageTreeTermManager();
+  const { advance: advanceDpl } = useDescendantsPageListForCurrentPathTermManager();
 
   // initial data
   if (pagingResult != null) {
@@ -66,6 +70,17 @@ export const DescendantsPageListSubstance = (props: SubstanceProps): JSX.Element
     }
   }, [advancePt, onPagesDeleted, t]);
 
+  const pagePutBackedHandler: OnPutBackedFunction = useCallback((path) => {
+    toastSuccess(t('page_has_been_reverted', { path }));
+
+    advancePt();
+    advanceDpl();
+
+    if (onPagePutBacked != null) {
+      onPagePutBacked(path);
+    }
+  }, [advanceDpl, advancePt, onPagePutBacked, t]);
+
   function setPageNumber(selectedPageNumber) {
     setActivePage(selectedPageNumber);
   }
@@ -89,6 +104,7 @@ export const DescendantsPageListSubstance = (props: SubstanceProps): JSX.Element
         isEnableActions={!isGuestUser}
         forceHideMenuItems={forceHideMenuItems}
         onPagesDeleted={pageDeletedHandler}
+        onPagePutBacked={pagePutBackedHandler}
       />
 
       { showPager && (
@@ -133,6 +149,7 @@ export const DescendantsPageList = (props: Props): JSX.Element => {
       activePage={activePage}
       setActivePage={setActivePage}
       onPagesDeleted={() => mutate()}
+      onPagePutBacked={() => mutate()}
     />
   );
 };

+ 4 - 1
packages/app/src/components/Page/TrashPageAlert.jsx

@@ -51,7 +51,10 @@ const TrashPageAlert = (props) => {
   }
 
   function openPutbackPageModalHandler() {
-    openPutBackPageModal(pageId, path);
+    const putBackedHandler = (path) => {
+      window.location.reload();
+    };
+    openPutBackPageModal({ pageId, path }, { onPutBacked: putBackedHandler });
   }
 
   function openPageDeleteModalHandler() {

+ 4 - 2
packages/app/src/components/PageDeleteModal.tsx

@@ -208,8 +208,10 @@ const PageDeleteModal: FC = () => {
   }
 
   const renderPagePathsToDelete = () => {
-    if (injectedPages != null && injectedPages != null) {
-      return injectedPages.map(page => (
+    const pages = injectedPages != null && injectedPages.length > 0 ? injectedPages : deleteModalData?.pages;
+
+    if (pages != null) {
+      return pages.map(page => (
         <div key={page.data._id}>
           <code>{ page.data.path }</code>
           { !page.meta?.isDeletable && <span className="ml-3 text-danger"><strong>(CAN NOT TO DELETE)</strong></span> }

+ 4 - 2
packages/app/src/components/PageList/PageList.tsx

@@ -2,7 +2,7 @@ import React from 'react';
 import { useTranslation } from 'react-i18next';
 
 import { IPageWithMeta } from '~/interfaces/page';
-import { OnDeletedFunction } from '~/interfaces/ui';
+import { OnDeletedFunction, OnPutBackedFunction } from '~/interfaces/ui';
 import { ForceHideMenuItems } from '../Common/Dropdown/PageItemControl';
 
 import { PageListItemL } from './PageListItemL';
@@ -13,12 +13,13 @@ type Props = {
   isEnableActions?: boolean,
   forceHideMenuItems?: ForceHideMenuItems,
   onPagesDeleted?: OnDeletedFunction,
+  onPagePutBacked?: OnPutBackedFunction,
 }
 
 const PageList = (props: Props): JSX.Element => {
   const { t } = useTranslation();
   const {
-    pages, isEnableActions, forceHideMenuItems, onPagesDeleted,
+    pages, isEnableActions, forceHideMenuItems, onPagesDeleted, onPagePutBacked,
   } = props;
 
   if (pages == null) {
@@ -38,6 +39,7 @@ const PageList = (props: Props): JSX.Element => {
       isEnableActions={isEnableActions}
       forceHideMenuItems={forceHideMenuItems}
       onPageDeleted={onPagesDeleted}
+      onPagePutBacked={onPagePutBacked}
     />
   ));
 

+ 7 - 4
packages/app/src/components/PageList/PageListItemL.tsx

@@ -23,7 +23,9 @@ import {
   IPageInfoAll, IPageInfoForEntity, IPageInfoForListing, IPageWithMeta, isIPageInfoForListing,
 } from '~/interfaces/page';
 import { IPageSearchMeta, isIPageSearchMeta } from '~/interfaces/search';
-import { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction } from '~/interfaces/ui';
+import {
+  OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction, OnPutBackedFunction,
+} from '~/interfaces/ui';
 import LinkedPagePath from '~/models/linked-page-path';
 
 import { ForceHideMenuItems, PageItemControl } from '../Common/Dropdown/PageItemControl';
@@ -40,6 +42,7 @@ type Props = {
   onPageDuplicated?: OnDuplicatedFunction,
   onPageRenamed?: OnRenamedFunction,
   onPageDeleted?: OnDeletedFunction,
+  onPagePutBacked?: OnPutBackedFunction,
 }
 
 const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (props: Props, ref): JSX.Element => {
@@ -48,7 +51,7 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (pr
     page: { data: pageData, meta: pageMeta }, isSelected, isEnableActions,
     forceHideMenuItems,
     showPageUpdatedTime,
-    onClickItem, onCheckboxChanged, onPageDuplicated, onPageRenamed, onPageDeleted,
+    onClickItem, onCheckboxChanged, onPageDuplicated, onPageRenamed, onPageDeleted, onPagePutBacked,
   } = props;
 
   const inputRef = useRef<HTMLInputElement>(null);
@@ -129,8 +132,8 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (pr
 
   const revertMenuItemClickHandler = useCallback(() => {
     const { _id: pageId, path } = pageData;
-    openPutBackPageModal(pageId, path);
-  }, [openPutBackPageModal, pageData]);
+    openPutBackPageModal({ pageId, path }, { onPutBacked: onPagePutBacked });
+  }, [onPagePutBacked, openPutBackPageModal, pageData]);
 
   const styleListGroupItem = (!isDeviceSmallerThanLg && onClickItem != null) ? 'list-group-item-action' : '';
   // background color of list item changes when class "active" exists under 'list-group-item'

+ 12 - 19
packages/app/src/components/PutbackPageModal.jsx

@@ -1,24 +1,23 @@
 import React, { useState } from 'react';
-import PropTypes from 'prop-types';
 
 import {
   Modal, ModalHeader, ModalBody, ModalFooter,
 } from 'reactstrap';
 
-import { withTranslation } from 'react-i18next';
+import { useTranslation } from 'react-i18next';
 
 import { usePutBackPageModal } from '~/stores/modal';
 import { apiPost } from '~/client/util/apiv1-client';
 
 import ApiErrorMessageList from './PageManagement/ApiErrorMessageList';
 
-const PutBackPageModal = (props) => {
-  const {
-    t,
-  } = props;
+const PutBackPageModal = () => {
+  const { t } = useTranslation();
 
   const { data: pageDataToRevert, close: closePutBackPageModal } = usePutBackPageModal();
-  const { isOpened, pageId, path } = pageDataToRevert;
+  const { isOpened, page } = pageDataToRevert;
+  const { pageId, path } = page;
+  const onPutBacked = pageDataToRevert.opts?.onPutBacked;
 
   const [errs, setErrs] = useState(null);
 
@@ -28,7 +27,7 @@ const PutBackPageModal = (props) => {
     setIsPutbackRecursively(!isPutbackRecursively);
   }
 
-  async function putbackPage() {
+  async function putbackPageButtonHandler() {
     setErrs(null);
 
     try {
@@ -41,17 +40,16 @@ const PutBackPageModal = (props) => {
         recursively,
       });
 
-      const putbackPagePath = response.page.path;
-      window.location.href = encodeURI(putbackPagePath);
+      if (onPutBacked != null) {
+        onPutBacked(response.page.path);
+      }
+      closePutBackPageModal();
     }
     catch (err) {
       setErrs(err);
     }
   }
 
-  async function putbackPageButtonHandler() {
-    putbackPage();
-  }
 
   return (
     <Modal isOpen={isOpened} toggle={closePutBackPageModal} className="grw-create-page">
@@ -90,9 +88,4 @@ const PutBackPageModal = (props) => {
 
 };
 
-PutBackPageModal.propTypes = {
-  t: PropTypes.func.isRequired, //  i18next
-};
-
-
-export default withTranslation()(PutBackPageModal);
+export default PutBackPageModal;

+ 1 - 0
packages/app/src/interfaces/ui.ts

@@ -24,3 +24,4 @@ export type ICustomNavTabMappings = { [key: string]: ICustomTabContent };
 export type OnDeletedFunction = (idOrPaths: string | string[], isRecursively: Nullable<true>, isCompletely: Nullable<true>) => void;
 export type OnRenamedFunction = (path: string) => void;
 export type OnDuplicatedFunction = (fromPath: string, toPath: string) => void;
+export type OnPutBackedFunction = (path: string) => void;

+ 2 - 0
packages/app/src/server/routes/apiv3/pages.js

@@ -174,6 +174,7 @@ module.exports = (crowi) => {
       body('pageId').isMongoId().withMessage('pageId is required'),
       body('revisionId').optional().isMongoId().withMessage('revisionId is required'), // required when v4
       body('newPagePath').isLength({ min: 1 }).withMessage('newPagePath is required'),
+      body('isRecursively').if(value => value != null).isBoolean().withMessage('isRecursively must be boolean'),
       body('isRenameRedirect').if(value => value != null).isBoolean().withMessage('isRenameRedirect must be boolean'),
       body('isRemainMetadata').if(value => value != null).isBoolean().withMessage('isRemainMetadata must be boolean'),
       body('isMoveMode').if(value => value != null).isBoolean().withMessage('isMoveMode must be boolean'),
@@ -473,6 +474,7 @@ module.exports = (crowi) => {
     let newPagePath = pathUtils.normalizePath(req.body.newPagePath);
 
     const options = {
+      isRecursively: req.body.isRecursively,
       createRedirectPage: req.body.isRenameRedirect,
       updateMetadata: !req.body.isRemainMetadata,
       isMoveMode: req.body.isMoveMode,

+ 13 - 4
packages/app/src/server/service/page.ts

@@ -585,17 +585,22 @@ class PageService {
     return newParent;
   }
 
-  // !!renaming always include descendant pages!!
   private async renamePageV4(page, newPagePath, user, options) {
     const Page = this.crowi.model('Page');
     const Revision = this.crowi.model('Revision');
-    const updateMetadata = options.updateMetadata || false;
+    const {
+      isRecursively = false,
+      createRedirectPage = false,
+      updateMetadata = false,
+    } = options;
 
     // sanitize path
     newPagePath = this.crowi.xss.process(newPagePath); // eslint-disable-line no-param-reassign
 
     // create descendants first
-    await this.renameDescendantsWithStream(page, newPagePath, user, options);
+    if (isRecursively) {
+      await this.renameDescendantsWithStream(page, newPagePath, user, options);
+    }
 
 
     const update: any = {};
@@ -610,12 +615,16 @@ class PageService {
     // update Rivisions
     await Revision.updateRevisionListByPageId(renamedPage._id, { pageId: renamedPage._id });
 
+    if (createRedirectPage) {
+      const PageRedirect = mongoose.model('PageRedirect') as unknown as PageRedirectModel;
+      await PageRedirect.create({ fromPath: page.path, toPath: newPagePath });
+    }
+
     this.pageEvent.emit('rename', page, user);
 
     return renamedPage;
   }
 
-
   private async renameDescendants(pages, user, options, oldPagePathPrefix, newPagePathPrefix, shouldUseV4Process = true) {
     // v4 compatible process
     if (shouldUseV4Process) {

+ 27 - 8
packages/app/src/stores/modal.tsx

@@ -1,6 +1,8 @@
 import { SWRResponse } from 'swr';
 import { useStaticSWR } from './use-static-swr';
-import { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction } from '~/interfaces/ui';
+import {
+  OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction, OnPutBackedFunction,
+} from '~/interfaces/ui';
 import { IPageToDeleteWithMeta } from '~/interfaces/page';
 
 
@@ -155,27 +157,44 @@ export const usePageRenameModal = (status?: RenameModalStatus): SWRResponse<Rena
 /*
 * PutBackPageModal
 */
+export type IPageForPagePutBackModal = {
+  pageId: string,
+  path: string
+}
+
+export type IPutBackPageModalOption = {
+  onPutBacked?: OnPutBackedFunction,
+}
+
 type PutBackPageModalStatus = {
   isOpened: boolean,
-  pageId?: string,
-  path?: string,
+  page?: IPageForPagePutBackModal,
+  opts?: IPutBackPageModalOption,
 }
 
 type PutBackPageModalUtils = {
-  open(pageId: string, path: string): Promise<PutBackPageModalStatus | undefined>
+  open(
+    page?: IPageForPagePutBackModal,
+    opts?: IPutBackPageModalOption,
+    ): Promise<PutBackPageModalStatus | undefined>
   close():Promise<PutBackPageModalStatus | undefined>
 }
 
 export const usePutBackPageModal = (status?: PutBackPageModalStatus): SWRResponse<PutBackPageModalStatus, Error> & PutBackPageModalUtils => {
-  const initialData = { isOpened: false, pageId: '', path: '' };
+  const initialData: PutBackPageModalStatus = {
+    isOpened: false,
+    page: { pageId: '', path: '' },
+  };
   const swrResponse = useStaticSWR<PutBackPageModalStatus, Error>('putBackPageModalStatus', status, { fallbackData: initialData });
 
   return {
     ...swrResponse,
-    open: (pageId: string, path: string) => swrResponse.mutate({
-      isOpened: true, pageId, path,
+    open: (
+        page: IPageForPagePutBackModal, opts?: IPutBackPageModalOption,
+    ) => swrResponse.mutate({
+      isOpened: true, page, opts,
     }),
-    close: () => swrResponse.mutate({ isOpened: false }),
+    close: () => swrResponse.mutate({ isOpened: false, page: { pageId: '', path: '' } }),
   };
 };
 

+ 3 - 1
packages/app/test/cypress/integration/1-install/install.spec.ts

@@ -52,7 +52,9 @@ context('Installing', () => {
 
     cy.getByTestid('btnSubmit').click();
 
-    cy.screenshot(`${ssPrefix}-installed`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-installed`, {
+      blackout: ['.grw-sidebar-content-container'],
+    });
   });
 
 });

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

@@ -37,43 +37,43 @@ context('Access to Admin page', () => {
   it('/admin is successfully loaded', () => {
     cy.visit('/admin');
     cy.getByTestid('admin-home').should('be.visible');
-    cy.screenshot(`${ssPrefix}-admin`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-admin`);
   });
 
   it('/admin/app is successfully loaded', () => {
     cy.visit('/admin/app');
     cy.getByTestid('admin-app-settings').should('be.visible');
-    cy.screenshot(`${ssPrefix}-admin-app`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-admin-app`);
   });
 
   it('/admin/security is successfully loaded', () => {
     cy.visit('/admin/security');
     cy.getByTestid('admin-security').should('be.visible');
-    cy.screenshot(`${ssPrefix}-admin-security`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-admin-security`);
   });
 
   it('/admin/markdown is successfully loaded', () => {
     cy.visit('/admin/markdown');
     cy.getByTestid('admin-markdown').should('be.visible');
-    cy.screenshot(`${ssPrefix}-admin-markdown`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-admin-markdown`);
   });
 
   it('/admin/customize is successfully loaded', () => {
     cy.visit('/admin/customize');
     cy.getByTestid('admin-customize').should('be.visible');
-    cy.screenshot(`${ssPrefix}-admin-customize`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-admin-customize`);
   });
 
   it('/admin/importer is successfully loaded', () => {
     cy.visit('/admin/importer');
     cy.getByTestid('admin-import-data').should('be.visible');
-    cy.screenshot(`${ssPrefix}-admin-importer`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-admin-importer`);
   });
 
   it('/admin/export is successfully loaded', () => {
     cy.visit('/admin/export');
     cy.getByTestid('admin-export-archive-data').should('be.visible');
-    cy.screenshot(`${ssPrefix}-admin-export`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-admin-export`);
   });
 
   it('/admin/notification is successfully loaded', () => {
@@ -81,32 +81,32 @@ context('Access to Admin page', () => {
     cy.getByTestid('admin-notification').should('be.visible');
     // wait for retrieving slack integration status
     cy.getByTestid('slack-integration-list-item').should('be.visible');
-    cy.screenshot(`${ssPrefix}-admin-notification`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-admin-notification`);
   });
 
   it('/admin/slack-integration is successfully loaded', () => {
     cy.visit('/admin/slack-integration');
     cy.getByTestid('admin-slack-integration').should('be.visible');
-    cy.screenshot(`${ssPrefix}-admin-slack-integration`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-admin-slack-integration`);
   });
 
   it('/admin/slack-integration-legacy is successfully loaded', () => {
     cy.visit('/admin/slack-integration-legacy');
     cy.getByTestid('admin-slack-integration-legacy').should('be.visible');
-    cy.screenshot(`${ssPrefix}-admin-slack-integration-legacy`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-admin-slack-integration-legacy`);
   });
 
   it('/admin/users is successfully loaded', () => {
     cy.visit('/admin/users');
     cy.getByTestid('admin-users').should('be.visible');
     cy.getByTestid('user-table-tr').first().should('be.visible');
-    cy.screenshot(`${ssPrefix}-admin-users`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-admin-users`);
   });
 
   it('/admin/user-groups is successfully loaded', () => {
     cy.visit('/admin/user-groups');
     cy.getByTestid('admin-user-groups').should('be.visible');
-    cy.screenshot(`${ssPrefix}-admin-user-groups`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-admin-user-groups`);
   });
 
   it('/admin/search is successfully loaded', () => {
@@ -114,7 +114,7 @@ context('Access to Admin page', () => {
     cy.getByTestid('admin-full-text-search').should('be.visible');
     // wait for connected
     cy.getByTestid('connection-status-badge-connected').should('be.visible');
-    cy.screenshot(`${ssPrefix}-admin-search`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-admin-search`);
   });
 
 });

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

@@ -23,12 +23,12 @@ context('Access to /me page', () => {
 
   it('/me is successfully loaded', () => {
     cy.visit('/me', {  });
-    cy.screenshot(`${ssPrefix}-me`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-me`);
   });
 
   it('Draft page is successfully shown', () => {
     cy.visit('/me/drafts');
-    cy.screenshot(`${ssPrefix}-draft-page`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-draft-page`);
   });
 
 });

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

@@ -24,29 +24,31 @@ context('Access to page', () => {
 
   it('/Sandbox is successfully loaded', () => {
     cy.visit('/Sandbox', {  });
-    cy.screenshot(`${ssPrefix}-sandbox`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-sandbox`);
   });
 
   it('/Sandbox with anchor hash is successfully loaded', () => {
     cy.visit('/Sandbox#Headers');
-    cy.getByTestid('grw-fab-create-page').should('be.visible');
-    cy.getByTestid('grw-fab-return-to-top').should('be.visible');
-    cy.screenshot(`${ssPrefix}-sandbox-headers`, { capture: 'viewport' });
+    cy.getByTestid('grw-fab-create-page').should('have.class', 'fadeInUp').should('be.visible');
+    cy.getByTestid('grw-fab-return-to-top').should('have.class', 'fadeInUp').should('be.visible');
+    cy.screenshot(`${ssPrefix}-sandbox-headers`, {
+      disableTimersAndAnimations: false,
+    });
   });
 
   it('/Sandbox/Math is successfully loaded', () => {
     cy.visit('/Sandbox/Math');
-    cy.screenshot(`${ssPrefix}-sandbox-math`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-sandbox-math`);
   });
 
   it('/Sandbox with edit is successfully loaded', () => {
     cy.visit('/Sandbox#edit');
-    cy.screenshot(`${ssPrefix}-sandbox-edit-page`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-sandbox-edit-page`);
   })
 
   it('/user/admin is successfully loaded', () => {
     cy.visit('/user/admin', {  });
-    cy.screenshot(`${ssPrefix}-user-admin`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-user-admin`);
   });
 
 });

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

@@ -25,7 +25,7 @@ context('Access to special pages', () => {
   it('/trash is successfully loaded', () => {
     cy.visit('/trash', {  });
     cy.getByTestid('trash-page-list').should('be.visible');
-    cy.screenshot(`${ssPrefix}-trash`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-trash`);
   });
 
   it('/tags is successfully loaded', () => {
@@ -38,7 +38,7 @@ context('Access to special pages', () => {
     cy.getByTestid('grw-sidebar-content-tags').should('be.visible');
 
     cy.getByTestid('tags-page').should('be.visible');
-    cy.screenshot(`${ssPrefix}-tags`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-tags`);
   });
 
 });

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

@@ -30,7 +30,7 @@ context('Open presentation modal', () => {
 
     // eslint-disable-next-line cypress/no-unnecessary-waiting
     cy.wait(1500);
-    cy.screenshot(`${ssPrefix}-opne-top`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-opne-top`);
   });
 
   it('PageCreateModal for "/Sandbox/Bootstrap4" is shown successfully', () => {
@@ -43,7 +43,7 @@ context('Open presentation modal', () => {
 
     // eslint-disable-next-line cypress/no-unnecessary-waiting
     cy.wait(1500);
-    cy.screenshot(`${ssPrefix}-open-bootstrap4`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-open-bootstrap4`);
   });
 
   it('PageCreateModal for /Sandbox/Bootstrap4#Cards" is shown successfully', () => {
@@ -56,6 +56,6 @@ context('Open presentation modal', () => {
 
     // eslint-disable-next-line cypress/no-unnecessary-waiting
     cy.wait(1500);
-    cy.screenshot(`${ssPrefix}-open-bootstrap4-with-ancker-link`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-open-bootstrap4-with-ancker-link`);
   });
 });

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

@@ -26,7 +26,7 @@ context('Access to legacy private pages directly', () => {
 
     cy.getByTestid('search-result-base').should('be.visible');
 
-    cy.screenshot(`${ssPrefix}-shown`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-shown`);
   });
 
 });

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

@@ -22,34 +22,34 @@ context('Access to search result page directly', () => {
   });
 
   it('/_search with "q" param is successfully loaded', () => {
-    cy.visit('/_search', { qs: { q: 'block labels alerts cards' } });
+    cy.visit('/_search', { qs: { q: 'labels alerts cards blocks' } });
 
     cy.getByTestid('search-result-list').should('be.visible');
     cy.getByTestid('search-result-content').should('be.visible');
 
-    cy.screenshot(`${ssPrefix}-with-q`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-with-q`);
   });
 
   it('checkboxes behaviors', () => {
-    cy.visit('/_search', { qs: { q: 'block labels alerts cards' } });
+    cy.visit('/_search', { qs: { q: 'labels alerts cards blocks' } });
 
     cy.getByTestid('search-result-base').should('be.visible');
     cy.getByTestid('search-result-list').should('be.visible');
 
     cy.getByTestid('cb-select').first().click({force: true});
-    cy.screenshot(`${ssPrefix}-the-first-checkbox-on`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-the-first-checkbox-on`);
     cy.getByTestid('cb-select').first().click({force: true});
-    cy.screenshot(`${ssPrefix}-the-first-checkbox-off`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-the-first-checkbox-off`);
 
     // click select all checkbox
     cy.getByTestid('cb-select-all').click({force: true});
-    cy.screenshot(`${ssPrefix}-the-select-all-checkbox-1`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-the-select-all-checkbox-1`);
     cy.getByTestid('cb-select').first().click({force: true});
-    cy.screenshot(`${ssPrefix}-the-select-all-checkbox-2`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-the-select-all-checkbox-2`);
     cy.getByTestid('cb-select').first().click({force: true});
-    cy.screenshot(`${ssPrefix}-the-select-all-checkbox-3`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-the-select-all-checkbox-3`);
     cy.getByTestid('cb-select-all').click({force: true});
-    cy.screenshot(`${ssPrefix}-the-select-all-checkbox-4`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-the-select-all-checkbox-4`);
   });
 
 });

+ 2 - 1
packages/app/test/cypress/support/screenshot.ts

@@ -1,3 +1,4 @@
 Cypress.Screenshot.defaults({
-  blackout: ['[data-hide-in-vrt=true]']
+  blackout: ['[data-hide-in-vrt=true]'],
+  capture: 'viewport',
 })

+ 4 - 7
packages/app/test/integration/service/page.test.js

@@ -293,7 +293,7 @@ describe('PageService', () => {
 
   describe('rename page without using renameDescendantsWithStreamSpy', () => {
     test('rename page with different tree with isRecursively [deeper]', async() => {
-      const resultPage = await crowi.pageService.renamePage(parentForRename6, '/parentForRename6/renamedChild', testUser1, {});
+      const resultPage = await crowi.pageService.renamePage(parentForRename6, '/parentForRename6/renamedChild', testUser1, { isRecursively: true });
       const wrongPage = await Page.findOne({ path: '/parentForRename6/renamedChild/renamedChild' });
       const expectPage1 = await Page.findOne({ path: '/parentForRename6/renamedChild' });
       const expectPage2 = await Page.findOne({ path: '/parentForRename6-2021H1' });
@@ -315,7 +315,7 @@ describe('PageService', () => {
 
       // when
       //   rename /level1/level2 --> /level1
-      await crowi.pageService.renamePage(parentForRename7, '/level1', testUser1, {});
+      await crowi.pageService.renamePage(parentForRename7, '/level1', testUser1, { isRecursively: true });
 
       // then
       expect(await Page.findOne({ path: '/level1' })).not.toBeNull();
@@ -348,7 +348,6 @@ describe('PageService', () => {
         const resultPage = await crowi.pageService.renamePage(parentForRename1, '/renamed1', testUser2, {});
 
         expect(xssSpy).toHaveBeenCalled();
-        expect(renameDescendantsWithStreamSpy).toHaveBeenCalled(); // single rename is deprecated
 
         expect(pageEventSpy).toHaveBeenCalledWith('rename', parentForRename1, testUser2);
 
@@ -362,7 +361,6 @@ describe('PageService', () => {
         const resultPage = await crowi.pageService.renamePage(parentForRename2, '/renamed2', testUser2, { updateMetadata: true });
 
         expect(xssSpy).toHaveBeenCalled();
-        expect(renameDescendantsWithStreamSpy).toHaveBeenCalled();
 
         expect(pageEventSpy).toHaveBeenCalledWith('rename', parentForRename2, testUser2);
 
@@ -376,7 +374,6 @@ describe('PageService', () => {
         const resultPage = await crowi.pageService.renamePage(parentForRename3, '/renamed3', testUser2, { createRedirectPage: true });
 
         expect(xssSpy).toHaveBeenCalled();
-        expect(renameDescendantsWithStreamSpy).toHaveBeenCalled();
         expect(pageEventSpy).toHaveBeenCalledWith('rename', parentForRename3, testUser2);
 
         expect(resultPage.path).toBe('/renamed3');
@@ -386,7 +383,7 @@ describe('PageService', () => {
 
       test('rename page with isRecursively', async() => {
 
-        const resultPage = await crowi.pageService.renamePage(parentForRename4, '/renamed4', testUser2, { }, true);
+        const resultPage = await crowi.pageService.renamePage(parentForRename4, '/renamed4', testUser2, { isRecursively: true });
 
         expect(xssSpy).toHaveBeenCalled();
         expect(renameDescendantsWithStreamSpy).toHaveBeenCalled();
@@ -399,7 +396,7 @@ describe('PageService', () => {
 
       test('rename page with different tree with isRecursively', async() => {
 
-        const resultPage = await crowi.pageService.renamePage(parentForRename5, '/parentForRename5/renamedChild', testUser1, {}, true);
+        const resultPage = await crowi.pageService.renamePage(parentForRename5, '/parentForRename5/renamedChild', testUser1, { isRecursively: true });
         const wrongPage = await Page.findOne({ path: '/parentForRename5/renamedChild/renamedChild' });
         const expectPage = await Page.findOne({ path: '/parentForRename5/renamedChild' });