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

Merge remote-tracking branch 'origin/master' into fix/scroll-to-specific-pagetree-item

Yuki Takei 4 лет назад
Родитель
Сommit
c90d807389

+ 1 - 1
packages/app/src/components/SearchPage.tsx

@@ -204,7 +204,7 @@ export const SearchPage = (props: Props): JSX.Element => {
       >
         <button
           type="button"
-          className="btn btn-outline-danger border-0 px-2"
+          className="btn btn-outline-danger text-nowrap border-0 px-2"
           disabled={isDisabled}
           onClick={deleteAllButtonClickedHandler}
         >

+ 1 - 1
packages/app/src/components/SearchPage/SearchControl.tsx

@@ -61,7 +61,7 @@ const SearchControl: FC <Props> = React.memo((props: Props) => {
   }, [invokeSearch]);
 
   return (
-    <div className="position-sticky fixed-top shadow-sm">
+    <div className="position-sticky sticky-top shadow-sm">
       <div className="grw-search-page-nav d-flex py-3 align-items-center">
         <div className="flex-grow-1 mx-4">
           <SearchForm

+ 11 - 11
packages/app/src/server/service/page.ts

@@ -304,7 +304,7 @@ class PageService {
     const isRoot = isTopPage(page.path);
     const isPageRestricted = page.grant === Page.GRANT_RESTRICTED;
 
-    const shouldUseV4Process = !isRoot && !isPageRestricted && (!isV5Compatible || !isPageMigrated || isTrashPage);
+    const shouldUseV4Process = !isRoot && (!isV5Compatible || !isPageMigrated || isTrashPage || isPageRestricted);
 
     return shouldUseV4Process;
   }
@@ -315,7 +315,7 @@ class PageService {
     const isV5Compatible = this.crowi.configManager.getConfig('crowi', 'app:isV5Compatible');
     const isPageRestricted = page.grant === Page.GRANT_RESTRICTED;
 
-    const shouldUseV4Process = !isPageRestricted && !isV5Compatible;
+    const shouldUseV4Process = !isV5Compatible || isPageRestricted;
 
     return shouldUseV4Process;
   }
@@ -533,18 +533,15 @@ class PageService {
     const newParentPath = pathlib.dirname(toPath);
 
     // local util
-    const collectAncestorPathsUntilFromPath = (path: string, paths: string[] = [path]): string[] => {
-      const nextPath = pathlib.dirname(path);
-      if (nextPath === fromPath) {
-        return [...paths, nextPath];
-      }
-
-      paths.push(nextPath);
+    const collectAncestorPathsUntilFromPath = (path: string, paths: string[] = []): string[] => {
+      if (path === fromPath) return paths;
 
-      return collectAncestorPathsUntilFromPath(nextPath, paths);
+      const parentPath = pathlib.dirname(path);
+      paths.push(parentPath);
+      return collectAncestorPathsUntilFromPath(parentPath, paths);
     };
 
-    const pathsToInsert = collectAncestorPathsUntilFromPath(newParentPath);
+    const pathsToInsert = collectAncestorPathsUntilFromPath(toPath);
     const originalParent = await Page.findById(originalPage.parent);
     if (originalParent == null) {
       throw Error('Original parent not found');
@@ -2372,6 +2369,9 @@ class PageService {
       // find pages again to get updated descendantCount
       // then calculate inc
       const pageAfterUpdatingDescendantCount = await Page.findByIdAndViewer(page._id, user);
+      if (pageAfterUpdatingDescendantCount == null) {
+        throw Error('Page not found after updating descendantCount');
+      }
 
       const exDescendantCount = page.descendantCount;
       const newDescendantCount = pageAfterUpdatingDescendantCount.descendantCount;

+ 1 - 0
packages/app/src/styles/_navbar.scss

@@ -1,6 +1,7 @@
 .grw-navbar {
   top: -$grw-navbar-height !important;
 
+  z-index: $grw-navbar-z-index !important;
   max-height: $grw-navbar-height + $grw-navbar-border-width;
   border-top: 0;
   border-right: 0;

+ 3 - 0
packages/app/src/styles/_variables.scss

@@ -15,6 +15,9 @@ $font-family-monospace-not-strictly: Monaco, Menlo, Consolas, 'Courier New', Mei
 //== Layout
 $grw-navbar-height: 52px;
 $grw-navbar-border-width: 3.3333px;
+// slightly larger than $zindex-sticky
+// https://getbootstrap.jp/docs/4.5/layout/overview/#z-index
+$grw-navbar-z-index: 1025;
 
 $grw-subnav-min-height: 95px;
 $grw-subnav-min-height-md: 115px;

+ 1 - 1
packages/app/src/styles/theme/mixins/_list-group.scss

@@ -36,7 +36,7 @@
     border-color: $border-color-global;
 
     &.grw-pagetree-is-target {
-      background: $bgcolor-active;
+      background: $bgcolor-hover;
     }
     .grw-pagetree-count {
       background: $bgcolor;

+ 317 - 34
packages/app/test/integration/service/v5.migration.test.js

@@ -6,64 +6,245 @@ describe('V5 page migration', () => {
   let crowi;
   let Page;
   let User;
+  let UserGroup;
+  let UserGroupRelation;
 
   let testUser1;
 
+  let rootPage;
+
+  const groupIdIsolate = new mongoose.Types.ObjectId();
+  const groupIdA = new mongoose.Types.ObjectId();
+  const groupIdB = new mongoose.Types.ObjectId();
+  const groupIdC = new mongoose.Types.ObjectId();
+
+  const pageId1 = new mongoose.Types.ObjectId();
+  const pageId2 = new mongoose.Types.ObjectId();
+  const pageId3 = new mongoose.Types.ObjectId();
+  const pageId4 = new mongoose.Types.ObjectId();
+  const pageId5 = new mongoose.Types.ObjectId();
+  const pageId6 = new mongoose.Types.ObjectId();
+  const pageId7 = new mongoose.Types.ObjectId();
+  const pageId8 = new mongoose.Types.ObjectId();
+  const pageId9 = new mongoose.Types.ObjectId();
+  const pageId10 = new mongoose.Types.ObjectId();
+  const pageId11 = new mongoose.Types.ObjectId();
+
   beforeAll(async() => {
     jest.restoreAllMocks();
 
     crowi = await getInstance();
     Page = mongoose.model('Page');
     User = mongoose.model('User');
+    UserGroup = mongoose.model('UserGroup');
+    UserGroupRelation = mongoose.model('UserGroupRelation');
 
     await crowi.configManager.updateConfigsInTheSameNamespace('crowi', { 'app:isV5Compatible': true });
 
     await User.insertMany([{ name: 'testUser1', username: 'testUser1', email: 'testUser1@example.com' }]);
     testUser1 = await User.findOne({ username: 'testUser1' });
+    rootPage = await Page.findOne({ path: '/' });
+
+    await UserGroup.insertMany([
+      {
+        _id: groupIdIsolate,
+        name: 'groupIsolate',
+      },
+      {
+        _id: groupIdA,
+        name: 'groupA',
+      },
+      {
+        _id: groupIdB,
+        name: 'groupB',
+        parent: groupIdA,
+      },
+      {
+        _id: groupIdC,
+        name: 'groupC',
+        parent: groupIdB,
+      },
+    ]);
+
+    await UserGroupRelation.insertMany([
+      {
+        relatedGroup: groupIdIsolate,
+        relatedUser: testUser1._id,
+      },
+      {
+        relatedGroup: groupIdA,
+        relatedUser: testUser1._id,
+      },
+      {
+        relatedGroup: groupIdB,
+        relatedUser: testUser1._id,
+      },
+      {
+        relatedGroup: groupIdC,
+        relatedUser: testUser1._id,
+      },
+    ]);
+
+    await Page.insertMany([
+      {
+        path: '/private1',
+        grant: Page.GRANT_OWNER,
+        creator: testUser1,
+        lastUpdateUser: testUser1,
+        grantedUsers: [testUser1._id],
+      },
+      {
+        path: '/dummyParent/private1',
+        grant: Page.GRANT_OWNER,
+        creator: testUser1,
+        lastUpdateUser: testUser1,
+        grantedUsers: [testUser1._id],
+      },
+      {
+        path: '/dummyParent/private1/private2',
+        grant: Page.GRANT_OWNER,
+        creator: testUser1,
+        lastUpdateUser: testUser1,
+        grantedUsers: [testUser1._id],
+      },
+      {
+        path: '/dummyParent/private1/private3',
+        grant: Page.GRANT_OWNER,
+        creator: testUser1,
+        lastUpdateUser: testUser1,
+        grantedUsers: [testUser1._id],
+      },
+      {
+        _id: pageId1,
+        path: '/normalize_1',
+        parent: rootPage._id,
+        grant: Page.GRANT_PUBLIC,
+        isEmpty: true,
+      },
+      {
+        _id: pageId2,
+        path: '/normalize_1/normalize_2',
+        parent: pageId1,
+        grant: Page.GRANT_USER_GROUP,
+        grantedGroup: groupIdB,
+        grantedUsers: [testUser1._id],
+      },
+      {
+        _id: pageId3,
+        path: '/normalize_1',
+        grant: Page.GRANT_USER_GROUP,
+        grantedGroup: groupIdA,
+        grantedUsers: [testUser1._id],
+      },
+      {
+        _id: pageId4,
+        path: '/normalize_4',
+        parent: rootPage._id,
+        grant: Page.GRANT_PUBLIC,
+        isEmpty: true,
+      },
+      {
+        _id: pageId5,
+        path: '/normalize_4/normalize_5',
+        parent: pageId4,
+        grant: Page.GRANT_USER_GROUP,
+        grantedGroup: groupIdA,
+        grantedUsers: [testUser1._id],
+      },
+      {
+        _id: pageId6,
+        path: '/normalize_4',
+        grant: Page.GRANT_USER_GROUP,
+        grantedGroup: groupIdIsolate,
+        grantedUsers: [testUser1._id],
+      },
+      {
+        path: '/normalize_7/normalize_8_gA',
+        grant: Page.GRANT_USER_GROUP,
+        creator: testUser1,
+        grantedGroup: groupIdA,
+        grantedUsers: [testUser1._id],
+      },
+      {
+        path: '/normalize_7/normalize_8_gA/normalize_9_gB',
+        grant: Page.GRANT_USER_GROUP,
+        creator: testUser1,
+        grantedGroup: groupIdB,
+        grantedUsers: [testUser1._id],
+      },
+      {
+        path: '/normalize_7/normalize_8_gC',
+        grant: Page.GRANT_USER_GROUP,
+        grantedGroup: groupIdC,
+        grantedUsers: [testUser1._id],
+      },
+      {
+        _id: pageId7,
+        path: '/normalize_10',
+        grant: Page.GRANT_PUBLIC,
+        isEmpty: true,
+        parent: rootPage._id,
+        descendantCount: 3,
+      },
+      {
+        _id: pageId8,
+        path: '/normalize_10/normalize_11_gA',
+        isEmpty: true,
+        parent: pageId7,
+        descendantCount: 1,
+      },
+      {
+        _id: pageId9, // not v5
+        path: '/normalize_10/normalize_11_gA',
+        grant: Page.GRANT_USER_GROUP,
+        grantedGroup: groupIdA,
+        grantedUsers: [testUser1._id],
+      },
+      {
+        _id: pageId10,
+        path: '/normalize_10/normalize_11_gA/normalize_11_gB',
+        grant: Page.GRANT_USER_GROUP,
+        grantedGroup: groupIdB,
+        grantedUsers: [testUser1._id],
+        parent: pageId8,
+        descendantCount: 0,
+      },
+      {
+        _id: pageId11,
+        path: '/normalize_10/normalize_12_gC',
+        grant: Page.GRANT_USER_GROUP,
+        grantedGroup: groupIdC,
+        grantedUsers: [testUser1._id],
+        parent: pageId7,
+        descendantCount: 0,
+      },
+
+    ]);
+
   });
 
+  // https://github.com/jest-community/eslint-plugin-jest/blob/v24.3.5/docs/rules/expect-expect.md#assertfunctionnames
+  // pass unless the data is one of [false, 0, '', null, undefined, NaN]
+  const expectAllToBeTruthy = (dataList) => {
+    dataList.forEach((data, i) => {
+      if (data == null) { console.log(`index: ${i}`) }
+      expect(data).toBeTruthy();
+    });
+  };
 
   describe('normalizeParentRecursivelyByPages()', () => {
+
+    const normalizeParentRecursivelyByPages = async(pages, user) => {
+      return crowi.pageService.normalizeParentRecursivelyByPages(pages, user);
+    };
+
     test('should migrate all pages specified by pageIds', async() => {
       jest.restoreAllMocks();
 
-      // initialize pages for test
-      await Page.insertMany([
-        {
-          path: '/private1',
-          grant: Page.GRANT_OWNER,
-          creator: testUser1,
-          lastUpdateUser: testUser1,
-          grantedUsers: [testUser1._id],
-        },
-        {
-          path: '/dummyParent/private1',
-          grant: Page.GRANT_OWNER,
-          creator: testUser1,
-          lastUpdateUser: testUser1,
-          grantedUsers: [testUser1._id],
-        },
-        {
-          path: '/dummyParent/private1/private2',
-          grant: Page.GRANT_OWNER,
-          creator: testUser1,
-          lastUpdateUser: testUser1,
-          grantedUsers: [testUser1._id],
-        },
-        {
-          path: '/dummyParent/private1/private3',
-          grant: Page.GRANT_OWNER,
-          creator: testUser1,
-          lastUpdateUser: testUser1,
-          grantedUsers: [testUser1._id],
-        },
-      ]);
-
       const pagesToRun = await Page.find({ path: { $in: ['/private1', '/dummyParent/private1'] } });
 
       // migrate
-      await crowi.pageService.normalizeParentRecursivelyByPages(pagesToRun, testUser1);
-
+      await normalizeParentRecursivelyByPages(pagesToRun, testUser1);
       const migratedPages = await Page.find({
         path: {
           $in: ['/private1', '/dummyParent', '/dummyParent/private1', '/dummyParent/private1/private2', '/dummyParent/private1/private3'],
@@ -76,6 +257,57 @@ describe('V5 page migration', () => {
       expect(migratedPagePaths.sort()).toStrictEqual(expected.sort());
     });
 
+    test('should change all v4 pages with usergroup to v5 compatible and create new parent page', async() => {
+      const page8 = await Page.findOne({ path: '/normalize_7/normalize_8_gA' });
+      const page9 = await Page.findOne({ path: '/normalize_7/normalize_8_gA/normalize_9_gB' });
+      const page10 = await Page.findOne({ path: '/normalize_7/normalize_8_gC' });
+      const page11 = await Page.findOne({ path: '/normalize_7' });
+      expectAllToBeTruthy([page8, page9, page10]);
+      expect(page11).toBeNull();
+      await normalizeParentRecursivelyByPages([page8, page9, page10], testUser1);
+
+      // AM => After Migration
+      const page7 = await Page.findOne({ path: '/normalize_7' });
+      const page8AM = await Page.findOne({ path: '/normalize_7/normalize_8_gA' });
+      const page9AM = await Page.findOne({ path: '/normalize_7/normalize_8_gA/normalize_9_gB' });
+      const page10AM = await Page.findOne({ path: '/normalize_7/normalize_8_gC' });
+      expectAllToBeTruthy([page7, page8AM, page9AM, page10AM]);
+
+      expect(page7.isEmpty).toBe(true);
+
+      expect(page7.parent).toStrictEqual(rootPage._id);
+      expect(page8AM.parent).toStrictEqual(page7._id);
+      expect(page9AM.parent).toStrictEqual(page8AM._id);
+      expect(page10AM.parent).toStrictEqual(page7._id);
+    });
+
+    test("should replace empty page with same path with new non-empty page and update all related children's parent", async() => {
+      const page1 = await Page.findOne({ path: '/normalize_10', isEmpty: true });
+      const page2 = await Page.findOne({ path: '/normalize_10/normalize_11_gA', _id: pageId8, isEmpty: true });
+      const page3 = await Page.findOne({ path: '/normalize_10/normalize_11_gA', _id: pageId9 }); // not v5
+      const page4 = await Page.findOne({ path: '/normalize_10/normalize_11_gA/normalize_11_gB' });
+      const page5 = await Page.findOne({ path: '/normalize_10/normalize_12_gC' });
+      expectAllToBeTruthy([page1, page2, page3, page4, page5]);
+
+      await normalizeParentRecursivelyByPages([page3], testUser1);
+
+      // AM => After Migration
+      const page1AM = await Page.findOne({ path: '/normalize_10' });
+      const page2AM = await Page.findOne({ path: '/normalize_10/normalize_11_gA', _id: pageId8 });
+      const page3AM = await Page.findOne({ path: '/normalize_10/normalize_11_gA', _id: pageId9 });
+      const page4AM = await Page.findOne({ path: '/normalize_10/normalize_11_gA/normalize_11_gB' });
+      const page5AM = await Page.findOne({ path: '/normalize_10/normalize_12_gC' });
+      expectAllToBeTruthy([page1AM, page3AM, page4AM, page5AM]);
+      expect(page2AM).toBeNull();
+
+      expect(page1AM.isEmpty).toBeTruthy();
+      expect(page3AM.parent).toStrictEqual(page1AM._id);
+      // Todo: enable the code below after this is solved: https://redmine.weseek.co.jp/issues/90060
+      // expect(page4AM.parent).toStrictEqual(page3AF._id);
+      expect(page5AM.parent).toStrictEqual(page1AM._id);
+
+      expect(page3AM.isEmpty).toBeFalsy();
+    });
   });
 
   describe('normalizeAllPublicPages()', () => {
@@ -174,6 +406,57 @@ describe('V5 page migration', () => {
     });
   });
 
+  describe('normalizeParentByPageId()', () => {
+    const normalizeParentByPageId = async(page, user) => {
+      return crowi.pageService.normalizeParentByPageId(page, user);
+    };
+    test('it should normalize not v5 page with usergroup that has parent group', async() => {
+      const page1 = await Page.findOne({ _id: pageId1, path: '/normalize_1', isEmpty: true });
+      const page2 = await Page.findOne({ _id: pageId2, path: '/normalize_1/normalize_2', parent: page1._id });
+      const page3 = await Page.findOne({ _id: pageId3, path: '/normalize_1' }); // NOT v5
+      expectAllToBeTruthy([page1, page2, page3]);
+
+      await normalizeParentByPageId(page3, testUser1);
+
+      // AM => After Migration
+      const page1AM = await Page.findOne({ _id: pageId1, path: '/normalize_1', isEmpty: true });
+      const page2AM = await Page.findOne({ _id: pageId2, path: '/normalize_1/normalize_2' });
+      const page3AM = await Page.findOne({ _id: pageId3, path: '/normalize_1' }); // v5 compatible
+      expectAllToBeTruthy([page2AM, page3AM]);
+      expect(page1AM).toBeNull();
+
+      expect(page2AM.parent).toStrictEqual(page3AM._id);
+      expect(page3AM.parent).toStrictEqual(rootPage._id);
+    });
+
+    test('should throw error if a page with isolated group becomes the parent of other page with different gourp after normalizing', async() => {
+      const page4 = await Page.findOne({ _id: pageId4, path: '/normalize_4', isEmpty: true });
+      const page5 = await Page.findOne({ _id: pageId5, path: '/normalize_4/normalize_5', parent: page4._id });
+      const page6 = await Page.findOne({ _id: pageId6, path: '/normalize_4' }); // NOT v5
+
+      expectAllToBeTruthy([page4, page5, page6]);
+
+      let isThrown;
+      try {
+        await normalizeParentByPageId(page6, testUser1);
+      }
+      catch (err) {
+        isThrown = true;
+      }
+
+      // AM => After Migration
+      const page4AM = await Page.findOne({ _id: pageId4, path: '/normalize_4', isEmpty: true });
+      const page5AM = await Page.findOne({ _id: pageId5, path: '/normalize_4/normalize_5', parent: page4._id });
+      const page6AM = await Page.findOne({ _id: pageId6, path: '/normalize_4' }); // NOT v5
+      expectAllToBeTruthy([page4AM, page5AM, page6AM]);
+
+      expect(isThrown).toBe(true);
+      expect(page4AM).toStrictEqual(page4);
+      expect(page5AM).toStrictEqual(page5);
+      expect(page6AM).toStrictEqual(page6);
+    });
+  });
+
   test('replace private parents with empty pages', async() => {
     const replacedPathPages = await Page.find({ path: '/publicA/privateB' }); // ex-private page
 

+ 111 - 0
packages/app/test/integration/service/v5.non-public-page.test.ts

@@ -0,0 +1,111 @@
+/* eslint-disable no-unused-vars */
+import { advanceTo } from 'jest-date-mock';
+
+import mongoose from 'mongoose';
+
+import { getInstance } from '../setup-crowi';
+
+describe('PageService page operations with non-public pages', () => {
+
+  let dummyUser1;
+  let dummyUser2;
+
+  let crowi;
+  let Page;
+  let Revision;
+  let User;
+  let Tag;
+  let PageTagRelation;
+  let Bookmark;
+  let Comment;
+  let ShareLink;
+  let PageRedirect;
+  let xssSpy;
+
+  let rootPage;
+
+  // pass unless the data is one of [false, 0, '', null, undefined, NaN]
+  const expectAllToBeTruthy = (dataList) => {
+    dataList.forEach((data, i) => {
+      if (data == null) { console.log(`index: ${i}`) }
+      expect(data).toBeTruthy();
+    });
+  };
+
+  beforeAll(async() => {
+    crowi = await getInstance();
+    await crowi.configManager.updateConfigsInTheSameNamespace('crowi', { 'app:isV5Compatible': true });
+
+    User = mongoose.model('User');
+    Page = mongoose.model('Page');
+    Revision = mongoose.model('Revision');
+    Tag = mongoose.model('Tag');
+    PageTagRelation = mongoose.model('PageTagRelation');
+    Bookmark = mongoose.model('Bookmark');
+    Comment = mongoose.model('Comment');
+    ShareLink = mongoose.model('ShareLink');
+    PageRedirect = mongoose.model('PageRedirect');
+
+    /*
+     * Common
+     */
+
+    dummyUser1 = await User.findOne({ username: 'v5DummyUser1' });
+    dummyUser2 = await User.findOne({ username: 'v5DummyUser2' });
+
+    xssSpy = jest.spyOn(crowi.xss, 'process').mockImplementation(path => path);
+
+    rootPage = await Page.findOne({ path: '/' });
+    if (rootPage == null) {
+      const pages = await Page.insertMany([{ path: '/', grant: Page.GRANT_PUBLIC }]);
+      rootPage = pages[0];
+    }
+
+    /*
+     * Rename
+     */
+
+    /*
+     * Duplicate
+     */
+
+    /**
+     * Delete
+     */
+
+    /**
+     * Delete completely
+     */
+
+    /**
+     * Revert
+     */
+  });
+
+  describe('Rename', () => {
+    test('dummy test to avoid test failure', async() => {
+      // write test code
+      expect(true).toBe(true);
+    });
+  });
+  describe('Duplicate', () => {
+    // test('', async() => {
+    //   // write test code
+    // });
+  });
+  describe('Delete', () => {
+    // test('', async() => {
+    //   // write test code
+    // });
+  });
+  describe('Delete completely', () => {
+    // test('', async() => {
+    //   // write test code
+    // });
+  });
+  describe('revert', () => {
+    // test('', async() => {
+    //   // write test code
+    // });
+  });
+});

+ 1 - 10
packages/app/test/integration/service/v5.page.test.ts → packages/app/test/integration/service/v5.public-page.test.ts

@@ -24,8 +24,6 @@ describe('PageService page operations with only public pages', () => {
 
   let rootPage;
 
-  /* eslint jest/expect-expect: ["error", { "assertFunctionNames": ["expectAllToBeTruthy"] }] */
-  // https://github.com/jest-community/eslint-plugin-jest/blob/v24.3.5/docs/rules/expect-expect.md#assertfunctionnames
 
   // pass unless the data is one of [false, 0, '', null, undefined, NaN]
   const expectAllToBeTruthy = (dataList) => {
@@ -901,7 +899,7 @@ describe('PageService page operations with only public pages', () => {
   describe('Rename', () => {
 
     const renamePage = async(page, newPagePath, user, options) => {
-    // mock return value
+      // mock return value
       const mockedRenameSubOperation = jest.spyOn(crowi.pageService, 'renameSubOperation').mockReturnValue(null);
       const mockedCreateAndSendNotifications = jest.spyOn(crowi.pageService, 'createAndSendNotifications').mockReturnValue(null);
       const renamedPage = await crowi.pageService.renamePage(page, newPagePath, user, options);
@@ -1166,7 +1164,6 @@ describe('PageService page operations with only public pages', () => {
       expect(renamedPageGrandchild.isEmpty).toBe(false);
     });
   });
-
   describe('Duplicate', () => {
 
     const duplicate = async(page, newPagePath, user, isRecursively) => {
@@ -1444,7 +1441,6 @@ describe('PageService page operations with only public pages', () => {
       expect(deletedTagRelation2.isPageTrashed).toBe(true);
     });
   });
-
   describe('Delete completely', () => {
     const deleteCompletely = async(page, user, options = {}, isRecursively = false, preventEmitting = false) => {
       const mockedDeleteCompletelyRecursivelyMainOperation = jest.spyOn(crowi.pageService, 'deleteCompletelyRecursivelyMainOperation').mockReturnValue(null);
@@ -1562,8 +1558,6 @@ describe('PageService page operations with only public pages', () => {
 
     });
   });
-
-
   describe('revert', () => {
     const revertDeletedPage = async(page, user, options = {}, isRecursively = false) => {
       // mock return value
@@ -1624,6 +1618,3 @@ describe('PageService page operations with only public pages', () => {
   });
 
 });
-describe('PageService page operations with non-public pages', () => {
-  // TODO: write test code
-});