From aace740df58e9389347a90c398ee8df368316241 Mon Sep 17 00:00:00 2001 From: Himself65 Date: Tue, 2 May 2023 23:31:04 -0500 Subject: [PATCH] fix: prohibit delete last workspace (#2212) --- .../pages/workspace/[workspaceId]/setting.tsx | 11 +++- packages/i18n/src/resources/en.json | 3 +- packages/i18n/src/resources/zh-Hans.json | 3 +- .../local-first-delete-workspace.spec.ts | 59 ++++++++++++++++--- 4 files changed, 64 insertions(+), 12 deletions(-) diff --git a/apps/web/src/pages/workspace/[workspaceId]/setting.tsx b/apps/web/src/pages/workspace/[workspaceId]/setting.tsx index 62b9a7a070..609ef5bf89 100644 --- a/apps/web/src/pages/workspace/[workspaceId]/setting.tsx +++ b/apps/web/src/pages/workspace/[workspaceId]/setting.tsx @@ -1,5 +1,6 @@ import { useTranslation } from '@affine/i18n'; import { atomWithSyncStorage } from '@affine/jotai'; +import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom'; import type { SettingPanel } from '@affine/workspace/type'; import { settingPanel, @@ -8,7 +9,7 @@ import { } from '@affine/workspace/type'; import { SettingsIcon } from '@blocksuite/icons'; import { assertExists } from '@blocksuite/store'; -import { useAtom } from 'jotai'; +import { useAtom, useAtomValue } from 'jotai'; import Head from 'next/head'; import { useRouter } from 'next/router'; import React, { useCallback, useEffect } from 'react'; @@ -23,6 +24,7 @@ import { useAppHelper } from '../../../hooks/use-workspaces'; import { WorkspaceLayout } from '../../../layouts/workspace-layout'; import { WorkspacePlugins } from '../../../plugins'; import type { NextPageWithLayout } from '../../../shared'; +import { toast } from '../../../utils'; const settingPanelAtom = atomWithSyncStorage( 'workspaceId', @@ -31,6 +33,7 @@ const settingPanelAtom = atomWithSyncStorage( const SettingPage: NextPageWithLayout = () => { const router = useRouter(); + const workspaceIds = useAtomValue(rootWorkspacesMetadataAtom); const [currentWorkspace] = useCurrentWorkspace(); const { t } = useTranslation(); useSyncRouterWithCurrentWorkspaceId(router); @@ -97,8 +100,12 @@ const SettingPage: NextPageWithLayout = () => { const onDeleteWorkspace = useCallback(() => { assertExists(currentWorkspace); const workspaceId = currentWorkspace.id; + if (workspaceIds.length === 1 && workspaceId === workspaceIds[0].id) { + toast(t('You cannot delete the last workspace')); + throw new Error('You cannot delete the last workspace'); + } return helper.deleteWorkspace(workspaceId); - }, [currentWorkspace, helper]); + }, [currentWorkspace, helper, t, workspaceIds]); const onTransformWorkspace = useOnTransformWorkspace(); if (!router.isReady) { return ; diff --git a/packages/i18n/src/resources/en.json b/packages/i18n/src/resources/en.json index 33d67f1a66..f5dfce1e0a 100644 --- a/packages/i18n/src/resources/en.json +++ b/packages/i18n/src/resources/en.json @@ -227,5 +227,6 @@ "Finding Current Workspace": "Finding Current Workspace", "Page is Loading": "Page is Loading", "Loading All Workspaces": "Loading All Workspaces", - "Loading Page": "Loading Page" + "Loading Page": "Loading Page", + "You cannot delete the last workspace": "You cannot delete the last workspace" } diff --git a/packages/i18n/src/resources/zh-Hans.json b/packages/i18n/src/resources/zh-Hans.json index 57d3188421..50c0e4cb1a 100644 --- a/packages/i18n/src/resources/zh-Hans.json +++ b/packages/i18n/src/resources/zh-Hans.json @@ -189,5 +189,6 @@ "is a Local Workspace": "是本地工作区", "Sign out description": "登出会导致未同步的内容丢失", "Not now": "稍后再说", - "Saved then enable AFFiNE Cloud": "所有改动已保存在本地,点击启用 AFFiNE 云服务" + "Saved then enable AFFiNE Cloud": "所有改动已保存在本地,点击启用 AFFiNE 云服务", + "You cannot delete the last workspace": "你不能删除最后一个工作区" } diff --git a/tests/parallels/local-first-delete-workspace.spec.ts b/tests/parallels/local-first-delete-workspace.spec.ts index 9018682576..8c9003330f 100644 --- a/tests/parallels/local-first-delete-workspace.spec.ts +++ b/tests/parallels/local-first-delete-workspace.spec.ts @@ -3,12 +3,50 @@ import { expect } from '@playwright/test'; import { openHomePage } from '../libs/load-page'; import { waitMarkdownImported } from '../libs/page-logic'; -import { clickSideBarSettingButton } from '../libs/sidebar'; +import { + clickSideBarCurrentWorkspaceBanner, + clickSideBarSettingButton, +} from '../libs/sidebar'; import { assertCurrentWorkspaceFlavour } from '../libs/workspace'; -test('New a workspace , then delete it in all workspaces, permanently delete it', async ({ - page, -}) => { +test('Create new workspace, then delete it', async ({ page }) => { + await openHomePage(page); + await waitMarkdownImported(page); + await clickSideBarCurrentWorkspaceBanner(page); + await page.getByTestId('new-workspace').click(); + await page + .getByTestId('create-workspace-input') + .type('Test Workspace', { delay: 50 }); + await page.getByTestId('create-workspace-button').click(); + await page.waitForTimeout(1000); + await page.waitForSelector('[data-testid="workspace-name"]'); + expect(await page.getByTestId('workspace-name').textContent()).toBe( + 'Test Workspace' + ); + await clickSideBarSettingButton(page); + await page.getByTestId('delete-workspace-button').click(); + const workspaceNameDom = await page.getByTestId('workspace-name'); + const currentWorkspaceName = await workspaceNameDom.evaluate( + node => node.textContent + ); + await page + .getByTestId('delete-workspace-input') + .type(currentWorkspaceName as string); + const promise = page + .getByTestId('affine-toast') + .waitFor({ state: 'attached' }); + await page.getByTestId('delete-workspace-confirm-button').click(); + await promise; + await page.reload(); + await page.waitForSelector('[data-testid="workspace-name"]'); + await page.waitForTimeout(1000); + expect(await page.getByTestId('workspace-name').textContent()).toBe( + 'Demo Workspace' + ); + await assertCurrentWorkspaceFlavour('local', page); +}); + +test('Should not delete the last one workspace', async ({ page }) => { await openHomePage(page); await waitMarkdownImported(page); await clickSideBarSettingButton(page); @@ -20,10 +58,15 @@ test('New a workspace , then delete it in all workspaces, permanently delete it' await page .getByTestId('delete-workspace-input') .type(currentWorkspaceName as string); + const promise = page + .getByTestId('affine-toast') + .waitFor({ state: 'attached' }); await page.getByTestId('delete-workspace-confirm-button').click(); - await page.getByTestId('affine-toast').waitFor({ state: 'attached' }); - expect(await page.getByTestId('workspace-card').count()).toBe(0); - await page.mouse.click(1, 1); - expect(await page.getByTestId('workspace-card').count()).toBe(0); + await promise; + await page.reload(); + await page.waitForSelector('[data-testid="workspace-name"]'); + expect(await page.getByTestId('workspace-name').textContent()).toBe( + 'Demo Workspace' + ); await assertCurrentWorkspaceFlavour('local', page); });