Преглед изворни кода

Merge branch 'master' into imprv/148445-upgrade-remark-growi-directive

reiji-h пре 1 година
родитељ
комит
c5fb9114d4
35 измењених фајлова са 581 додато и 1441 уклоњено
  1. 63 0
      .github/mergify.yml
  2. 14 22
      .github/workflows/ci-app-prod.yml
  3. 1 146
      .github/workflows/reusable-app-prod.yml
  4. 0 24
      .mergify.yml
  5. 0 2
      apps/app/.gitignore
  6. 0 30
      apps/app/cypress.config.ts
  7. 0 3
      apps/app/package.json
  8. 12 11
      apps/app/playwright/21-basic-features-for-guest/access-to-page.spec.ts
  9. 170 0
      apps/app/playwright/50-sidebar/access-to-sidebar.spec.ts
  10. 43 0
      apps/app/playwright/50-sidebar/switching-sidebar-mode.spec.ts
  11. 0 2
      apps/app/playwright/utils/CollapseSidebar.ts
  12. 1 1
      apps/app/regconfig.json
  13. 1 1
      apps/app/src/client/components/PageEditor/PageEditor.tsx
  14. 16 11
      apps/app/src/client/components/PageSelectModal/PageSelectModal.tsx
  15. 1 1
      apps/app/src/client/components/Sidebar/Tag.tsx
  16. 2 2
      apps/app/src/server/models/user-group-relation.ts
  17. 3 1
      apps/app/src/server/routes/apiv3/page/update-page.ts
  18. 1 1
      apps/app/src/server/util/compare-objectId.ts
  19. 18 4
      apps/app/src/stores/ui.tsx
  20. 0 8
      apps/app/test/cypress/.eslintrc.js
  21. 0 102
      apps/app/test/cypress/e2e/0-advanced-examples/misc.cy.ts
  22. 0 59
      apps/app/test/cypress/e2e/0-advanced-examples/viewport.cy.ts
  23. 0 18
      apps/app/test/cypress/e2e/21-basic-features-for-guest/21-basic-features-for-guest--access-to-page.cy.ts
  24. 0 303
      apps/app/test/cypress/e2e/50-sidebar/50-sidebar--access-to-side-bar.cy.ts
  25. 0 65
      apps/app/test/cypress/e2e/50-sidebar/50-sidebar--switching-sidebar-mode.cy.ts
  26. 0 6
      apps/app/test/cypress/fixtures/user-admin.json
  27. 0 21
      apps/app/test/cypress/support/assertions.ts
  28. 0 14
      apps/app/test/cypress/support/blackout.ts
  29. 0 118
      apps/app/test/cypress/support/commands.ts
  30. 0 48
      apps/app/test/cypress/support/index.ts
  31. 0 9
      apps/app/test/cypress/support/screenshot.ts
  32. 0 16
      apps/app/test/cypress/tsconfig.json
  33. 0 53
      bin/github-actions/generate-cypress-spec-arg.mjs
  34. 0 2
      package.json
  35. 235 337
      yarn.lock

+ 63 - 0
.github/mergify.yml

@@ -0,0 +1,63 @@
+queue_rules:
+  - name: default
+    allow_inplace_checks: false
+    queue_conditions:
+      - '#approved-reviews-by >= 2'
+      - check-success = "check-title"
+      - check-success = "ci-slackbot-proxy-lint (20.x)"
+      - check-success = "ci-slackbot-proxy-launch-dev (20.x)"
+      - check-success = "ci-slackbot-proxy-launch-prod (20.x)"
+      - check-success = "ci-app-lint (20.x)"
+      - check-success = "ci-app-test (20.x)"
+      - check-success = "ci-app-launch-dev (20.x)"
+    merge_conditions:
+      - '#approved-reviews-by >= 2'
+      - check-success = "check-title"
+      - check-success = "ci-slackbot-proxy-lint (20.x)"
+      - check-success = "ci-slackbot-proxy-launch-dev (20.x)"
+      - check-success = "ci-slackbot-proxy-launch-prod (20.x)"
+      - check-success = "ci-app-lint (20.x)"
+      - check-success = "ci-app-test (20.x)"
+      - check-success = "ci-app-launch-dev (20.x)"
+      - check-success = "test-prod-node18 / launch-prod"
+      - check-success = "test-prod-node20 / launch-prod"
+
+pull_request_rules:
+  - name: Automatic queue to merge
+    conditions:
+      - '#approved-reviews-by >= 1'
+      - check-success = "check-title"
+      - check-success = "ci-slackbot-proxy-lint (20.x)"
+      - check-success = "ci-slackbot-proxy-launch-dev (20.x)"
+      - check-success = "ci-slackbot-proxy-launch-prod (20.x)"
+      - check-success = "ci-app-lint (20.x)"
+      - check-success = "ci-app-test (20.x)"
+      - check-success = "ci-app-launch-dev (20.x)"
+    actions:
+      queue:
+        method: merge
+
+  - name: Automatic merge for Dependabot pull requests
+    conditions:
+      - author = dependabot[bot]
+      - '#approved-reviews-by >= 1'
+      - check-success = "check-title"
+      - check-success = "ci-slackbot-proxy-lint (20.x)"
+      - check-success = "ci-slackbot-proxy-launch-dev (20.x)"
+      - check-success = "ci-slackbot-proxy-launch-prod (20.x)"
+      - check-success = "ci-app-lint (20.x)"
+      - check-success = "ci-app-test (20.x)"
+      - check-success = "ci-app-launch-dev (20.x)"
+      - check-success = "test-prod-node18 / launch-prod"
+      - check-success = "test-prod-node20 / launch-prod"
+    actions:
+      merge:
+        method: merge
+
+  - name: Automatic merge for Preparing next version
+    conditions:
+      - author = github-actions[bot]
+      - label = "type/prepare-next-version"
+    actions:
+      merge:
+        method: merge

+ 14 - 22
.github/workflows/ci-app-prod.yml

@@ -7,6 +7,7 @@ on:
       - dev/7.*.x
       - dev/6.*.x
     paths:
+      - .github/mergify.yml
       - .github/workflows/ci-app-prod.yml
       - .github/workflows/reusable-app-prod.yml
       - .github/workflows/reusable-app-reg-suit.yml
@@ -24,6 +25,7 @@ on:
       - dev/6.*.x
     types: [opened, reopened, synchronize]
     paths:
+      - .github/mergify.yml
       - .github/workflows/ci-app-prod.yml
       - .github/workflows/reusable-app-prod.yml
       - .github/workflows/reusable-app-reg-suit.yml
@@ -34,12 +36,6 @@ on:
       - apps/app/**
       - '!apps/app/docker/**'
       - packages/**
-  workflow_call:
-    inputs:
-      cypress-config-video:
-        description: 'Enable video when running Cypress test'
-        type: boolean
-        default: false
 
 concurrency:
   group: ${{ github.workflow }}-${{ github.ref }}
@@ -62,25 +58,21 @@ jobs:
     with:
       node-version: 20.x
       skip-e2e-test: ${{ contains( github.event.pull_request.labels.*.name, 'dependencies' ) }}
-      cypress-report-artifact-name-prefix: cypress-report-
-      cypress-config-video: ${{ inputs.cypress-config-video || false }}
     secrets:
       SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
 
+  # run-reg-suit-node20:
+  #   needs: [test-prod-node20]
 
-  run-reg-suit-node20:
-    needs: [test-prod-node20]
+  #   uses: weseek/growi/.github/workflows/reusable-app-reg-suit.yml@master
 
-    uses: weseek/growi/.github/workflows/reusable-app-reg-suit.yml@master
+  #   if: always()
 
-    if: always()
-
-    with:
-      node-version: 20.x
-      skip-reg-suit: ${{ contains( github.event.pull_request.labels.*.name, 'dependencies' ) }}
-      cypress-report-artifact-name-pattern: cypress-report-*
-    secrets:
-      REG_NOTIFY_GITHUB_PLUGIN_CLIENTID: ${{ secrets.REG_NOTIFY_GITHUB_PLUGIN_CLIENTID }}
-      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
-      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
-      SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
+  #   with:
+  #     node-version: 20.x
+  #     skip-reg-suit: ${{ contains( github.event.pull_request.labels.*.name, 'dependencies' ) }}
+  #   secrets:
+  #     REG_NOTIFY_GITHUB_PLUGIN_CLIENTID: ${{ secrets.REG_NOTIFY_GITHUB_PLUGIN_CLIENTID }}
+  #     AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
+  #     AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+  #     SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

+ 1 - 146
.github/workflows/reusable-app-prod.yml

@@ -8,11 +8,6 @@ on:
         type: string
       skip-e2e-test:
         type: boolean
-      cypress-report-artifact-name-prefix:
-        type: string
-      cypress-config-video:
-        type: boolean
-        default: false
     secrets:
       SLACK_WEBHOOK_URL:
         required: true
@@ -201,150 +196,10 @@ jobs:
         url: ${{ secrets.SLACK_WEBHOOK_URL }}
 
 
-
-  run-cypress:
-    needs: [build-prod]
-
-    if: ${{ !inputs.skip-e2e-test }}
-
-    runs-on: ubuntu-latest
-
-    strategy:
-      fail-fast: false
-      matrix:
-        # List string expressions that is comma separated ids of tests in "test/cypress/integration"
-        spec-group: ['21', '50']
-
-    services:
-      mongodb:
-        image: mongo:6.0
-        ports:
-        - 27017/tcp
-      elasticsearch:
-        image: docker.elastic.co/elasticsearch/elasticsearch:7.17.1
-        ports:
-        - 9200/tcp
-        env:
-          discovery.type: single-node
-
-    steps:
-    - uses: actions/checkout@v4
-
-    - name: Install fonts
-      run: sudo apt install fonts-noto
-
-    - uses: actions/setup-node@v4
-      with:
-        node-version: ${{ inputs.node-version }}
-        cache: 'yarn'
-        cache-dependency-path: '**/yarn.lock'
-
-    - name: Install turbo
-      run: |
-        yarn global add turbo
-
-    - name: Prune repositories
-      run: |
-        turbo prune @growi/app
-        rm -rf apps packages
-        mv out/* .
-
-    - name: Restore node_modules
-      uses: actions/cache/restore@v4
-      with:
-        path: |
-          **/node_modules
-        # saved key by build-prod
-        key: node_modules-app-build-prod-${{ runner.OS }}-node${{ inputs.node-version }}-${{ hashFiles('**/yarn.lock') }}
-        restore-keys: |
-          node_modules-app-build-prod-${{ runner.OS }}-node${{ inputs.node-version }}-
-
-    - name: Cache/Restore Cypress files
-      uses: actions/cache@v4
-      with:
-        path: |
-          ~/.cache/Cypress
-        key: deps-for-cypress-${{ runner.OS }}-node${{ inputs.node-version }}-${{ hashFiles('**/yarn.lock') }}
-        restore-keys: |
-          deps-for-cypress-${{ runner.OS }}-node${{ inputs.node-version }}-
-
-    - name: Install dependencies
-      run: |
-        yarn global add node-gyp
-        yarn --frozen-lockfile
-        yarn cypress install
-
-    - name: Download production files artifact
-      uses: actions/download-artifact@v4
-      with:
-        name: Production Files (node${{ inputs.node-version }})
-
-    - name: Extract procution files artifact
-      run: |
-        tar -xf ${{ needs.build-prod.outputs.PROD_FILES }}
-
-    - name: Determine spec expression
-      id: determine-spec-exp
-      run: |
-        SPEC=`node bin/github-actions/generate-cypress-spec-arg.mjs --prefix="test/cypress/e2e/" --suffix="-*/*.cy.{ts,tsx}" "${{ matrix.spec-group }}"`
-        echo "value=$SPEC" >> $GITHUB_OUTPUT
-
-    - name: Copy dotenv file for ci
-      working-directory: ./apps/app
-      run: |
-        cat config/ci/.env.local.for-ci >> .env.production.local
-
-    - name: Copy dotenv file for automatic installation
-      if: ${{ matrix.spec-group != '10' }}
-      working-directory: ./apps/app
-      run: |
-        cat config/ci/.env.local.for-auto-install >> .env.production.local
-
-    - name: Copy dotenv file for automatic installation with allowing guest mode
-      if: ${{ matrix.spec-group == '21' }}
-      working-directory: ./apps/app
-      run: |
-        cat config/ci/.env.local.for-auto-install-with-allowing-guest >> .env.production.local
-
-    - name: Cypress Run
-      uses: cypress-io/github-action@v6
-      with:
-        browser: chromium
-        working-directory: ./apps/app
-        spec: '${{ steps.determine-spec-exp.outputs.value }}'
-        install: false
-        start: yarn server
-        wait-on: 'http://localhost:3000'
-        config: video=${{ inputs.cypress-config-video }}
-      env:
-        MONGO_URI: mongodb://localhost:${{ job.services.mongodb.ports['27017'] }}/growi-vrt
-        ELASTICSEARCH_URI: http://localhost:${{ job.services.elasticsearch.ports['9200'] }}/growi
-
-    - name: Upload results
-      if: always()
-      uses: actions/upload-artifact@v4
-      with:
-        name: ${{ inputs.cypress-report-artifact-name-prefix }}${{ matrix.spec-group }}
-        path: |
-          apps/app/test/cypress/screenshots
-          apps/app/test/cypress/videos
-
-    - name: Slack Notification
-      uses: weseek/ghaction-slack-notification@master
-      if: failure()
-      with:
-        type: ${{ job.status }}
-        job_name: '*Node CI for growi - run-cypress (${{ inputs.node-version }})*'
-        channel: '#ci'
-        isCompactMode: true
-        url: ${{ secrets.SLACK_WEBHOOK_URL }}
-
-
-
   run-playwright:
     needs: [build-prod]
 
-    if: ${{ !inputs.skip-e2e-test }}
+    if: ${{ !inputs.skip-e2e-test && startsWith(github.head_ref, 'mergify/merge-queue/') }}
 
     runs-on: ubuntu-latest
     container:

+ 0 - 24
.mergify.yml

@@ -1,24 +0,0 @@
-pull_request_rules:
-  - name: Automatic merge for Dependabot pull requests
-    conditions:
-      - author = dependabot[bot]
-      - '#approved-reviews-by >= 1'
-      - check-success = "ci-slackbot-proxy-lint (20.x)"
-      - check-success = "ci-slackbot-proxy-launch-dev (20.x)"
-      - check-success = "ci-slackbot-proxy-launch-prod (20.x)"
-      - check-success = "ci-app-lint (20.x)"
-      - check-success = "ci-app-test (20.x)"
-      - check-success = "ci-app-launch-dev (20.x)"
-      - check-success = "test-prod-node18 / launch-prod"
-      - check-success = "test-prod-node20 / launch-prod"
-    actions:
-      merge:
-        method: merge
-
-  - name: Automatic merge for Preparing next version
-    conditions:
-      - author = github-actions[bot]
-      - label = "type/prepare-next-version"
-    actions:
-      merge:
-        method: merge

+ 0 - 2
apps/app/.gitignore

@@ -3,8 +3,6 @@
 /out/
 
 # test
-test/cypress/screenshots
-test/cypress/videos
 .reg
 
 # dist

+ 0 - 30
apps/app/cypress.config.ts

@@ -1,30 +0,0 @@
-import { defineConfig } from 'cypress';
-
-export default defineConfig({
-  e2e: {
-    baseUrl: 'http://localhost:3000',
-    specPattern: 'test/cypress/e2e/**/*.cy.{ts,tsx}',
-    supportFile: 'test/cypress/support/index.ts',
-    setupNodeEvents: (on) => {
-      // change screen size
-      // see: https://docs.cypress.io/api/plugins/browser-launch-api#Set-screen-size-when-running-headless
-      on('before:browser:launch', (browser, launchOptions) => {
-        if (browser.name === 'chromium' && browser.isHeadless) {
-          launchOptions.args.push('--window-size=1400,1024');
-          launchOptions.args.push('--force-device-scale-factor=1');
-        }
-        return launchOptions;
-      });
-    },
-    defaultCommandTimeout: 7000,
-  },
-  fileServerFolder: 'test/cypress',
-  fixturesFolder: 'test/cypress/fixtures',
-  screenshotsFolder: 'test/cypress/screenshots',
-  videosFolder: 'test/cypress/videos',
-  video: false,
-
-  viewportWidth: 1400,
-  viewportHeight: 1024,
-
-});

+ 0 - 3
apps/app/package.json

@@ -25,7 +25,6 @@
     "dev:migrate:status": "yarn dev:migrate-mongo status -f config/migrate-mongo-config.js",
     "dev:migrate:up": "yarn dev:migrate-mongo up -f config/migrate-mongo-config.js",
     "dev:migrate:down": "yarn dev:migrate-mongo down -f config/migrate-mongo-config.js",
-    "cy:run": "cypress run --browser chromium",
     "//// for CI": "",
     "dev:ci": "yarn cross-env NODE_ENV=development yarn ts-node src/server/app.ts --ci",
     "lint:typecheck": "npx -y tspc",
@@ -243,11 +242,9 @@
     "babel-loader": "^8.2.5",
     "bootstrap": "=5.3.2",
     "connect-browser-sync": "^2.1.0",
-    "cypress-real-events": "^1.12.0",
     "diff2html": "^3.4.47",
     "downshift": "^8.2.3",
     "eazy-logger": "^3.1.0",
-    "eslint-plugin-cypress": "^2.12.1",
     "eslint-plugin-jest": "^26.5.3",
     "eslint-plugin-regex": "^1.8.0",
     "fslightbox-react": "^1.7.6",

+ 12 - 11
apps/app/playwright/21-basic-features-for-guest/access-to-page.spec.ts

@@ -32,14 +32,15 @@ test('Access to /trash page', async({ page }) => {
   await expect(page.getByTestId('trash-page-list')).toBeVisible();
 });
 
-// TODO: Improve collapseSidebar (https://redmine.weseek.co.jp/issues/148538)
-// test('Access to /tags page', async({ page }) => {
-//   await page.goto('/tags');
-
-//   await collapseSidebar(page, false);
-//   await page.getByTestId('grw-sidebar-nav-primary-tags').click();
-//   await expect(page.getByTestId('grw-sidebar-content-tags')).toBeVisible();
-//   await expect(page.getByTestId('grw-tags-list').first()).toBeVisible();
-//   await expect(page.getByTestId('grw-tags-list').first()).toContainText('You have no tag, You can set tags on pages');
-//   await expect(page.getByTestId('tags-page')).toBeVisible();
-// });
+test('Access to /tags page', async({ page }) => {
+  await page.goto('/');
+
+  await collapseSidebar(page, false);
+  await page.getByTestId('grw-sidebar-nav-primary-tags').click();
+  await expect(page.getByTestId('grw-sidebar-content-tags')).toBeVisible();
+  await expect(page.getByTestId('grw-tags-list').first()).toBeVisible();
+  await expect(page.getByTestId('grw-tags-list').first()).toContainText('You have no tag, You can set tags on pages');
+
+  await page.getByTestId('check-all-tags-button').click();
+  await expect(page.getByTestId('tags-page')).toBeVisible();
+});

+ 170 - 0
apps/app/playwright/50-sidebar/access-to-sidebar.spec.ts

@@ -0,0 +1,170 @@
+import { test, expect } from '@playwright/test';
+
+import { collapseSidebar } from '../utils';
+
+
+test.describe('Access to sidebar', () => {
+
+  test.beforeEach(async({ page }) => {
+    await page.goto('/');
+    await collapseSidebar(page, false);
+  });
+
+  test('Successfully show sidebar', async({ page }) => {
+    await expect(page.getByTestId('grw-sidebar-contents')).toBeVisible();
+  });
+
+  test('Successfully access to page tree', async({ page }) => {
+    await page.getByTestId('grw-sidebar-nav-primary-page-tree').click();
+    await expect(page.getByTestId('grw-sidebar-contents')).toBeVisible();
+    await expect(page.getByTestId('grw-pagetree-item-container').first()).toBeVisible();
+  });
+
+  test('Successfully access to recent changes', async({ page }) => {
+    await page.getByTestId('grw-sidebar-nav-primary-recent-changes').click();
+    await expect(page.getByTestId('grw-recent-changes')).toBeVisible();
+    await expect(page.locator('.list-group-item').first()).toBeVisible();
+  });
+
+  test('Successfully access to custom sidebar', async({ page }) => {
+    await page.getByTestId('grw-sidebar-nav-primary-custom-sidebar').click();
+    await expect(page.getByTestId('grw-sidebar-contents')).toBeVisible();
+    await expect(page.locator('.grw-sidebar-content-header > h3').locator('a')).toBeVisible();
+  });
+
+  test('Successfully access to GROWI Docs page', async({ page }) => {
+    const linkElement = page.locator('.grw-sidebar-nav-secondary-container a[href*="https://docs.growi.org"]');
+    const docsUrl = await linkElement.getAttribute('href');
+    if (docsUrl == null) {
+      throw new Error('url is null');
+    }
+    const response = await page.request.get(docsUrl);
+    const body = await response.text();
+    expect(body).toContain('</html>');
+  });
+
+  test('Successfully access to trash page', async({ page }) => {
+    await page.locator('.grw-sidebar-nav-secondary-container a[href*="/trash"]').click();
+    await expect(page.getByTestId('trash-page-list')).toBeVisible();
+  });
+
+
+  //
+  // Deactivate: An error occurs that cannot be reproduced in the development environment. -- Yuki Takei 2024.05.10
+  //
+
+  // it('Successfully click Add to Bookmarks button', () => {
+  //   cy.waitUntil(() => {
+  //     // do
+  //     cy.getByTestid('grw-sidebar-contents').within(() => {
+  //       cy.getByTestid('grw-pagetree-item-container').eq(1).within(() => { // against the second element
+  //         cy.get('li').realHover();
+  //         cy.getByTestid('open-page-item-control-btn').find('button').first().realClick();
+  //       });
+  //     });
+  //     // wait until
+  //     return cy.get('.dropdown-menu.show').then($elem => $elem.is(':visible'));
+  //   });
+
+  //   cy.get('.dropdown-menu.show').should('be.visible').within(() => {
+  //     // take a screenshot for dropdown menu
+  //     cy.screenshot(`${ssPrefix}page-tree-2-before-adding-bookmark`)
+  //     // click add remove bookmark btn
+  //     cy.getByTestid('add-bookmark-btn').click();
+  //   })
+
+  //   // show dropdown again
+  //   cy.waitUntil(() => {
+  //     // do
+  //     cy.getByTestid('grw-sidebar-contents').within(() => {
+  //       cy.getByTestid('grw-pagetree-item-container').eq(1).within(() => { // against the second element
+  //         cy.get('li').realHover();
+  //         cy.getByTestid('open-page-item-control-btn').find('button').first().realClick();
+  //       });
+  //     });
+  //     // wait until
+  //     return cy.get('.dropdown-menu.show').then($elem => $elem.is(':visible'));
+  //   });
+
+  //   cy.get('.dropdown-menu.show').should('be.visible').within(() => {
+  //     // expect to be visible
+  //     cy.getByTestid('remove-bookmark-btn').should('be.visible');
+  //     // take a screenshot for dropdown menu
+  //     cy.screenshot(`${ssPrefix}page-tree-2-after-adding-bookmark`);
+  //   });
+  // });
+
+  // it('Successfully show duplicate page modal', () => {
+  //   cy.waitUntil(() => {
+  //     // do
+  //     cy.getByTestid('grw-sidebar-contents').within(() => {
+  //       cy.getByTestid('grw-pagetree-item-container').eq(1).within(() => { // against the second element
+  //         cy.get('li').realHover();
+  //         cy.getByTestid('open-page-item-control-btn').find('button').first().realClick();
+  //       });
+  //     });
+  //     // wait until
+  //     return cy.get('.dropdown-menu.show').then($elem => $elem.is(':visible'));
+  //   });
+
+  //   cy.get('.dropdown-menu.show').should('be.visible').within(() => {
+  //     cy.getByTestid('open-page-duplicate-modal-btn').click();
+  //   })
+
+  //   cy.getByTestid('page-duplicate-modal').should('be.visible').within(() => {
+  //     cy.get('.form-control').type('_test');
+
+  //     cy.screenshot(`${ssPrefix}page-tree-5-duplicate-page-modal`, { blackout: blackoutOverride });
+
+  //     cy.get('.modal-header > button').click();
+  //   });
+  // });
+
+  // it('Successfully rename page', () => {
+  //   cy.waitUntil(() => {
+  //     // do
+  //     cy.getByTestid('grw-sidebar-contents').within(() => {
+  //       cy.getByTestid('grw-pagetree-item-container').eq(1).within(() => { // against the second element
+  //         cy.get('li').realHover();
+  //         cy.getByTestid('open-page-item-control-btn').find('button').first().realClick();
+  //       });
+  //     });
+  //     // wait until
+  //     return cy.get('.dropdown-menu.show').then($elem => $elem.is(':visible'));
+  //   });
+
+  //   cy.get('.dropdown-menu.show').should('be.visible').within(() => {
+  //     cy.getByTestid('rename-page-btn').click();
+  //   })
+
+  //   cy.getByTestid('grw-sidebar-contents').within(() => {
+  //     cy.getByTestid('autosize-submittable-input').type('_newname');
+  //   })
+
+  //   cy.screenshot(`${ssPrefix}page-tree-6-rename-page`, { blackout: blackoutOverride });
+  // });
+
+  // it('Successfully show delete page modal', () => {
+  //   cy.waitUntil(() => {
+  //     // do
+  //     cy.getByTestid('grw-sidebar-contents').within(() => {
+  //       cy.getByTestid('grw-pagetree-item-container').eq(1).within(() => { // against the second element
+  //         cy.get('li').realHover();
+  //         cy.getByTestid('open-page-item-control-btn').find('button').first().realClick();
+  //       });
+  //     });
+  //     // wait until
+  //     return cy.get('.dropdown-menu.show').then($elem => $elem.is(':visible'));
+  //   });
+
+  //   cy.get('.dropdown-menu.show').should('be.visible').within(() => {
+  //     cy.getByTestid('open-page-delete-modal-btn').click();
+  //   })
+
+  //   cy.getByTestid('page-delete-modal').should('be.visible').within(() => {
+  //     cy.screenshot(`${ssPrefix}page-tree-7-delete-page-modal`, { blackout: blackoutOverride });
+  //     cy.get('.modal-header > button').click();
+  //   });
+  // });
+
+});

+ 43 - 0
apps/app/playwright/50-sidebar/switching-sidebar-mode.spec.ts

@@ -0,0 +1,43 @@
+import { test } from '@playwright/test';
+
+import { collapseSidebar } from '../utils';
+
+
+test('Switch sidebar mode', async({ page }) => {
+  await page.goto('/');
+  await collapseSidebar(page, false);
+  await collapseSidebar(page, true);
+});
+
+// Write tests using VRT
+// context('Switch viewport size', () => {
+//   const ssPrefix = 'switch-viewport-size-';
+
+//   const sizes = {
+//     'xl': [1200, 1024],
+//     'lg': [992, 1024],
+//     'md': [768, 1024],
+//     'sm': [576, 1024],
+//     'xs': [575, 1024],
+//     'iphone-x': [375, 812],
+//   };
+
+//   Object.entries(sizes).forEach(([screenLabel, size]) => {
+//     it(`on ${screenLabel} screen`, () => {
+//       cy.viewport(size[0], size[1]);
+
+//       // login
+//       cy.fixture("user-admin.json").then(user => {
+//         cy.login(user.username, user.password);
+//       });
+//       cy.visit('/');
+
+//       cy.get('.layout-root').should('be.visible');
+
+//       cy.screenshot(`${ssPrefix}-${screenLabel}`, {
+//         blackout: blackoutOverride,
+//       });
+//     });
+//   });
+
+// });

+ 0 - 2
apps/app/playwright/utils/CollapseSidebar.ts

@@ -1,4 +1,3 @@
-// TODO: https://redmine.weseek.co.jp/issues/148538
 import { expect, type Page } from '@playwright/test';
 
 export const collapseSidebar = async(page: Page, isCollapsed: boolean): Promise<void> => {
@@ -9,7 +8,6 @@ export const collapseSidebar = async(page: Page, isCollapsed: boolean): Promise<
 
   const collapseSidebarToggle = page.getByTestId('btn-toggle-collapse');
   await expect(collapseSidebarToggle).toBeVisible();
-
   await collapseSidebarToggle.click();
 
   if (isCollapsed) {

+ 1 - 1
apps/app/regconfig.json

@@ -1,7 +1,7 @@
 {
   "core": {
     "workingDir": ".reg",
-    "actualDir": "test/cypress/screenshots",
+    "actualDir": "test/playwright/screenshots",
     "thresholdRate": 0.001,
     "addIgnore": true,
     "ximgdiff": {

+ 1 - 1
apps/app/src/client/components/PageEditor/PageEditor.tsx

@@ -211,7 +211,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
     finally {
       mutateWaitingSaveProcessing(false);
     }
-  }, [pageId, selectedGrant, mutateWaitingSaveProcessing, t, mutateIsGrantNormalized]);
+  }, [pageId, selectedGrant, mutateWaitingSaveProcessing, updatePage, mutateIsGrantNormalized, t]);
 
   const saveAndReturnToViewHandler = useCallback(async(opts: SaveOptions) => {
     const markdown = codeMirrorEditor?.getDoc();

+ 16 - 11
apps/app/src/client/components/PageSelectModal/PageSelectModal.tsx

@@ -10,6 +10,7 @@ import { useTranslation } from 'next-i18next';
 import {
   Modal, ModalHeader, ModalBody, ModalFooter, Button,
 } from 'reactstrap';
+import SimpleBar from 'simplebar-react';
 
 import type { IPageForItem } from '~/interfaces/page';
 import { useTargetAndAncestors, useIsGuestUser, useIsReadOnlyUser } from '~/stores-universal/context';
@@ -22,6 +23,7 @@ import { usePagePathRenameHandler } from '../PageEditor/page-path-rename-utils';
 
 import { TreeItemForModal } from './TreeItemForModal';
 
+
 export const PageSelectModal: FC = () => {
   const {
     data: PageSelectModalData,
@@ -82,20 +84,23 @@ export const PageSelectModal: FC = () => {
       isOpen={isOpened}
       toggle={closeModal}
       centered
-      size="sm"
     >
       <ModalHeader toggle={closeModal}>{t('page_select_modal.select_page_location')}</ModalHeader>
-      <ModalBody>
+      <ModalBody className="p-0">
         <Suspense fallback={<ItemsTreeContentSkeleton />}>
-          <ItemsTree
-            CustomTreeItem={TreeItemForModal}
-            isEnableActions={!isGuestUser}
-            isReadOnlyUser={!!isReadOnlyUser}
-            targetPath={targetPath}
-            targetPathOrId={targetPathOrId}
-            targetAndAncestorsData={targetAndAncestorsData}
-            onClickTreeItem={onClickTreeItem}
-          />
+          <SimpleBar style={{ maxHeight: 'calc(85vh - 133px)' }}> {/* 133px = 63px(ModalHeader) + 70px(ModalFooter) */}
+            <div className="p-3">
+              <ItemsTree
+                CustomTreeItem={TreeItemForModal}
+                isEnableActions={!isGuestUser}
+                isReadOnlyUser={!!isReadOnlyUser}
+                targetPath={targetPath}
+                targetPathOrId={targetPathOrId}
+                targetAndAncestorsData={targetAndAncestorsData}
+                onClickTreeItem={onClickTreeItem}
+              />
+            </div>
+          </SimpleBar>
         </Suspense>
       </ModalBody>
       <ModalFooter>

+ 1 - 1
apps/app/src/client/components/Sidebar/Tag.tsx

@@ -68,7 +68,7 @@ const Tag: FC = () => {
         )
       }
 
-      <div className="d-flex justify-content-center my-5">
+      <div className="d-flex justify-content-center my-5" data-testid="check-all-tags-button">
         <Link
           href="/tags"
           className="btn btn-primary rounded px-4"

+ 2 - 2
apps/app/src/server/models/user-group-relation.ts

@@ -128,7 +128,7 @@ schema.statics.findAllRelationForUserGroups = function(userGroups) {
  * @memberof UserGroupRelation
  */
 schema.statics.findAllGroupsForUser = async function(user): Promise<UserGroupDocument[]> {
-  const userGroupRelations = await this.find({ relatedUser: user.id }).populate('relatedGroup');
+  const userGroupRelations = await this.find({ relatedUser: user._id }).populate('relatedGroup');
   const userGroups = userGroupRelations.map((relation) => {
     return isPopulated(relation.relatedGroup) ? relation.relatedGroup as UserGroupDocument : null;
   });
@@ -161,7 +161,7 @@ schema.statics.findAllUserGroupIdsRelatedToUser = async function(user): Promise<
 schema.statics.countByGroupIdsAndUser = async function(userGroupIds: ObjectIdLike[], userData): Promise<number> {
   const query = {
     relatedGroup: { $in: userGroupIds },
-    relatedUser: userData.id,
+    relatedUser: userData._id,
   };
 
   return this.count(query);

+ 3 - 1
apps/app/src/server/routes/apiv3/page/update-page.ts

@@ -54,7 +54,9 @@ export const updatePageHandlersFactory: UpdatePageHandlersFactory = (crowi) => {
       .withMessage("'revisionId' must be specified"),
     body('body').exists().isString()
       .withMessage("Empty value is not allowed for 'body'"),
-    body('grant').optional().isInt({ min: 0, max: 5 }).withMessage('grant must be integer from 1 to 5'),
+    body('grant').optional().not().isString()
+      .isInt({ min: 0, max: 5 })
+      .withMessage('grant must be an integer from 1 to 5'),
     body('userRelatedGrantUserGroupIds').optional().isArray().withMessage('userRelatedGrantUserGroupIds must be an array of group id'),
     body('overwriteScopesOfDescendants').optional().isBoolean().withMessage('overwriteScopesOfDescendants must be boolean'),
     body('isSlackEnabled').optional().isBoolean().withMessage('isSlackEnabled must be boolean'),

+ 1 - 1
apps/app/src/server/util/compare-objectId.ts

@@ -1,6 +1,6 @@
 import mongoose from 'mongoose';
 
-import { ObjectIdLike } from '~/server/interfaces/mongoose-utils';
+import type { ObjectIdLike } from '~/server/interfaces/mongoose-utils';
 
 type IObjectId = mongoose.Types.ObjectId;
 const ObjectId = mongoose.Types.ObjectId;

+ 18 - 4
apps/app/src/stores/ui.tsx

@@ -337,11 +337,25 @@ export const useCommentEditorDirtyMap = (): SWRResponse<Map<string, boolean>, Er
  *********************************************************** */
 
 export const useIsAbleToShowTrashPageManagementButtons = (): SWRResponse<boolean, Error> => {
-  const { data: currentUser } = useCurrentUser();
-  const { data: isReadOnlyUser } = useIsReadOnlyUser();
-  const { data: isTrashPage } = useIsTrashPage();
+  const key = 'isAbleToShowTrashPageManagementButtons';
 
-  return useStaticSWR('isAbleToShowTrashPageManagementButtons', isTrashPage && currentUser != null && !isReadOnlyUser);
+  const { data: _currentUser } = useCurrentUser();
+  const isCurrentUserExist = _currentUser != null;
+
+  const { data: _currentPageId } = useCurrentPageId();
+  const { data: _isNotFound } = useIsNotFound();
+  const { data: _isTrashPage } = useIsTrashPage();
+  const { data: _isReadOnlyUser } = useIsReadOnlyUser();
+  const isPageExist = _currentPageId != null && _isNotFound === false;
+  const isTrashPage = isPageExist && _isTrashPage === true;
+  const isReadOnlyUser = isPageExist && _isReadOnlyUser === true;
+
+  const includesUndefined = [_currentUser, _currentPageId, _isNotFound, _isReadOnlyUser, _isTrashPage].some(v => v === undefined);
+
+  return useSWRImmutable(
+    includesUndefined ? null : [key, isTrashPage, isCurrentUserExist, isReadOnlyUser],
+    ([, isTrashPage, isCurrentUserExist, isReadOnlyUser]) => isTrashPage && isCurrentUserExist && !isReadOnlyUser,
+  );
 };
 
 export const useIsAbleToShowPageManagement = (): SWRResponse<boolean, Error> => {

+ 0 - 8
apps/app/test/cypress/.eslintrc.js

@@ -1,8 +0,0 @@
-module.exports = {
-  root: true,
-  extends: [
-    'weseek/typescript',
-    'plugin:cypress/recommended',
-  ],
-  plugins: ['@typescript-eslint', 'cypress'],
-};

+ 0 - 102
apps/app/test/cypress/e2e/0-advanced-examples/misc.cy.ts

@@ -1,102 +0,0 @@
-context('Misc', () => {
-  beforeEach(() => {
-    cy.visit('https://example.cypress.io/commands/misc')
-  })
-
-  it('.end() - end the command chain', () => {
-    // https://on.cypress.io/end
-
-    // cy.end is useful when you want to end a chain of commands
-    // and force Cypress to re-query from the root element
-    cy.get('.misc-table').within(() => {
-      // ends the current chain and yields null
-      cy.contains('Cheryl').click().end()
-
-      // queries the entire table again
-      cy.contains('Charles').click()
-    })
-  })
-
-  it('cy.exec() - execute a system command', () => {
-    // execute a system command.
-    // so you can take actions necessary for
-    // your test outside the scope of Cypress.
-    // https://on.cypress.io/exec
-
-    // we can use Cypress.platform string to
-    // select appropriate command
-    // https://on.cypress/io/platform
-    cy.log(`Platform ${Cypress.platform} architecture ${Cypress.arch}`)
-
-    // on CircleCI Windows build machines we have a failure to run bash shell
-    // https://github.com/cypress-io/cypress/issues/5169
-    // so skip some of the tests by passing flag "--env circle=true"
-    const isCircleOnWindows = Cypress.platform === 'win32' && Cypress.env('circle')
-
-    if (isCircleOnWindows) {
-      cy.log('Skipping test on CircleCI')
-
-      return
-    }
-
-    // cy.exec problem on Shippable CI
-    // https://github.com/cypress-io/cypress/issues/6718
-    const isShippable = Cypress.platform === 'linux' && Cypress.env('shippable')
-
-    if (isShippable) {
-      cy.log('Skipping test on ShippableCI')
-
-      return
-    }
-
-    cy.exec('echo Jane Lane')
-      .its('stdout').should('contain', 'Jane Lane')
-
-    if (Cypress.platform === 'win32') {
-      cy.exec('print cypress.json')
-        .its('stderr').should('be.empty')
-    } else {
-      cy.exec('cat cypress.json')
-        .its('stderr').should('be.empty')
-
-      cy.exec('pwd')
-        .its('code').should('eq', 0)
-    }
-  })
-
-  it('cy.focused() - get the DOM element that has focus', () => {
-    // https://on.cypress.io/focused
-    cy.get('.misc-form').find('#name').click()
-    cy.focused().should('have.id', 'name')
-
-    cy.get('.misc-form').find('#description').click()
-    cy.focused().should('have.id', 'description')
-  })
-
-  context('Cypress.Screenshot', function () {
-    it('cy.screenshot() - take a screenshot', () => {
-      // https://on.cypress.io/screenshot
-      cy.screenshot('my-image')
-    })
-
-    it('Cypress.Screenshot.defaults() - change default config of screenshots', function () {
-      Cypress.Screenshot.defaults({
-        blackout: ['.foo'],
-        capture: 'viewport',
-        clip: { x: 0, y: 0, width: 200, height: 200 },
-        scale: false,
-        disableTimersAndAnimations: true,
-        screenshotOnRunFailure: true,
-        onBeforeScreenshot () { },
-        onAfterScreenshot () { },
-      })
-    })
-  })
-
-  it('cy.wrap() - wrap an object', () => {
-    // https://on.cypress.io/wrap
-    cy.wrap({ foo: 'bar' })
-      .should('have.property', 'foo')
-      .and('include', 'bar')
-  })
-})

+ 0 - 59
apps/app/test/cypress/e2e/0-advanced-examples/viewport.cy.ts

@@ -1,59 +0,0 @@
-context('Viewport', () => {
-  beforeEach(() => {
-    cy.visit('https://example.cypress.io/commands/viewport')
-  })
-
-  it('cy.viewport() - set the viewport size and dimension', () => {
-    // https://on.cypress.io/viewport
-
-    cy.get('#navbar').should('be.visible')
-    cy.viewport(320, 480)
-
-    // the navbar should have collapse since our screen is smaller
-    cy.get('#navbar').should('not.be.visible')
-    cy.get('.navbar-toggle').should('be.visible').click()
-    cy.get('.nav').find('a').should('be.visible')
-
-    // lets see what our app looks like on a super large screen
-    cy.viewport(2999, 2999)
-
-    // cy.viewport() accepts a set of preset sizes
-    // to easily set the screen to a device's width and height
-
-    // We added a cy.wait() between each viewport change so you can see
-    // the change otherwise it is a little too fast to see :)
-
-    /* eslint-disable cypress/no-unnecessary-waiting */
-    cy.viewport('macbook-15')
-    cy.wait(200)
-    cy.viewport('macbook-13')
-    cy.wait(200)
-    cy.viewport('macbook-11')
-    cy.wait(200)
-    cy.viewport('ipad-2')
-    cy.wait(200)
-    cy.viewport('ipad-mini')
-    cy.wait(200)
-    cy.viewport('iphone-6+')
-    cy.wait(200)
-    cy.viewport('iphone-6')
-    cy.wait(200)
-    cy.viewport('iphone-5')
-    cy.wait(200)
-    cy.viewport('iphone-4')
-    cy.wait(200)
-    cy.viewport('iphone-3')
-    cy.wait(200)
-
-    // cy.viewport() accepts an orientation for all presets
-    // the default orientation is 'portrait'
-    cy.viewport('ipad-2', 'portrait')
-    cy.wait(200)
-    cy.viewport('iphone-4', 'landscape')
-    cy.wait(200)
-    /* eslint-enable cypress/no-unnecessary-waiting */
-
-    // The viewport will be reset back to the default dimensions
-    // in between tests (the  default can be set in cypress.json)
-  })
-})

+ 0 - 18
apps/app/test/cypress/e2e/21-basic-features-for-guest/21-basic-features-for-guest--access-to-page.cy.ts

@@ -1,18 +0,0 @@
-context('Access to special pages by guest', () => {
-  const ssPrefix = 'access-to-special-pages-by-guest-';
-  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('grw-tags-list').should('be.visible');
-    cy.getByTestid('grw-tags-list').contains('You have no tag, You can set tags on pages');
-
-    cy.getByTestid('tags-page').should('be.visible');
-    cy.screenshot(`${ssPrefix}-tags`);
-  });
-
-});

+ 0 - 303
apps/app/test/cypress/e2e/50-sidebar/50-sidebar--access-to-side-bar.cy.ts

@@ -1,303 +0,0 @@
-import { BlackoutGroup } from "../../support/blackout";
-
-// Blackout for recalculation of toc content hight
-const blackoutOverride = [
-  ...BlackoutGroup.BASIS,
-  ...BlackoutGroup.SIDE_CONTENTS,
-];
-
-describe('Access to sidebar', () => {
-  const ssPrefix = 'access-to-sidebar-';
-
-  context('when logged in', () => {
-    beforeEach(() => {
-      // login
-      cy.fixture("user-admin.json").then(user => {
-        cy.login(user.username, user.password);
-      });
-    });
-
-    context('when access to root page', { scrollBehavior: false }, () => {
-      beforeEach(() => {
-        cy.visit('/');
-
-        // Since this is a sidebar test, call collapseSidebar in beforeEach.
-        cy.collapseSidebar(false, true);
-      });
-
-      describe('Test show/collapse button', () => {
-        it('Successfully show sidebar', () => {
-          cy.getByTestid('grw-sidebar-contents').should('be.visible');
-
-          cy.waitUntilSkeletonDisappear();
-          cy.screenshot(`${ssPrefix}1-sidebar-shown`, {
-            capture: 'viewport',
-            blackout: blackoutOverride,
-          });
-        });
-
-        it('Successfully collapse sidebar', () => {
-          cy.getByTestid('btn-toggle-collapse').click({force: true});
-
-          cy.getByTestid('grw-sidebar-contents').should('not.be.visible');
-
-          cy.waitUntilSkeletonDisappear();
-          cy.screenshot(`${ssPrefix}2-sidebar-collapsed`, {
-            capture: 'viewport',
-            blackout: blackoutOverride,
-          });
-        });
-      });
-
-      describe('Test page tree tab', () => {
-        beforeEach(() => {
-          cy.getByTestid('grw-sidebar-nav-primary-page-tree').click();
-        });
-
-        it('Successfully access to page tree', () => {
-          cy.getByTestid('grw-sidebar-contents').within(() => {
-            cy.getByTestid('grw-pagetree-item-container').should('be.visible');
-
-            cy.waitUntilSkeletonDisappear();
-            cy.screenshot(`${ssPrefix}page-tree-1-access-to-page-tree`, { blackout: blackoutOverride });
-          });
-        });
-
-
-        //
-        // Deactivate: An error occurs that cannot be reproduced in the development environment. -- Yuki Takei 2024.05.10
-        //
-
-        // it('Successfully click Add to Bookmarks button', () => {
-        //   cy.waitUntil(() => {
-        //     // do
-        //     cy.getByTestid('grw-sidebar-contents').within(() => {
-        //       cy.getByTestid('grw-pagetree-item-container').eq(1).within(() => { // against the second element
-        //         cy.get('li').realHover();
-        //         cy.getByTestid('open-page-item-control-btn').find('button').first().realClick();
-        //       });
-        //     });
-        //     // wait until
-        //     return cy.get('.dropdown-menu.show').then($elem => $elem.is(':visible'));
-        //   });
-
-        //   cy.get('.dropdown-menu.show').should('be.visible').within(() => {
-        //     // take a screenshot for dropdown menu
-        //     cy.screenshot(`${ssPrefix}page-tree-2-before-adding-bookmark`)
-        //     // click add remove bookmark btn
-        //     cy.getByTestid('add-bookmark-btn').click();
-        //   })
-
-        //   // show dropdown again
-        //   cy.waitUntil(() => {
-        //     // do
-        //     cy.getByTestid('grw-sidebar-contents').within(() => {
-        //       cy.getByTestid('grw-pagetree-item-container').eq(1).within(() => { // against the second element
-        //         cy.get('li').realHover();
-        //         cy.getByTestid('open-page-item-control-btn').find('button').first().realClick();
-        //       });
-        //     });
-        //     // wait until
-        //     return cy.get('.dropdown-menu.show').then($elem => $elem.is(':visible'));
-        //   });
-
-        //   cy.get('.dropdown-menu.show').should('be.visible').within(() => {
-        //     // expect to be visible
-        //     cy.getByTestid('remove-bookmark-btn').should('be.visible');
-        //     // take a screenshot for dropdown menu
-        //     cy.screenshot(`${ssPrefix}page-tree-2-after-adding-bookmark`);
-        //   });
-        // });
-
-        // it('Successfully show duplicate page modal', () => {
-        //   cy.waitUntil(() => {
-        //     // do
-        //     cy.getByTestid('grw-sidebar-contents').within(() => {
-        //       cy.getByTestid('grw-pagetree-item-container').eq(1).within(() => { // against the second element
-        //         cy.get('li').realHover();
-        //         cy.getByTestid('open-page-item-control-btn').find('button').first().realClick();
-        //       });
-        //     });
-        //     // wait until
-        //     return cy.get('.dropdown-menu.show').then($elem => $elem.is(':visible'));
-        //   });
-
-        //   cy.get('.dropdown-menu.show').should('be.visible').within(() => {
-        //     cy.getByTestid('open-page-duplicate-modal-btn').click();
-        //   })
-
-        //   cy.getByTestid('page-duplicate-modal').should('be.visible').within(() => {
-        //     cy.get('.form-control').type('_test');
-
-        //     cy.screenshot(`${ssPrefix}page-tree-5-duplicate-page-modal`, { blackout: blackoutOverride });
-
-        //     cy.get('.modal-header > button').click();
-        //   });
-        // });
-
-        // it('Successfully rename page', () => {
-        //   cy.waitUntil(() => {
-        //     // do
-        //     cy.getByTestid('grw-sidebar-contents').within(() => {
-        //       cy.getByTestid('grw-pagetree-item-container').eq(1).within(() => { // against the second element
-        //         cy.get('li').realHover();
-        //         cy.getByTestid('open-page-item-control-btn').find('button').first().realClick();
-        //       });
-        //     });
-        //     // wait until
-        //     return cy.get('.dropdown-menu.show').then($elem => $elem.is(':visible'));
-        //   });
-
-        //   cy.get('.dropdown-menu.show').should('be.visible').within(() => {
-        //     cy.getByTestid('rename-page-btn').click();
-        //   })
-
-        //   cy.getByTestid('grw-sidebar-contents').within(() => {
-        //     cy.getByTestid('autosize-submittable-input').type('_newname');
-        //   })
-
-        //   cy.screenshot(`${ssPrefix}page-tree-6-rename-page`, { blackout: blackoutOverride });
-        // });
-
-        // it('Successfully show delete page modal', () => {
-        //   cy.waitUntil(() => {
-        //     // do
-        //     cy.getByTestid('grw-sidebar-contents').within(() => {
-        //       cy.getByTestid('grw-pagetree-item-container').eq(1).within(() => { // against the second element
-        //         cy.get('li').realHover();
-        //         cy.getByTestid('open-page-item-control-btn').find('button').first().realClick();
-        //       });
-        //     });
-        //     // wait until
-        //     return cy.get('.dropdown-menu.show').then($elem => $elem.is(':visible'));
-        //   });
-
-        //   cy.get('.dropdown-menu.show').should('be.visible').within(() => {
-        //     cy.getByTestid('open-page-delete-modal-btn').click();
-        //   })
-
-        //   cy.getByTestid('page-delete-modal').should('be.visible').within(() => {
-        //     cy.screenshot(`${ssPrefix}page-tree-7-delete-page-modal`, { blackout: blackoutOverride });
-        //     cy.get('.modal-header > button').click();
-        //   });
-        // });
-      });
-
-      describe('Test custom sidebar tab', () => {
-        beforeEach(() => {
-          cy.getByTestid('grw-sidebar-nav-primary-custom-sidebar').click();
-        });
-
-        it('Successfully access to custom sidebar', () => {
-          cy.getByTestid('grw-sidebar-contents').within(() => {
-            cy.get('.grw-sidebar-content-header > h4').find('a');
-
-            cy.waitUntilSkeletonDisappear();
-            cy.screenshot(`${ssPrefix}custom-sidebar-1-access-to-custom-sidebar`, { blackout: blackoutOverride });
-          });
-        });
-
-        // TODO: fix by https://redmine.weseek.co.jp/issues/138562
-        // it('Successfully redirect to editor', () => {
-        //   const content = '# HELLO \n ## Hello\n ### Hello';
-
-        //   cy.get('.grw-sidebar-content-header > h3 > a').should('be.visible').click();
-
-        //   cy.get('.layout-root').should('have.class', 'editing');
-        //   cy.get('.CodeMirror textarea').type(content, {force: true});
-
-        //   cy.screenshot(`${ssPrefix}custom-sidebar-2-redirect-to-editor`, { blackout: blackoutOverride });
-
-        //   cy.getByTestid('save-page-btn').click();
-        // });
-
-        // it('Successfully create custom sidebar content', () => {
-        //   cy.getByTestid('grw-sidebar-nav-primary-custom-sidebar')
-        //     .should('be.visible')
-        //     .should('have.class', 'active');
-
-        //   cy.waitUntilSkeletonDisappear();
-        //   cy.screenshot(`${ssPrefix}custom-sidebar-3-content-created`, { blackout: blackoutOverride });
-        // });
-      });
-
-      describe('Test recent changes tab', () => {
-        beforeEach(() => {
-          cy.getByTestid('grw-sidebar-nav-primary-recent-changes').click();
-        });
-
-        it('Successfully access to recent changes', () => {
-          cy.getByTestid('grw-recent-changes').should('be.visible');
-          cy.get('.list-group-item').should('be.visible');
-
-          // The scope of the screenshot is not narrowed because the blackout is shifted
-          cy.screenshot(`${ssPrefix}recent-changes-access-to-recent-changes`, { blackout: blackoutOverride });
-        });
-
-      });
-
-      //
-      // Deactivate: An error occurs that cannot be reproduced in the development environment. -- Yuki Takei 2024.05.10
-      //
-      // describe('Test tags tab', () => {
-      //   beforeEach(() => {
-      //     cy.getByTestid('grw-sidebar-nav-primary-tags').click();
-      //   });
-
-      //   it('Successfully access to tags', () => {
-      //     cy.getByTestid('grw-sidebar-contents').within(() => {
-      //       cy.getByTestid('grw-tags-list').should('be.visible');
-
-      //       cy.screenshot(`${ssPrefix}tags-1-access-to-tags`, { blackout: blackoutOverride });
-      //     });
-      //   });
-
-      //   it('Succesfully click all tags button', () => {
-      //     cy.getByTestid('grw-sidebar-content-tags').within(() => {
-      //       cy.get('.btn-primary').as('check-all-tags-button');
-      //       cy.get('@check-all-tags-button').should('be.visible');
-      //       cy.get('@check-all-tags-button').click({force: true});
-      //     });
-      //     cy.collapseSidebar(true);
-      //     cy.getByTestid('grw-tags-list').should('be.visible');
-
-      //     cy.screenshot(`${ssPrefix}tags-2-click-all-tags-button`, { blackout: blackoutOverride });
-      //   });
-      // });
-
-      // // TODO: No Drafts pages on GROWI version 6
-      // it('Successfully access to My Drafts page', () => {
-      //   cy.visit('/');
-      //   cy.collapseSidebar(true);
-      //   cy.get('.grw-sidebar-nav-secondary-container').within(() => {
-      //     cy.get('a[href*="/me/drafts"]').click();
-      //   });
-      //   cy.screenshot(`${ssPrefix}access-to-drafts-page`, { blackout: blackoutOverride });
-      // });
-
-      describe('Test access to GROWI Docs page', () => {
-        it('Successfully access to GROWI Docs page', () => {
-          cy.get('.grw-sidebar-nav-secondary-container').within(() => {
-            cy.get('a[href*="https://docs.growi.org"]').then(($a) => {
-              const url = $a.prop('href')
-              cy.request(url).its('body').should('include', '</html>');
-            });
-          });
-        });
-      });
-
-      describe('Test access to trash page', () => {
-        it('Successfully access to trash page', () => {
-          cy.collapseSidebar(true);
-          cy.get('.grw-sidebar-nav-secondary-container').within(() => {
-            cy.get('a[href*="/trash"]').click();
-          });
-
-          cy.getByTestid('trash-page-list').should('be.visible');
-
-          cy.screenshot(`${ssPrefix}access-to-trash-page`, { blackout: blackoutOverride });
-        });
-      });
-    });
-  });
-});

+ 0 - 65
apps/app/test/cypress/e2e/50-sidebar/50-sidebar--switching-sidebar-mode.cy.ts

@@ -1,65 +0,0 @@
-import { BlackoutGroup } from "../../support/blackout";
-
-// Blackout for recalculation of toc content hight
-const blackoutOverride = [
-  ...BlackoutGroup.BASIS,
-  ...BlackoutGroup.SIDE_CONTENTS,
-];
-
-context('Switch sidebar mode', () => {
-  const ssPrefix = 'switch-sidebar-mode-';
-
-  beforeEach(() => {
-    // login
-    cy.fixture("user-admin.json").then(user => {
-      cy.login(user.username, user.password);
-    });
-    cy.visit('/');
-  });
-
-  it('Switching sidebar mode', () => {
-    cy.collapseSidebar(false);
-    cy.screenshot(`${ssPrefix}-doc-mode-opened`, {
-      blackout: blackoutOverride,
-    });
-
-    cy.collapseSidebar(true);
-    cy.screenshot(`${ssPrefix}-doc-mode-closed`, {
-      blackout: blackoutOverride,
-    });
-  });
-
-});
-
-context('Switch viewport size', () => {
-  const ssPrefix = 'switch-viewport-size-';
-
-  const sizes = {
-    'xl': [1200, 1024],
-    'lg': [992, 1024],
-    'md': [768, 1024],
-    'sm': [576, 1024],
-    'xs': [575, 1024],
-    'iphone-x': [375, 812],
-  };
-
-  Object.entries(sizes).forEach(([screenLabel, size]) => {
-    it(`on ${screenLabel} screen`, () => {
-      cy.viewport(size[0], size[1]);
-
-      // login
-      cy.fixture("user-admin.json").then(user => {
-        cy.login(user.username, user.password);
-      });
-      cy.visit('/');
-
-      cy.get('.layout-root').should('be.visible');
-
-      cy.screenshot(`${ssPrefix}-${screenLabel}`, {
-        blackout: blackoutOverride,
-      });
-    });
-  });
-
-});
-

+ 0 - 6
apps/app/test/cypress/fixtures/user-admin.json

@@ -1,6 +0,0 @@
-{
-  "username": "admin",
-  "name": "Admin",
-  "email": "admin@example.com",
-  "password": "adminadmin"
-}

+ 0 - 21
apps/app/test/cypress/support/assertions.ts

@@ -1,21 +0,0 @@
-// from https://github.com/cypress-io/cypress/issues/877#issuecomment-538708750
-const isInViewport = (_chai) => {
-  function assertIsInViewport() {
-
-    const subject = this._obj;
-
-    const bottom = Cypress.config("viewportWidth");
-    const rect = subject[0].getBoundingClientRect();
-
-    this.assert(
-      rect.top < bottom && rect.bottom < bottom,
-      "expected #{this} to be in viewport",
-      "expected #{this} to not be in viewport",
-      this._obj
-    )
-  }
-
-  _chai.Assertion.addMethod('inViewport', assertIsInViewport)
-};
-
-chai.use(isInViewport);

+ 0 - 14
apps/app/test/cypress/support/blackout.ts

@@ -1,14 +0,0 @@
-export const BlackoutGroup = {
-  BASIS: [
-    '[data-vrt-blackout=true]',
-    '[data-vrt-blackout-hash=true]',
-    '[data-vrt-blackout-profile=true]',
-    '[data-vrt-blackout-datetime=true]',
-  ],
-  SIDEBAR_NAV: [
-    '[data-vrt-blackout-sidebar-nav=true]',
-  ],
-  SIDE_CONTENTS: [
-    '[data-vrt-blackout-side-contents=true]',
-  ],
-} as const;

+ 0 - 118
apps/app/test/cypress/support/commands.ts

@@ -1,118 +0,0 @@
-// ***********************************************
-// This example commands.js shows you how to
-// create various custom commands and overwrite
-// existing commands.
-//
-// For more comprehensive examples of custom
-// commands please read more here:
-// https://on.cypress.io/custom-commands
-// ***********************************************
-//
-//
-// -- This is a parent command --
-// Cypress.Commands.add('login', (email, password) => { ... })
-//
-//
-// -- This is a child command --
-// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
-//
-//
-// -- This is a dual command --
-// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
-//
-//
-// -- This will overwrite an existing command --
-// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
-
-import 'cypress-wait-until';
-
-function isVisible($elem: JQuery<Element>) {
-  return $elem.is(':visible');
-}
-function isHidden($elem: JQuery<Element>) {
-  return !isVisible($elem);
-}
-function isVisibleByTestId(testId: string) {
-  return isVisible(Cypress.$(`[data-testid=${testId}]`));
-}
-function isHiddenByTestId(testId: string) {
-  return !isVisibleByTestId(testId);
-}
-
-Cypress.Commands.add('getByTestid', (selector, options?) => {
-  return cy.get(`[data-testid=${selector}]`, options);
-});
-
-Cypress.Commands.add('login', (username, password) => {
-  cy.session(username, () => {
-    cy.visit('/page-to-return-after-login');
-    cy.getByTestid('tiUsernameForLogin').type(username);
-    cy.getByTestid('tiPasswordForLogin').type(password);
-
-    cy.intercept('POST', '/_api/v3/login').as('login');
-    cy.getByTestid('btnSubmitForLogin').click();
-    cy.wait('@login')
-  });
-});
-
-Cypress.Commands.add('waitUntilSkeletonDisappear', () => {
-  if (isHidden(Cypress.$('.grw-skeleton'))) {
-    return;
-  }
-  cy.get('.grw-skeleton').should('not.exist');
-});
-
-Cypress.Commands.add('waitUntilSpinnerDisappear', () => {
-  if (isHidden(Cypress.$('.fa-spinner'))) {
-    return;
-  }
-  cy.get('.fa-spinner').should('not.exist');
-});
-
-Cypress.Commands.add('collapseSidebar', (isCollapsed: boolean, waitUntilSaving = false) => {
-  cy.getByTestid('grw-sidebar').within(($sidebar) => {
-
-    // skip if .grw-sidebar-dock does not exist
-    if (!$sidebar.hasClass('grw-sidebar-dock')) {
-      return;
-    }
-
-  });
-
-  cy.getByTestid('grw-sidebar').should('be.visible').within(() => {
-
-    const isSidebarContentsHidden = isHiddenByTestId('grw-sidebar-contents');
-    if (isSidebarContentsHidden === isCollapsed) {
-      return;
-    }
-
-    cy.waitUntil(() => {
-      // do
-      cy.getByTestid("btn-toggle-collapse").click({force: true});
-      // wait until saving UserUISettings
-      if (waitUntilSaving) {
-        // eslint-disable-next-line cypress/no-unnecessary-waiting
-        cy.wait(1500);
-      }
-
-      // wait until
-      return cy.getByTestid('grw-sidebar-contents').then($contents => isHidden($contents) === isCollapsed);
-    });
-  });
-
-});
-
-Cypress.Commands.add('appendTextToEditorUntilContains', (inputText: string) => {
-  const lines: string[] = [];
-  cy.waitUntil(() => {
-    // do
-    cy.get('.CodeMirror textarea').type(inputText, { force: true });
-    // until
-    return cy.get('.CodeMirror-line')
-      .each(($item) => {
-        lines.push($item.text());
-      }).then(() => {
-        return lines.join('\n').endsWith(inputText);
-      });
-  });
-});

+ 0 - 48
apps/app/test/cypress/support/index.ts

@@ -1,48 +0,0 @@
-// ***********************************************************
-// This example support/index.js is processed and
-// loaded automatically before your test files.
-//
-// This is a great place to put global configuration and
-// behavior that modifies Cypress.
-//
-// You can change the location of this file or turn off
-// automatically serving support files with the
-// 'supportFile' configuration option.
-//
-// You can read more here:
-// https://on.cypress.io/configuration
-// ***********************************************************
-
-// Import commands.js using ES2015 syntax:
-import 'cypress-real-events';
-
-import './assertions'
-import './commands'
-import './screenshot'
-
-// Alternatively you can use CommonJS syntax:
-// require('./commands')
-
-// Ignore 'ResizeObserver loop limit exceeded' exception
-// https://github.com/cypress-io/cypress/issues/8418
-const resizeObserverLoopErrRe = /^[^(ResizeObserver loop limit exceeded)]/
-Cypress.on('uncaught:exception', (err) => {
-    /* returning false here prevents Cypress from failing the test */
-    if (resizeObserverLoopErrRe.test(err.message)) {
-        return false
-    }
-})
-
-declare global {
-  // eslint-disable-next-line @typescript-eslint/no-namespace
-  namespace Cypress {
-    interface Chainable {
-       getByTestid(selector: string, options?: Partial<Loggable & Timeoutable & Withinable & Shadow>): Chainable<JQuery<Element>>,
-       login(username: string, password: string): Chainable<void>,
-       collapseSidebar(isCollapsed: boolean, waitUntilSaving?: boolean): Chainable<void>,
-       waitUntilSkeletonDisappear(): Chainable<void>,
-       waitUntilSpinnerDisappear(): Chainable<void>,
-       appendTextToEditorUntilContains(inputText: string): Chainable<void>
-    }
-  }
-}

+ 0 - 9
apps/app/test/cypress/support/screenshot.ts

@@ -1,9 +0,0 @@
-import { BlackoutGroup } from "./blackout";
-
-Cypress.Screenshot.defaults({
-  blackout: [
-    ...BlackoutGroup.BASIS,
-    ...BlackoutGroup.SIDEBAR_NAV,
-  ],
-  capture: 'viewport',
-})

+ 0 - 16
apps/app/test/cypress/tsconfig.json

@@ -1,16 +0,0 @@
-{
-  "extends": "../../tsconfig.json",
-  "compilerOptions": {
-    "noEmit": true,
-    // be explicit about types included
-    // to avoid clashing with Jest types
-    "types": ["cypress", "cypress-real-events"],
-    // turn off sourceMap
-    // see: https://github.com/cypress-io/cypress/issues/26203
-    "sourceMap": false
-  },
-  "include": [
-    "../../node_modules/cypress",
-    "./**/*.ts"
-  ]
-}

+ 0 - 53
bin/github-actions/generate-cypress-spec-arg.mjs

@@ -1,53 +0,0 @@
-/* eslint-disable no-console */
-
-/*
- * USAGE:
- *  node generate-cypress-spec-arg --prefix=${prefix} --suffix=${suffix} ${value}
- *
- * OPTIONS:
- *  --prefix : prefix string for each items
- *  --suffix : suffix string for each items
- *
- * EXAMPLE:
- *  node generate-cypress-spec-arg --prefix=${prefix}"A" --suffix="Z" "1,3,5"
- *  => A1Z,A3Z,A5Z
- */
-
-import yargs from 'yargs';
-import { hideBin } from 'yargs/helpers';
-
-const argv = yargs(hideBin(process.argv)).argv;
-
-
-const printExample = () => {
-  console.group('** Usage **');
-  // eslint-disable-next-line no-template-curly-in-string
-  console.log('$ node generate-cypress-spec-arg --prefix=${prefix}"A" --suffix="Z" "1,3,5"');
-  console.log('  ==> A1Z,A3Z,A5Z');
-  console.groupEnd();
-  console.log('\n');
-};
-
-
-const { prefix, suffix, _: value } = argv;
-
-if (prefix == null) {
-  printExample();
-  throw new Error('The option "prefix" must be specified');
-}
-if (suffix == null) {
-  printExample();
-  throw new Error('The option "suffix" must be specified');
-}
-if (value.length === 0) {
-  printExample();
-  throw new Error('A value string must be specified');
-}
-
-const result = value[0]
-  .toString().split(',')
-  .map(v => v.trim())
-  .map(v => `${prefix}${v}${suffix}`)
-  .join(',');
-
-console.log(result);

+ 0 - 2
package.json

@@ -71,8 +71,6 @@
     "@vitejs/plugin-react": "^4.3.1",
     "@vitest/coverage-v8": "^1.6.0",
     "@vitest/ui": "^1.6.0",
-    "cypress": "^13.3.0",
-    "cypress-wait-until": "^2.0.1",
     "eslint": "^8.41.0",
     "eslint-config-next": "^12.1.6",
     "eslint-config-weseek": "^2.1.1",

Разлика између датотеке није приказан због своје велике величине
+ 235 - 337
yarn.lock


Неке датотеке нису приказане због велике количине промена