Quellcode durchsuchen

feat: support vue file unit testing, add some components unit testing (#4119)

Vben vor 1 Jahr
Ursprung
Commit
3f9ce63868
53 geänderte Dateien mit 241 neuen und 156 gelöschten Zeilen
  1. 4 4
      .github/workflows/deploy.yml
  2. 3 0
      package.json
  3. 1 1
      packages/@core/ui-kit/shadcn-ui/src/components/alert-dialog/alert-dialog.vue
  4. 1 5
      packages/@core/ui-kit/shadcn-ui/src/components/avatar/avatar.vue
  5. 1 1
      packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/breadcrumb-background.vue
  6. 5 5
      packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/breadcrumb.vue
  7. 1 1
      packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/index.ts
  8. 0 0
      packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/types.ts
  9. 2 4
      packages/@core/ui-kit/shadcn-ui/src/components/button/button.vue
  10. 2 2
      packages/@core/ui-kit/shadcn-ui/src/components/button/icon-button.vue
  11. 2 2
      packages/@core/ui-kit/shadcn-ui/src/components/checkbox/checkbox.vue
  12. 3 3
      packages/@core/ui-kit/shadcn-ui/src/components/context-menu/context-menu.vue
  13. 1 1
      packages/@core/ui-kit/shadcn-ui/src/components/dropdown-menu/dropdown-menu.vue
  14. 1 1
      packages/@core/ui-kit/shadcn-ui/src/components/dropdown-menu/dropdown-radio-menu.vue
  15. 3 3
      packages/@core/ui-kit/shadcn-ui/src/components/hover-card/hover-card.vue
  16. 1 4
      packages/@core/ui-kit/shadcn-ui/src/components/input-password/input-password.vue
  17. 1 1
      packages/@core/ui-kit/shadcn-ui/src/components/input/index.ts
  18. 1 1
      packages/@core/ui-kit/shadcn-ui/src/components/input/input.vue
  19. 0 0
      packages/@core/ui-kit/shadcn-ui/src/components/input/types.ts
  20. 1 1
      packages/@core/ui-kit/shadcn-ui/src/components/pin-input/index.ts
  21. 3 7
      packages/@core/ui-kit/shadcn-ui/src/components/pin-input/input.vue
  22. 0 0
      packages/@core/ui-kit/shadcn-ui/src/components/pin-input/types.ts
  23. 3 3
      packages/@core/ui-kit/shadcn-ui/src/components/popover/popover.vue
  24. 2 4
      packages/@core/ui-kit/shadcn-ui/src/components/scrollbar/scrollbar.vue
  25. 2 2
      packages/@core/ui-kit/shadcn-ui/src/components/segmented/index.ts
  26. 2 7
      packages/@core/ui-kit/shadcn-ui/src/components/segmented/segmented.vue
  27. 0 0
      packages/@core/ui-kit/shadcn-ui/src/components/segmented/types.ts
  28. 5 8
      packages/@core/ui-kit/shadcn-ui/src/components/sheet/sheet.vue
  29. 1 1
      packages/@core/ui-kit/shadcn-ui/src/components/tooltip/tooltip.vue
  30. 2 1
      packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogAction.vue
  31. 2 1
      packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogCancel.vue
  32. 2 1
      packages/@core/ui-kit/shadcn-ui/src/components/ui/toggle-group/ToggleGroup.vue
  33. 2 1
      packages/@core/ui-kit/shadcn-ui/src/components/ui/toggle-group/ToggleGroupItem.vue
  34. 39 0
      packages/effects/common-ui/src/components/ellipsis-text/__tests__/ellipsis-text.test.ts
  35. 1 0
      packages/effects/common-ui/src/components/ellipsis-text/ellipsis-text.vue
  36. 74 0
      packages/effects/common-ui/src/components/page/__tests__/page.test.ts
  37. 0 13
      packages/effects/common-ui/src/components/page/page-footer.vue
  38. 0 18
      packages/effects/common-ui/src/components/page/page-header.vue
  39. 0 11
      packages/effects/common-ui/src/components/page/page.ts
  40. 23 16
      packages/effects/common-ui/src/components/page/page.vue
  41. 1 1
      packages/effects/common-ui/src/ui/authentication/code-login.vue
  42. 1 1
      packages/effects/common-ui/src/ui/authentication/index.ts
  43. 1 1
      packages/effects/common-ui/src/ui/authentication/login-expired-modal.vue
  44. 1 1
      packages/effects/common-ui/src/ui/authentication/login.vue
  45. 1 1
      packages/effects/common-ui/src/ui/authentication/register.vue
  46. 0 0
      packages/effects/common-ui/src/ui/authentication/types.ts
  47. 8 2
      packages/effects/layouts/src/widgets/lock-screen/lock-screen-modal.vue
  48. 0 9
      packages/effects/layouts/src/widgets/lock-screen/typings.ts
  49. 6 2
      playground/src/views/demos/access/admin-visible.vue
  50. 6 2
      playground/src/views/demos/access/super-visible.vue
  51. 6 2
      playground/src/views/demos/access/user-visible.vue
  52. 10 0
      pnpm-lock.yaml
  53. 3 0
      vitest.config.ts

+ 4 - 4
.github/workflows/deploy.yml

@@ -7,7 +7,7 @@ on:
 
 jobs:
   deploy-push-playground-ftp:
-    name: Deploy Push Ftp
+    name: Deploy Push Playground Ftp
     if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
     runs-on: ubuntu-latest
     steps:
@@ -46,7 +46,7 @@ jobs:
           local-dir: ./docs/.vitepress/dist/
 
   deploy-push-antd-ftp:
-    name: Deploy Push Ftp
+    name: Deploy Push Antd Ftp
     if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
     runs-on: ubuntu-latest
     steps:
@@ -77,7 +77,7 @@ jobs:
           local-dir: ./apps/web-antd/dist/
 
   deploy-push-ele-ftp:
-    name: Deploy Push Ftp
+    name: Deploy Push Element Ftp
     if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
     runs-on: ubuntu-latest
     steps:
@@ -108,7 +108,7 @@ jobs:
           local-dir: ./apps/web-ele/dist/
 
   deploy-push-naive-ftp:
-    name: Deploy Push Ftp
+    name: Deploy Push Naive Ftp
     if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
     runs-on: ubuntu-latest
     steps:

+ 3 - 0
package.json

@@ -70,6 +70,8 @@
     "@vben/turbo-run": "workspace:*",
     "@vben/vite-config": "workspace:*",
     "@vben/vsh": "workspace:*",
+    "@vitejs/plugin-vue": "^5.1.2",
+    "@vitejs/plugin-vue-jsx": "^4.0.0",
     "@vue/test-utils": "^2.4.6",
     "autoprefixer": "^10.4.20",
     "cross-env": "^7.0.3",
@@ -85,6 +87,7 @@
     "unbuild": "^2.0.0",
     "vite": "^5.4.0",
     "vitest": "^2.0.5",
+    "vue": "^3.4.37",
     "vue-tsc": "^2.0.29"
   },
   "engines": {

+ 1 - 1
packages/@core/ui-kit/shadcn-ui/src/components/alert-dialog/alert-dialog.vue

@@ -8,7 +8,7 @@ import {
   AlertDialogHeader,
   AlertDialog as AlertDialogRoot,
   AlertDialogTitle,
-} from '@vben-core/shadcn-ui/components/ui/alert-dialog';
+} from '../ui/alert-dialog';
 
 interface Props {
   cancelText?: string;

+ 1 - 5
packages/@core/ui-kit/shadcn-ui/src/components/avatar/avatar.vue

@@ -7,11 +7,7 @@ import type {
 
 import { computed } from 'vue';
 
-import {
-  Avatar,
-  AvatarFallback,
-  AvatarImage,
-} from '@vben-core/shadcn-ui/components/ui/avatar';
+import { Avatar, AvatarFallback, AvatarImage } from '../ui/avatar';
 
 interface Props extends AvatarRootProps, AvatarFallbackProps, AvatarImageProps {
   alt?: string;

+ 1 - 1
packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/breadcrumb-background.vue

@@ -1,5 +1,5 @@
 <script lang="ts" setup>
-import type { IBreadcrumb } from './interface';
+import type { IBreadcrumb } from './types';
 
 import { VbenIcon } from '../icon';
 

+ 5 - 5
packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/breadcrumb.vue

@@ -1,7 +1,9 @@
 <script lang="ts" setup>
-import type { IBreadcrumb } from './interface';
+import type { IBreadcrumb } from './types';
 
 import { ChevronDown } from '@vben-core/icons';
+
+import { VbenIcon } from '../icon';
 import {
   Breadcrumb,
   BreadcrumbItem,
@@ -9,15 +11,13 @@ import {
   BreadcrumbList,
   BreadcrumbPage,
   BreadcrumbSeparator,
-} from '@vben-core/shadcn-ui/components/ui/breadcrumb';
+} from '../ui/breadcrumb';
 import {
   DropdownMenu,
   DropdownMenuContent,
   DropdownMenuItem,
   DropdownMenuTrigger,
-} from '@vben-core/shadcn-ui/components/ui/dropdown-menu';
-
-import { VbenIcon } from '../icon';
+} from '../ui/dropdown-menu';
 
 interface Props {
   breadcrumbs: IBreadcrumb[];

+ 1 - 1
packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/index.ts

@@ -1,4 +1,4 @@
 export { default as VbenBreadcrumb } from './breadcrumb.vue';
 export { default as VbenBackgroundBreadcrumb } from './breadcrumb-background.vue';
 
-export type * from './interface';
+export type * from './types';

+ 0 - 0
packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/interface.ts → packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/types.ts


+ 2 - 4
packages/@core/ui-kit/shadcn-ui/src/components/button/button.vue

@@ -2,14 +2,12 @@
 import { computed } from 'vue';
 
 import { LoaderCircle } from '@vben-core/icons';
-import {
-  type ButtonVariants,
-  buttonVariants,
-} from '@vben-core/shadcn-ui/components/ui/button';
 import { cn } from '@vben-core/shared';
 
 import { Primitive, type PrimitiveProps } from 'radix-vue';
 
+import { type ButtonVariants, buttonVariants } from '../ui/button';
+
 interface Props extends PrimitiveProps {
   class?: any;
   disabled?: boolean;

+ 2 - 2
packages/@core/ui-kit/shadcn-ui/src/components/button/icon-button.vue

@@ -1,13 +1,13 @@
 <script setup lang="ts">
-import type { ButtonVariants } from '@vben-core/shadcn-ui/components/ui/button';
+import type { ButtonVariants } from '../ui/button';
 
 import { computed, useSlots } from 'vue';
 
-import { VbenTooltip } from '@vben-core/shadcn-ui/components/tooltip';
 import { cn } from '@vben-core/shared';
 
 import { type PrimitiveProps } from 'radix-vue';
 
+import { VbenTooltip } from '../tooltip';
 import VbenButton from './button.vue';
 
 interface Props extends PrimitiveProps {

+ 2 - 2
packages/@core/ui-kit/shadcn-ui/src/components/checkbox/checkbox.vue

@@ -1,10 +1,10 @@
 <script setup lang="ts">
 import type { CheckboxRootEmits, CheckboxRootProps } from 'radix-vue';
 
-import { Checkbox } from '@vben-core/shadcn-ui/components/ui/checkbox';
-
 import { useForwardPropsEmits } from 'radix-vue';
 
+import { Checkbox } from '../ui/checkbox';
+
 const props = defineProps<
   {
     name: string;

+ 3 - 3
packages/@core/ui-kit/shadcn-ui/src/components/context-menu/context-menu.vue

@@ -9,6 +9,8 @@ import type { IContextMenuItem } from './interface';
 
 import { computed } from 'vue';
 
+import { useForwardPropsEmits } from 'radix-vue';
+
 import {
   ContextMenu,
   ContextMenuContent,
@@ -16,9 +18,7 @@ import {
   ContextMenuSeparator,
   ContextMenuShortcut,
   ContextMenuTrigger,
-} from '@vben-core/shadcn-ui/components/ui/context-menu';
-
-import { useForwardPropsEmits } from 'radix-vue';
+} from '../ui/context-menu';
 
 const props = defineProps<
   {

+ 1 - 1
packages/@core/ui-kit/shadcn-ui/src/components/dropdown-menu/dropdown-menu.vue

@@ -11,7 +11,7 @@ import {
   DropdownMenuItem,
   DropdownMenuSeparator,
   DropdownMenuTrigger,
-} from '@vben-core/shadcn-ui/components/ui/dropdown-menu';
+} from '../ui/dropdown-menu';
 
 interface Props extends DropdownMenuProps {}
 

+ 1 - 1
packages/@core/ui-kit/shadcn-ui/src/components/dropdown-menu/dropdown-radio-menu.vue

@@ -7,7 +7,7 @@ import {
   DropdownMenuGroup,
   DropdownMenuItem,
   DropdownMenuTrigger,
-} from '@vben-core/shadcn-ui/components/ui/dropdown-menu';
+} from '../ui/dropdown-menu';
 
 interface Props extends DropdownMenuProps {}
 

+ 3 - 3
packages/@core/ui-kit/shadcn-ui/src/components/hover-card/hover-card.vue

@@ -7,13 +7,13 @@ import type {
 
 import { computed } from 'vue';
 
+import { useForwardPropsEmits } from 'radix-vue';
+
 import {
   HoverCard,
   HoverCardContent,
   HoverCardTrigger,
-} from '@vben-core/shadcn-ui/components/ui/hover-card';
-
-import { useForwardPropsEmits } from 'radix-vue';
+} from '../ui/hover-card';
 
 interface Props extends HoverCardRootProps {
   class?: any;

+ 1 - 4
packages/@core/ui-kit/shadcn-ui/src/components/input-password/input-password.vue

@@ -2,13 +2,10 @@
 import { ref, useSlots } from 'vue';
 
 import { Eye, EyeOff } from '@vben-core/icons';
-import {
-  type InputProps,
-  VbenInput,
-} from '@vben-core/shadcn-ui/components/input';
 
 import { useForwardProps } from 'radix-vue';
 
+import { type InputProps, VbenInput } from '../input';
 import PasswordStrength from './password-strength.vue';
 
 interface Props extends InputProps {}

+ 1 - 1
packages/@core/ui-kit/shadcn-ui/src/components/input/index.ts

@@ -1,2 +1,2 @@
 export { default as VbenInput } from './input.vue';
-export type * from './interface';
+export type * from './types';

+ 1 - 1
packages/@core/ui-kit/shadcn-ui/src/components/input/input.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import type { InputProps } from './interface';
+import type { InputProps } from './types';
 
 import { computed } from 'vue';
 

+ 0 - 0
packages/@core/ui-kit/shadcn-ui/src/components/input/interface.ts → packages/@core/ui-kit/shadcn-ui/src/components/input/types.ts


+ 1 - 1
packages/@core/ui-kit/shadcn-ui/src/components/pin-input/index.ts

@@ -1,3 +1,3 @@
 export { default as VbenPinInput } from './input.vue';
 
-export type * from './interface';
+export type * from './types';

+ 3 - 7
packages/@core/ui-kit/shadcn-ui/src/components/pin-input/input.vue

@@ -1,14 +1,10 @@
 <script setup lang="ts">
-import type { PinInputProps } from './interface';
+import type { PinInputProps } from './types';
 
 import { computed, ref, watch } from 'vue';
 
-import { VbenButton } from '@vben-core/shadcn-ui/components/button';
-import {
-  PinInput,
-  PinInputGroup,
-  PinInputInput,
-} from '@vben-core/shadcn-ui/components/ui/pin-input';
+import { VbenButton } from '../button';
+import { PinInput, PinInputGroup, PinInputInput } from '../ui/pin-input';
 
 defineOptions({
   inheritAttrs: false,

+ 0 - 0
packages/@core/ui-kit/shadcn-ui/src/components/pin-input/interface.ts → packages/@core/ui-kit/shadcn-ui/src/components/pin-input/types.ts


+ 3 - 3
packages/@core/ui-kit/shadcn-ui/src/components/popover/popover.vue

@@ -7,13 +7,13 @@ import type {
 
 import { computed } from 'vue';
 
+import { useForwardPropsEmits } from 'radix-vue';
+
 import {
   PopoverContent,
   Popover as PopoverRoot,
   PopoverTrigger,
-} from '@vben-core/shadcn-ui/components/ui/popover';
-
-import { useForwardPropsEmits } from 'radix-vue';
+} from '../ui/popover';
 
 interface Props extends PopoverRootProps {
   class?: any;

+ 2 - 4
packages/@core/ui-kit/shadcn-ui/src/components/scrollbar/scrollbar.vue

@@ -1,12 +1,10 @@
 <script setup lang="ts">
 import { ref } from 'vue';
 
-import {
-  ScrollArea,
-  ScrollBar,
-} from '@vben-core/shadcn-ui/components/ui/scroll-area';
 import { cn } from '@vben-core/shared';
 
+import { ScrollArea, ScrollBar } from '../ui/scroll-area';
+
 interface Props {
   class?: any;
   horizontal?: boolean;

+ 2 - 2
packages/@core/ui-kit/shadcn-ui/src/components/segmented/index.ts

@@ -1,3 +1,3 @@
-export type * from './interface';
-
 export { default as VbenSegmented } from './segmented.vue';
+
+export type * from './types';

+ 2 - 7
packages/@core/ui-kit/shadcn-ui/src/components/segmented/segmented.vue

@@ -1,16 +1,11 @@
 <script setup lang="ts">
-import type { SegmentedItem } from './interface';
+import type { SegmentedItem } from './types';
 
 import { computed } from 'vue';
 
-import {
-  Tabs,
-  TabsContent,
-  TabsList,
-} from '@vben-core/shadcn-ui/components/ui/tabs';
-
 import { TabsTrigger } from 'radix-vue';
 
+import { Tabs, TabsContent, TabsList } from '../ui/tabs';
 import TabsIndicator from './tabs-indicator.vue';
 
 interface Props {

+ 0 - 0
packages/@core/ui-kit/shadcn-ui/src/components/segmented/interface.ts → packages/@core/ui-kit/shadcn-ui/src/components/segmented/types.ts


+ 5 - 8
packages/@core/ui-kit/shadcn-ui/src/components/sheet/sheet.vue

@@ -1,11 +1,10 @@
 <script setup lang="ts">
 import { computed, useSlots } from 'vue';
 
-import {
-  VbenButton,
-  VbenIconButton,
-} from '@vben-core/shadcn-ui/components/button';
-import { VbenScrollbar } from '@vben-core/shadcn-ui/components/scrollbar';
+import { X } from 'lucide-vue-next';
+
+import { VbenButton, VbenIconButton } from '../button';
+import { VbenScrollbar } from '../scrollbar';
 import {
   Sheet,
   SheetClose,
@@ -15,9 +14,7 @@ import {
   SheetHeader,
   SheetTitle,
   SheetTrigger,
-} from '@vben-core/shadcn-ui/components/ui/sheet';
-
-import { X } from 'lucide-vue-next';
+} from '../ui/sheet';
 
 interface Props {
   cancelText?: string;

+ 1 - 1
packages/@core/ui-kit/shadcn-ui/src/components/tooltip/tooltip.vue

@@ -8,7 +8,7 @@ import {
   TooltipContent,
   TooltipProvider,
   TooltipTrigger,
-} from '@vben-core/shadcn-ui/components/ui/tooltip';
+} from '../ui/tooltip';
 
 interface Props {
   contentClass?: any;

+ 2 - 1
packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogAction.vue

@@ -1,11 +1,12 @@
 <script setup lang="ts">
 import { computed, type HTMLAttributes } from 'vue';
 
-import { buttonVariants } from '@vben-core/shadcn-ui/components/ui/button';
 import { cn } from '@vben-core/shared';
 
 import { AlertDialogAction, type AlertDialogActionProps } from 'radix-vue';
 
+import { buttonVariants } from '../button';
+
 const props = defineProps<
   { class?: HTMLAttributes['class'] } & AlertDialogActionProps
 >();

+ 2 - 1
packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogCancel.vue

@@ -1,11 +1,12 @@
 <script setup lang="ts">
 import { computed, type HTMLAttributes } from 'vue';
 
-import { buttonVariants } from '@vben-core/shadcn-ui/components/ui/button';
 import { cn } from '@vben-core/shared';
 
 import { AlertDialogCancel, type AlertDialogCancelProps } from 'radix-vue';
 
+import { buttonVariants } from '../button';
+
 const props = defineProps<
   { class?: HTMLAttributes['class'] } & AlertDialogCancelProps
 >();

+ 2 - 1
packages/@core/ui-kit/shadcn-ui/src/components/ui/toggle-group/ToggleGroup.vue

@@ -1,7 +1,8 @@
 <script setup lang="ts">
-import type { toggleVariants } from '@vben-core/shadcn-ui/components/ui/toggle';
 import type { VariantProps } from 'class-variance-authority';
 
+import type { toggleVariants } from '../toggle';
+
 import { computed, type HTMLAttributes, provide } from 'vue';
 
 import { cn } from '@vben-core/shared';

+ 2 - 1
packages/@core/ui-kit/shadcn-ui/src/components/ui/toggle-group/ToggleGroupItem.vue

@@ -3,7 +3,6 @@ import type { VariantProps } from 'class-variance-authority';
 
 import { computed, type HTMLAttributes, inject } from 'vue';
 
-import { toggleVariants } from '@vben-core/shadcn-ui/components/ui/toggle';
 import { cn } from '@vben-core/shared';
 
 import {
@@ -12,6 +11,8 @@ import {
   useForwardProps,
 } from 'radix-vue';
 
+import { toggleVariants } from '../toggle';
+
 type ToggleGroupVariants = VariantProps<typeof toggleVariants>;
 
 const props = defineProps<

+ 39 - 0
packages/effects/common-ui/src/components/ellipsis-text/__tests__/ellipsis-text.test.ts

@@ -0,0 +1,39 @@
+import { mount } from '@vue/test-utils';
+import { describe, expect, it } from 'vitest';
+
+import { EllipsisText } from '..';
+
+describe('ellipsis-text.vue', () => {
+  it('renders the correct content and truncates text', async () => {
+    const wrapper = mount(EllipsisText, {
+      props: {
+        line: 1,
+        title: 'Test Title',
+      },
+      slots: {
+        default: 'This is a very long text that should be truncated.',
+      },
+    });
+
+    expect(wrapper.text()).toContain('This is a very long text');
+    // 检查 ellipsis 是否应用了正确的 class
+    const ellipsis = wrapper.find('.truncate');
+    expect(ellipsis.exists()).toBe(true);
+  });
+
+  it('expands text on click if expand is true', async () => {
+    const wrapper = mount(EllipsisText, {
+      props: {
+        expand: true,
+        line: 1,
+      },
+      slots: {
+        default: 'This is a very long text that should be truncated.',
+      },
+    });
+
+    const ellipsis = wrapper.find('.truncate');
+    await ellipsis.trigger('click');
+    expect(wrapper.emitted('expandChange')).toBeTruthy();
+  });
+});

+ 1 - 0
packages/effects/common-ui/src/components/ellipsis-text/ellipsis-text.vue

@@ -51,6 +51,7 @@ interface Props {
    */
   tooltipOverlayStyle?: CSSProperties;
 }
+
 const props = withDefaults(defineProps<Props>(), {
   expand: false,
   line: 1,

+ 74 - 0
packages/effects/common-ui/src/components/page/__tests__/page.test.ts

@@ -0,0 +1,74 @@
+import { mount } from '@vue/test-utils';
+import { describe, expect, it } from 'vitest';
+
+import { Page } from '..';
+
+describe('page.vue', () => {
+  it('renders title when passed', () => {
+    const wrapper = mount(Page, {
+      props: {
+        title: 'Test Title',
+      },
+    });
+
+    expect(wrapper.text()).toContain('Test Title');
+  });
+
+  it('renders description when passed', () => {
+    const wrapper = mount(Page, {
+      props: {
+        description: 'Test Description',
+      },
+    });
+
+    expect(wrapper.text()).toContain('Test Description');
+  });
+
+  it('renders default slot content', () => {
+    const wrapper = mount(Page, {
+      slots: {
+        default: '<p>Default Slot Content</p>',
+      },
+    });
+
+    expect(wrapper.html()).toContain('<p>Default Slot Content</p>');
+  });
+
+  it('renders footer slot when showFooter is true', () => {
+    const wrapper = mount(Page, {
+      props: {
+        showFooter: true,
+      },
+      slots: {
+        footer: '<p>Footer Slot Content</p>',
+      },
+    });
+
+    expect(wrapper.html()).toContain('<p>Footer Slot Content</p>');
+  });
+
+  it('applies the custom contentClass', () => {
+    const wrapper = mount(Page, {
+      props: {
+        contentClass: 'custom-class',
+      },
+    });
+
+    const contentDiv = wrapper.find('.m-4');
+    expect(contentDiv.classes()).toContain('custom-class');
+  });
+
+  it('does not render description slot if description prop is provided', () => {
+    const wrapper = mount(Page, {
+      props: {
+        description: 'Test Description',
+      },
+      slots: {
+        description: '<p>Description Slot Content</p>',
+      },
+    });
+
+    expect(wrapper.text()).toContain('Test Description');
+    expect(wrapper.html()).not.toContain('Description Slot Content');
+  });
+});

+ 0 - 13
packages/effects/common-ui/src/components/page/page-footer.vue

@@ -1,13 +0,0 @@
-<script setup lang="ts">
-defineOptions({
-  name: 'PageFooter',
-});
-</script>
-
-<template>
-  <div
-    class="bg-card align-center absolute bottom-0 left-0 right-0 flex px-6 py-4"
-  >
-    <slot></slot>
-  </div>
-</template>

+ 0 - 18
packages/effects/common-ui/src/components/page/page-header.vue

@@ -1,18 +0,0 @@
-<script setup lang="ts">
-import type { PageHeaderProps } from './page.ts';
-
-defineOptions({
-  name: 'PageHeader',
-});
-
-const props = defineProps<PageHeaderProps>();
-</script>
-
-<template>
-  <div class="bg-card px-6 py-4">
-    <div class="mb-2 flex justify-between text-xl font-bold leading-10">
-      {{ props.title }}
-    </div>
-    <slot></slot>
-  </div>
-</template>

+ 0 - 11
packages/effects/common-ui/src/components/page/page.ts

@@ -1,11 +0,0 @@
-interface PageHeaderProps {
-  title?: string;
-  description?: string;
-}
-
-interface Props extends PageHeaderProps {
-  contentClass?: string;
-  showFooter?: boolean;
-}
-
-export type { PageHeaderProps, Props };

+ 23 - 16
packages/effects/common-ui/src/components/page/page.vue

@@ -1,14 +1,17 @@
 <script setup lang="ts">
-import type { Props } from './page';
-
-import PageFooter from './page-footer.vue';
-import PageHeader from './page-header.vue';
+interface Props {
+  title?: string;
+  description?: string;
+  contentClass?: string;
+  showFooter?: boolean;
+}
 
 defineOptions({
   name: 'Page',
 });
 
 const props = withDefaults(defineProps<Props>(), {
+  contentClass: '',
   description: '',
   showFooter: false,
   title: '',
@@ -17,22 +20,26 @@ const props = withDefaults(defineProps<Props>(), {
 
 <template>
   <div class="relative h-full">
-    <PageHeader
+    <div
       v-if="description || $slots.description || title"
-      :title="props.title"
+      class="bg-card px-6 py-4"
     >
-      <template #default>
-        <template v-if="description">{{ description }}</template>
-        <slot v-else name="description"></slot>
-      </template>
-    </PageHeader>
+      <div class="mb-2 flex justify-between text-xl font-bold leading-10">
+        {{ title }}
+      </div>
+      <template v-if="description">{{ description }}</template>
+      <slot v-else name="description"></slot>
+    </div>
+
     <div :class="contentClass" class="m-4">
       <slot></slot>
     </div>
-    <PageFooter v-if="props.showFooter">
-      <template #default>
-        <slot name="footer"></slot>
-      </template>
-    </PageFooter>
+
+    <div
+      v-if="props.showFooter"
+      class="bg-card align-center absolute bottom-0 left-0 right-0 flex px-6 py-4"
+    >
+      <slot name="footer"></slot>
+    </div>
   </div>
 </template>

+ 1 - 1
packages/effects/common-ui/src/ui/authentication/code-login.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import type { LoginCodeEmits } from './typings';
+import type { LoginCodeEmits } from './types';
 
 import { computed, onBeforeUnmount, reactive, ref } from 'vue';
 import { useRouter } from 'vue-router';

+ 1 - 1
packages/effects/common-ui/src/ui/authentication/index.ts

@@ -8,4 +8,4 @@ export type {
   AuthenticationProps,
   LoginAndRegisterParams,
   LoginCodeParams,
-} from './typings';
+} from './types';

+ 1 - 1
packages/effects/common-ui/src/ui/authentication/login-expired-modal.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import type { AuthenticationProps, LoginAndRegisterParams } from './typings';
+import type { AuthenticationProps, LoginAndRegisterParams } from './types';
 
 import { useForwardPropsEmits } from '@vben/hooks';
 import {

+ 1 - 1
packages/effects/common-ui/src/ui/authentication/login.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import type { AuthenticationProps, LoginEmits } from './typings';
+import type { AuthenticationProps, LoginEmits } from './types';
 
 import { computed, reactive } from 'vue';
 import { useRouter } from 'vue-router';

+ 1 - 1
packages/effects/common-ui/src/ui/authentication/register.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import type { RegisterEmits } from './typings';
+import type { RegisterEmits } from './types';
 
 import { computed, reactive } from 'vue';
 import { useRouter } from 'vue-router';

+ 0 - 0
packages/effects/common-ui/src/ui/authentication/typings.ts → packages/effects/common-ui/src/ui/authentication/types.ts


+ 8 - 2
packages/effects/layouts/src/widgets/lock-screen/lock-screen-modal.vue

@@ -1,6 +1,4 @@
 <script setup lang="ts">
-import type { RegisterEmits } from './typings';
-
 import { computed, reactive } from 'vue';
 
 import {
@@ -19,6 +17,14 @@ interface Props {
   text?: string;
 }
 
+interface LockAndRegisterParams {
+  lockScreenPassword: string;
+}
+
+interface RegisterEmits {
+  submit: [LockAndRegisterParams];
+}
+
 defineOptions({
   name: 'LockScreenModal',
 });

+ 0 - 9
packages/effects/layouts/src/widgets/lock-screen/typings.ts

@@ -1,9 +0,0 @@
-interface LockAndRegisterParams {
-  lockScreenPassword: string;
-}
-
-interface RegisterEmits {
-  submit: [LockAndRegisterParams];
-}
-
-export type { LockAndRegisterParams, RegisterEmits };

+ 6 - 2
playground/src/views/demos/access/admin-visible.vue

@@ -1,7 +1,11 @@
 <script lang="ts" setup>
-import { Page } from '@vben/common-ui';
+import { Fallback } from '@vben/common-ui';
 </script>
 
 <template>
-  <Page description="当前页面仅 Admin 账号可见" title="页面访问测试" />
+  <Fallback
+    description="当前页面仅 Admin 账号可见"
+    status="coming-soon"
+    title="页面访问测试"
+  />
 </template>

+ 6 - 2
playground/src/views/demos/access/super-visible.vue

@@ -1,7 +1,11 @@
 <script lang="ts" setup>
-import { Page } from '@vben/common-ui';
+import { Fallback } from '@vben/common-ui';
 </script>
 
 <template>
-  <Page description="当前页面仅 Super 账号可见" title="页面访问测试" />
+  <Fallback
+    description="当前页面仅 Super 账号可见"
+    status="coming-soon"
+    title="页面访问测试"
+  />
 </template>

+ 6 - 2
playground/src/views/demos/access/user-visible.vue

@@ -1,7 +1,11 @@
 <script lang="ts" setup>
-import { Page } from '@vben/common-ui';
+import { Fallback } from '@vben/common-ui';
 </script>
 
 <template>
-  <Page description="当前页面仅 User 账号可见" title="页面访问测试" />
+  <Fallback
+    description="当前页面仅 User 账号可见"
+    status="coming-soon"
+    title="页面访问测试"
+  />
 </template>

+ 10 - 0
pnpm-lock.yaml

@@ -55,6 +55,12 @@ importers:
       '@vben/vsh':
         specifier: workspace:*
         version: link:scripts/vsh
+      '@vitejs/plugin-vue':
+        specifier: ^5.1.2
+        version: 5.1.2(vite@5.4.0(@types/node@22.2.0)(sass@1.77.8)(terser@5.31.3))(vue@3.4.37(typescript@5.5.4))
+      '@vitejs/plugin-vue-jsx':
+        specifier: ^4.0.0
+        version: 4.0.0(vite@5.4.0(@types/node@22.2.0)(sass@1.77.8)(terser@5.31.3))(vue@3.4.37(typescript@5.5.4))
       '@vue/test-utils':
         specifier: ^2.4.6
         version: 2.4.6
@@ -100,6 +106,9 @@ importers:
       vitest:
         specifier: ^2.0.5
         version: 2.0.5(@types/node@22.2.0)(jsdom@24.1.1)(sass@1.77.8)(terser@5.31.3)
+      vue:
+        specifier: ^3.4.37
+        version: 3.4.37(typescript@5.5.4)
       vue-tsc:
         specifier: ^2.0.29
         version: 2.0.29(typescript@5.5.4)
@@ -3344,6 +3353,7 @@ packages:
 
   '@ls-lint/ls-lint@2.2.3':
     resolution: {integrity: sha512-ekM12jNm/7O2I/hsRv9HvYkRdfrHpiV1epVuI2NP+eTIcEgdIdKkKCs9KgQydu/8R5YXTov9aHdOgplmCHLupw==}
+    cpu: [x64, arm64, s390x]
     os: [darwin, linux, win32]
     hasBin: true
 

+ 3 - 0
vitest.config.ts

@@ -1,6 +1,9 @@
+import Vue from '@vitejs/plugin-vue';
+import VueJsx from '@vitejs/plugin-vue-jsx';
 import { defineConfig } from 'vitest/config';
 
 export default defineConfig({
+  plugins: [Vue(), VueJsx()],
   test: {
     environment: 'jsdom',
   },