Просмотр исходного кода

Merge branch 'master' into fix/87892-fix-pt-scroll

* master:
  remove empty line
  add empty line
  add vrt file
  imprv: access to trash page test
  Add visual regression test - Add test: access to admin page spec
  add vrt tags page
  rename sentance
  rename sentence
  add draft vrt
  ok
  imprv: visit user page test
  add new variables IPageForPageDeleteModal
  remove  unneessary emoty string from useTranslation args
  switch delete modal ui by clicking on delete completely check box
Mao 4 лет назад
Родитель
Сommit
7a85b5f97a

+ 5 - 16
packages/app/src/components/PageDeleteModal.tsx

@@ -26,21 +26,14 @@ const deleteIconAndKey = {
   },
 };
 
-type Props = {
-  isDeleteCompletelyModal: boolean,
-  isAbleToDeleteCompletely: boolean,
-  onClose?: () => void,
-}
-
-const PageDeleteModal: FC<Props> = (props: Props) => {
-  const { t } = useTranslation('');
-  const {
-    isDeleteCompletelyModal, isAbleToDeleteCompletely,
-  } = props;
+const PageDeleteModal: FC = () => {
+  const { t } = useTranslation();
 
   const { data: deleteModalData, close: closeDeleteModal } = usePageDeleteModal();
 
   const isOpened = deleteModalData?.isOpened ?? false;
+  const isAbleToDeleteCompletely = deleteModalData?.isAbleToDeleteCompletely ?? false;
+  const isDeleteCompletelyModal = deleteModalData?.isDeleteCompletelyModal ?? false;
 
   const [isDeleteRecursively, setIsDeleteRecursively] = useState(true);
   const [isDeleteCompletely, setIsDeleteCompletely] = useState(isDeleteCompletelyModal && isAbleToDeleteCompletely);
@@ -143,7 +136,6 @@ const PageDeleteModal: FC<Props> = (props: Props) => {
   // DeleteCompletely is currently disabled
   // TODO1 : Retrive isAbleToDeleteCompleltly state everywhere in the system via swr.
   // Story: https://redmine.weseek.co.jp/issues/82222
-
   // TODO2 : use toaster
   // TASK : https://redmine.weseek.co.jp/issues/82299
   function renderDeleteCompletelyForm() {
@@ -154,13 +146,10 @@ const PageDeleteModal: FC<Props> = (props: Props) => {
           name="completely"
           id="deleteCompletely"
           type="checkbox"
-          // disabled={!isAbleToDeleteCompletely}
-          // disabled // Todo: will be implemented at https://redmine.weseek.co.jp/issues/82222
+          disabled={!isAbleToDeleteCompletely}
           checked={isDeleteCompletely}
           onChange={changeIsDeleteCompletelyHandler}
         />
-        {/* ↓↓ undo this comment out at https://redmine.weseek.co.jp/issues/82222 ↓↓ */}
-        {/* <label className="custom-control-label text-danger" htmlFor="deleteCompletely"> */}
         <label className="custom-control-label" htmlFor="deleteCompletely">
           { t('modal_delete.delete_completely')}
           <p className="form-text text-muted mt-0"> { t('modal_delete.completely') }</p>

+ 10 - 5
packages/app/src/components/Sidebar/PageTree/Item.tsx

@@ -13,7 +13,9 @@ import { pathUtils, pagePathUtils } from '@growi/core';
 import { toastWarning, toastError, toastSuccess } from '~/client/util/apiNotification';
 
 import { useSWRxPageChildren } from '~/stores/page-listing';
+import { useSWRxPageInfo } from '~/stores/page';
 import { apiv3Put, apiv3Post } from '~/client/util/apiv3-client';
+import { useShareLinkId } from '~/stores/context';
 import { IPageForPageDeleteModal } from '~/stores/modal';
 
 import TriangleIcon from '~/components/Icons/TriangleIcon';
@@ -29,7 +31,7 @@ interface ItemProps {
   isOpen?: boolean
   onClickDuplicateMenuItem?(pageId: string, path: string): void
   onClickRenameMenuItem?(pageId: string, revisionId: string, path: string): void
-  onClickDeleteMenuItem?(pageToDelete: IPageForPageDeleteModal | null): void
+  onClickDeleteMenuItem?(pageToDelete: IPageForPageDeleteModal | null, isAbleToDeleteCompletely: boolean): void
 }
 
 // Utility to mark target
@@ -76,6 +78,8 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
   const { page, children } = itemNode;
 
   const [pageTitle, setPageTitle] = useState(page.path);
+  const { data: shareLinkId } = useShareLinkId();
+  const { data: pageInfo } = useSWRxPageInfo(page._id ?? null, shareLinkId);
   const [currentChildren, setCurrentChildren] = useState(children);
   const [isOpen, setIsOpen] = useState(_isOpen);
   const [isNewPageInputShown, setNewPageInputShown] = useState(false);
@@ -237,7 +241,7 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
     onClickRenameMenuItem(pageId, revisionId as string, path);
   }, [onClickRenameMenuItem, page]);
 
-  const onClickDeleteButton = useCallback(async(_pageId: string): Promise<void> => {
+  const deleteMenuItemClickHandler = useCallback(async(_pageId: string): Promise<void> => {
     if (onClickDeleteMenuItem == null) {
       return;
     }
@@ -253,9 +257,10 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
       revisionId: revisionId as string,
       path,
     };
+    const isAbleToDeleteCompletely = pageInfo?.isAbleToDeleteCompletely ?? false;
 
-    onClickDeleteMenuItem(pageToDelete);
-  }, [page, onClickDeleteMenuItem]);
+    onClickDeleteMenuItem(pageToDelete, isAbleToDeleteCompletely);
+  }, [onClickDeleteMenuItem, page, pageInfo?.isAbleToDeleteCompletely]);
 
   const onPressEnterForCreateHandler = async(inputText: string) => {
     setNewPageInputShown(false);
@@ -382,7 +387,7 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
             showBookmarkMenuItem
             onClickBookmarkMenuItem={bookmarkMenuItemClickHandler}
             onClickDuplicateMenuItem={duplicateMenuItemClickHandler}
-            onClickDeleteMenuItem={onClickDeleteButton}
+            onClickDeleteMenuItem={deleteMenuItemClickHandler}
             onClickRenameMenuItem={renameMenuItemClickHandler}
           >
             <DropdownToggle color="transparent" className="border-0 rounded btn-page-item-control p-0">

+ 3 - 3
packages/app/src/components/Sidebar/PageTree/ItemsTree.tsx

@@ -64,7 +64,7 @@ const renderByInitialNode = (
     targetPathOrId?: string,
     onClickDuplicateMenuItem?: (pageId: string, path: string) => void,
     onClickRenameMenuItem?: (pageId: string, revisionId: string, path: string) => void,
-    onClickDeleteMenuItem?: (pageToDelete: IPageForPageDeleteModal | null) => void,
+    onClickDeleteMenuItem?: (pageToDelete: IPageForPageDeleteModal | null, isAbleToDeleteCompletely: boolean) => void,
 ): JSX.Element => {
 
   return (
@@ -160,8 +160,8 @@ const ItemsTree: FC<ItemsTreeProps> = (props: ItemsTreeProps) => {
     }
   };
 
-  const onClickDeleteMenuItem = (pageToDelete: IPageForPageDeleteModal) => {
-    openDeleteModal([pageToDelete], onDeletedHandler);
+  const onClickDeleteMenuItem = (pageToDelete: IPageForPageDeleteModal, isAbleToDeleteCompletely) => {
+    openDeleteModal([pageToDelete], onDeletedHandler, isAbleToDeleteCompletely);
   };
 
   if (error1 != null || error2 != null) {

+ 25 - 3
packages/app/src/stores/modal.tsx

@@ -34,6 +34,8 @@ export type IPageForPageDeleteModal = {
   pageId: string,
   revisionId?: string,
   path: string
+  isAbleToDeleteCompletely?: boolean,
+  isDeleteCompletelyModal?: boolean,
 }
 
 export type OnDeletedFunction = (pathOrPaths: string | string[], isRecursively: Nullable<true>, isCompletely: Nullable<true>) => void;
@@ -42,20 +44,40 @@ type DeleteModalStatus = {
   isOpened: boolean,
   pages?: IPageForPageDeleteModal[],
   onDeleted?: OnDeletedFunction,
+  isAbleToDeleteCompletely?: boolean,
+  isDeleteCompletelyModal?: boolean,
 }
 
 type DeleteModalStatusUtils = {
-  open(pages?: IPageForPageDeleteModal[], onDeleted?: OnDeletedFunction): Promise<DeleteModalStatus | undefined>,
+  open(
+    pages?: IPageForPageDeleteModal[],
+    onDeleted?: OnDeletedFunction,
+    isAbleToDeleteCompletely?: boolean,
+    isDeleteCompletelyModal?: boolean,
+  ): Promise<DeleteModalStatus | undefined>,
   close(): Promise<DeleteModalStatus | undefined>,
 }
 
 export const usePageDeleteModal = (status?: DeleteModalStatus): SWRResponse<DeleteModalStatus, Error> & DeleteModalStatusUtils => {
-  const initialData: DeleteModalStatus = { isOpened: false, pages: [], onDeleted: () => {} };
+  const initialData: DeleteModalStatus = {
+    isOpened: false,
+    pages: [],
+    onDeleted: () => {},
+    isAbleToDeleteCompletely: false,
+    isDeleteCompletelyModal: false,
+  };
   const swrResponse = useStaticSWR<DeleteModalStatus, Error>('deleteModalStatus', status, { fallbackData: initialData });
 
   return {
     ...swrResponse,
-    open: (pages?: IPageForPageDeleteModal[], onDeleted?: OnDeletedFunction) => swrResponse.mutate({ isOpened: true, pages, onDeleted }),
+    open: (
+        pages?: IPageForPageDeleteModal[],
+        onDeleted?: OnDeletedFunction,
+        isAbleToDeleteCompletely?: boolean,
+        isDeleteCompletelyModal?: boolean,
+    ) => swrResponse.mutate({
+      isOpened: true, pages, onDeleted, isAbleToDeleteCompletely, isDeleteCompletelyModal,
+    }),
     close: () => swrResponse.mutate({ isOpened: false }),
   };
 };

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

@@ -0,0 +1,103 @@
+const ssPrefix = 'access-to-admin-page-';
+
+const adminMenues = [
+  'app', // App
+  'security', // Security
+  'security', // Security
+  'security', // Security
+  'security', // Security
+  'security', // Security
+  'security', // Security
+  'security', // Security
+  'security', // Security
+  'security', // Security
+  'security', // Security
+];
+
+context('Access to Admin 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;
+    });
+  });
+
+  beforeEach(() => {
+    if (connectSid != null) {
+      cy.setCookie('connect.sid', connectSid);
+    }
+  });
+
+  it('/admin is successfully loaded', () => {
+    cy.visit('/admin');
+    cy.screenshot(`${ssPrefix}-admin`, { capture: 'viewport' });
+  });
+
+  it('/admin/app is successfully loaded', () => {
+    cy.visit('/admin/app');
+    cy.screenshot(`${ssPrefix}-admin-app`, { capture: 'viewport' });
+  });
+
+  it('/admin/security is successfully loaded', () => {
+    cy.visit('/admin/security');
+    cy.screenshot(`${ssPrefix}-admin-security`, { capture: 'viewport' });
+  });
+
+  it('/admin/markdown is successfully loaded', () => {
+    cy.visit('/admin/markdown');
+    cy.screenshot(`${ssPrefix}-admin-markdown`, { capture: 'viewport' });
+  });
+
+  it('/admin/customize is successfully loaded', () => {
+    cy.visit('/admin/customize');
+    cy.screenshot(`${ssPrefix}-admin-customize`, { capture: 'viewport' });
+  });
+
+  it('/admin/importer is successfully loaded', () => {
+    cy.visit('/admin/importer');
+    cy.screenshot(`${ssPrefix}-admin-importer`, { capture: 'viewport' });
+  });
+
+  it('/admin/export is successfully loaded', () => {
+    cy.visit('/admin/export');
+    cy.screenshot(`${ssPrefix}-admin-export`, { capture: 'viewport' });
+  });
+
+  it('/admin/notification is successfully loaded', () => {
+    cy.visit('/admin/notification');
+    cy.screenshot(`${ssPrefix}-admin-notification`, { capture: 'viewport' });
+  });
+
+  it('/admin/slack-integration is successfully loaded', () => {
+    cy.visit('/admin/slack-integration');
+    cy.screenshot(`${ssPrefix}-admin-slack-integration`, { capture: 'viewport' });
+  });
+
+  it('/admin/slack-integration-legacy is successfully loaded', () => {
+    cy.visit('/admin/slack-integration-legacy');
+    cy.screenshot(`${ssPrefix}-admin-slack-integration-legacy`, { capture: 'viewport' });
+  });
+
+  it('/admin/users is successfully loaded', () => {
+    cy.visit('/admin/users');
+    cy.screenshot(`${ssPrefix}-admin-users`, { capture: 'viewport' });
+  });
+
+  it('/admin/user-groups is successfully loaded', () => {
+    cy.visit('/admin/user-groups');
+    cy.screenshot(`${ssPrefix}-admin-user-groups`, { capture: 'viewport' });
+  });
+
+  it('/admin/search is successfully loaded', () => {
+    cy.visit('/admin/search');
+    cy.screenshot(`${ssPrefix}-admin-search`, { capture: 'viewport' });
+  });
+
+});

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

@@ -0,0 +1,28 @@
+const ssPrefix = 'access-to-page-';
+
+context('Access to 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;
+    });
+  });
+
+  beforeEach(() => {
+    if (connectSid != null) {
+      cy.setCookie('connect.sid', connectSid);
+    }
+  });
+
+  it('/me is successfully loaded', () => {
+    cy.visit('/me', {  });
+    cy.screenshot(`${ssPrefix}-me`, { capture: 'viewport' });
+  });
+
+});

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

@@ -30,9 +30,34 @@ context('Access to page', () => {
     cy.screenshot(`${ssPrefix}-sandbox-headers`, { capture: 'viewport' });
   });
 
+  it('/trash is successfully loaded', () => {
+    cy.visit('/trash', {  });
+    cy.screenshot(`${ssPrefix}-trash`, { capture: 'viewport' });
+  });
+
   it('/Sandbox/Math is successfully loaded', () => {
     cy.visit('/Sandbox/Math');
     cy.screenshot(`${ssPrefix}-sandbox-math`, { capture: 'viewport' });
   });
 
+  it('/Sandbox with edit is successfully loaded', () => {
+    cy.visit('/Sandbox#edit');
+    cy.screenshot(`${ssPrefix}-sandbox-edit-page`, { capture: 'viewport' });
+  })
+
+  it('/user/admin is successfully loaded', () => {
+    cy.visit('/user/admin', {  });
+    cy.screenshot(`${ssPrefix}-user-admin`, { capture: 'viewport' });
+  });
+
+  it('Draft page is successfully shown', () => {
+    cy.visit('/me/drafts');
+    cy.screenshot(`${ssPrefix}-draft-page`, { capture: 'viewport' });
+  });
+  
+  it('/tags is successfully loaded', () => {
+    cy.visit('/tags');
+    cy.screenshot(`${ssPrefix}-tags`, { capture: 'viewport' });
+  });
+  
 });