@@ -82,6 +82,7 @@
|
|||||||
"@eslint-react/eslint-plugin": "^2.3.13",
|
"@eslint-react/eslint-plugin": "^2.3.13",
|
||||||
"@eslint/js": "^9.39.1",
|
"@eslint/js": "^9.39.1",
|
||||||
"@hey-api/openapi-ts": "0.91.0",
|
"@hey-api/openapi-ts": "0.91.0",
|
||||||
|
"@playwright/test": "^1.58.2",
|
||||||
"@redux-devtools/extension": "^3.3.0",
|
"@redux-devtools/extension": "^3.3.0",
|
||||||
"@storybook/addon-a11y": "^10.2.3",
|
"@storybook/addon-a11y": "^10.2.3",
|
||||||
"@storybook/addon-docs": "^10.2.3",
|
"@storybook/addon-docs": "^10.2.3",
|
||||||
@@ -109,7 +110,7 @@
|
|||||||
"eslint-plugin-storybook": "^10.2.3",
|
"eslint-plugin-storybook": "^10.2.3",
|
||||||
"globals": "^16.5.0",
|
"globals": "^16.5.0",
|
||||||
"lint-staged": "^16.2.7",
|
"lint-staged": "^16.2.7",
|
||||||
"playwright": "^1.58.0",
|
"playwright": "^1.58.2",
|
||||||
"simple-git-hooks": "^2.13.1",
|
"simple-git-hooks": "^2.13.1",
|
||||||
"storybook": "^10.2.3",
|
"storybook": "^10.2.3",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
|
|||||||
20
client/cms/playwright.config.ts
Normal file
20
client/cms/playwright.config.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { defineConfig, devices } from '@playwright/test';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: './tests',
|
||||||
|
fullyParallel: true,
|
||||||
|
forbidOnly: !!process.env.CI,
|
||||||
|
retries: process.env.CI ? 2 : 0,
|
||||||
|
workers: process.env.CI ? 1 : undefined,
|
||||||
|
reporter: 'html',
|
||||||
|
use: {
|
||||||
|
baseURL: 'http://localhost:5173',
|
||||||
|
trace: 'on-first-retry',
|
||||||
|
},
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'chromium',
|
||||||
|
use: { ...devices['Desktop Chrome'] },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
40
client/cms/pnpm-lock.yaml
generated
40
client/cms/pnpm-lock.yaml
generated
@@ -207,6 +207,9 @@ importers:
|
|||||||
'@hey-api/openapi-ts':
|
'@hey-api/openapi-ts':
|
||||||
specifier: 0.91.0
|
specifier: 0.91.0
|
||||||
version: 0.91.0(magicast@0.5.1)(typescript@5.9.3)
|
version: 0.91.0(magicast@0.5.1)(typescript@5.9.3)
|
||||||
|
'@playwright/test':
|
||||||
|
specifier: ^1.58.2
|
||||||
|
version: 1.58.2
|
||||||
'@redux-devtools/extension':
|
'@redux-devtools/extension':
|
||||||
specifier: ^3.3.0
|
specifier: ^3.3.0
|
||||||
version: 3.3.0(redux@5.0.1)
|
version: 3.3.0(redux@5.0.1)
|
||||||
@@ -266,7 +269,7 @@ importers:
|
|||||||
version: 5.1.2(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))
|
version: 5.1.2(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))
|
||||||
'@vitest/browser-playwright':
|
'@vitest/browser-playwright':
|
||||||
specifier: ^4.0.18
|
specifier: ^4.0.18
|
||||||
version: 4.0.18(playwright@1.58.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.18)
|
version: 4.0.18(playwright@1.58.2)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.18)
|
||||||
'@vitest/coverage-v8':
|
'@vitest/coverage-v8':
|
||||||
specifier: ^4.0.18
|
specifier: ^4.0.18
|
||||||
version: 4.0.18(@vitest/browser@4.0.18(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.18))(vitest@4.0.18)
|
version: 4.0.18(@vitest/browser@4.0.18(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.18))(vitest@4.0.18)
|
||||||
@@ -289,8 +292,8 @@ importers:
|
|||||||
specifier: ^16.2.7
|
specifier: ^16.2.7
|
||||||
version: 16.2.7
|
version: 16.2.7
|
||||||
playwright:
|
playwright:
|
||||||
specifier: ^1.58.0
|
specifier: ^1.58.2
|
||||||
version: 1.58.0
|
version: 1.58.2
|
||||||
simple-git-hooks:
|
simple-git-hooks:
|
||||||
specifier: ^2.13.1
|
specifier: ^2.13.1
|
||||||
version: 2.13.1
|
version: 2.13.1
|
||||||
@@ -1125,6 +1128,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==}
|
resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==}
|
||||||
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
|
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
|
||||||
|
|
||||||
|
'@playwright/test@1.58.2':
|
||||||
|
resolution: {integrity: sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
'@polka/url@1.0.0-next.29':
|
'@polka/url@1.0.0-next.29':
|
||||||
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
|
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
|
||||||
|
|
||||||
@@ -4599,13 +4607,13 @@ packages:
|
|||||||
pkg-types@2.3.0:
|
pkg-types@2.3.0:
|
||||||
resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
|
resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
|
||||||
|
|
||||||
playwright-core@1.58.0:
|
playwright-core@1.58.2:
|
||||||
resolution: {integrity: sha512-aaoB1RWrdNi3//rOeKuMiS65UCcgOVljU46At6eFcOFPFHWtd2weHRRow6z/n+Lec0Lvu0k9ZPKJSjPugikirw==}
|
resolution: {integrity: sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
playwright@1.58.0:
|
playwright@1.58.2:
|
||||||
resolution: {integrity: sha512-2SVA0sbPktiIY/MCOPX8e86ehA/e+tDNq+e5Y8qjKYti2Z/JG7xnronT/TXTIkKbYGWlCbuucZ6dziEgkoEjQQ==}
|
resolution: {integrity: sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -6353,6 +6361,10 @@ snapshots:
|
|||||||
|
|
||||||
'@pkgr/core@0.2.9': {}
|
'@pkgr/core@0.2.9': {}
|
||||||
|
|
||||||
|
'@playwright/test@1.58.2':
|
||||||
|
dependencies:
|
||||||
|
playwright: 1.58.2
|
||||||
|
|
||||||
'@polka/url@1.0.0-next.29': {}
|
'@polka/url@1.0.0-next.29': {}
|
||||||
|
|
||||||
'@radix-ui/number@1.1.1': {}
|
'@radix-ui/number@1.1.1': {}
|
||||||
@@ -7291,7 +7303,7 @@ snapshots:
|
|||||||
storybook: 10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
storybook: 10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@vitest/browser': 4.0.18(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.18)
|
'@vitest/browser': 4.0.18(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.18)
|
||||||
'@vitest/browser-playwright': 4.0.18(playwright@1.58.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.18)
|
'@vitest/browser-playwright': 4.0.18(playwright@1.58.2)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.18)
|
||||||
'@vitest/runner': 4.0.18
|
'@vitest/runner': 4.0.18
|
||||||
vitest: 4.0.18(@types/node@25.0.9)(@vitest/browser-playwright@4.0.18)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)
|
vitest: 4.0.18(@types/node@25.0.9)(@vitest/browser-playwright@4.0.18)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -7970,11 +7982,11 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@vitest/browser-playwright@4.0.18(playwright@1.58.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.18)':
|
'@vitest/browser-playwright@4.0.18(playwright@1.58.2)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.18)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/browser': 4.0.18(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.18)
|
'@vitest/browser': 4.0.18(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.18)
|
||||||
'@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))
|
'@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))
|
||||||
playwright: 1.58.0
|
playwright: 1.58.2
|
||||||
tinyrainbow: 3.0.3
|
tinyrainbow: 3.0.3
|
||||||
vitest: 4.0.18(@types/node@25.0.9)(@vitest/browser-playwright@4.0.18)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)
|
vitest: 4.0.18(@types/node@25.0.9)(@vitest/browser-playwright@4.0.18)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -10254,11 +10266,11 @@ snapshots:
|
|||||||
exsolve: 1.0.8
|
exsolve: 1.0.8
|
||||||
pathe: 2.0.3
|
pathe: 2.0.3
|
||||||
|
|
||||||
playwright-core@1.58.0: {}
|
playwright-core@1.58.2: {}
|
||||||
|
|
||||||
playwright@1.58.0:
|
playwright@1.58.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
playwright-core: 1.58.0
|
playwright-core: 1.58.2
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.2
|
fsevents: 2.3.2
|
||||||
|
|
||||||
@@ -11196,7 +11208,7 @@ snapshots:
|
|||||||
why-is-node-running: 2.3.0
|
why-is-node-running: 2.3.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 25.0.9
|
'@types/node': 25.0.9
|
||||||
'@vitest/browser-playwright': 4.0.18(playwright@1.58.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.18)
|
'@vitest/browser-playwright': 4.0.18(playwright@1.58.2)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.18)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- jiti
|
- jiti
|
||||||
- less
|
- less
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { useUserInfo } from '@/hooks/data/useUserInfo';
|
||||||
|
import { CheckinScannerNavView } from './checkin-scanner-nav.view';
|
||||||
|
|
||||||
|
export function CheckinScannerNavContainer() {
|
||||||
|
const { data } = useUserInfo();
|
||||||
|
|
||||||
|
if ((data.data?.permission_level ?? 0) <= 20) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <CheckinScannerNavView />;
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { IconScan } from '@tabler/icons-react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { Dialog, DialogTrigger } from '@/components/ui/dialog';
|
||||||
|
import { SidebarMenuButton, SidebarMenuItem } from '@/components/ui/sidebar';
|
||||||
|
import { CheckinScannerDialogView } from './checkin-scanner.dialog.view';
|
||||||
|
|
||||||
|
export function CheckinScannerNavView() {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const handleScan = (value: string) => {
|
||||||
|
console.log('Scanned:', value);
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<SidebarMenuButton tooltip="扫码签到">
|
||||||
|
<IconScan />
|
||||||
|
<span>扫码签到</span>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</DialogTrigger>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
<CheckinScannerDialogView onScan={handleScan} />
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
} from '@/components/ui/sidebar';
|
} from '@/components/ui/sidebar';
|
||||||
|
|
||||||
export function AppSidebar({ navData, footerWidget, ...props }: React.ComponentProps<typeof Sidebar> & { navData: NavData; footerWidget: React.ReactNode }) {
|
export function AppSidebar({ navData, footerWidget, secondaryNavExtra, ...props }: React.ComponentProps<typeof Sidebar> & { navData: NavData; footerWidget: React.ReactNode; secondaryNavExtra?: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<Sidebar collapsible="offcanvas" {...props}>
|
<Sidebar collapsible="offcanvas" {...props}>
|
||||||
<SidebarHeader>
|
<SidebarHeader>
|
||||||
@@ -33,7 +33,9 @@ export function AppSidebar({ navData, footerWidget, ...props }: React.ComponentP
|
|||||||
</SidebarHeader>
|
</SidebarHeader>
|
||||||
<SidebarContent>
|
<SidebarContent>
|
||||||
<NavMain items={navData.navMain} />
|
<NavMain items={navData.navMain} />
|
||||||
<NavSecondary items={navData.navSecondary} className="mt-auto" />
|
<NavSecondary items={navData.navSecondary} className="mt-auto">
|
||||||
|
{secondaryNavExtra}
|
||||||
|
</NavSecondary>
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
<SidebarFooter>
|
<SidebarFooter>
|
||||||
{footerWidget}
|
{footerWidget}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
|
|
||||||
export function NavSecondary({
|
export function NavSecondary({
|
||||||
items,
|
items,
|
||||||
|
children,
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
items: {
|
items: {
|
||||||
@@ -21,6 +22,7 @@ export function NavSecondary({
|
|||||||
url: string;
|
url: string;
|
||||||
icon: Icon;
|
icon: Icon;
|
||||||
}[];
|
}[];
|
||||||
|
children?: React.ReactNode;
|
||||||
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
|
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
|
||||||
return (
|
return (
|
||||||
<SidebarGroup {...props}>
|
<SidebarGroup {...props}>
|
||||||
@@ -40,6 +42,7 @@ export function NavSecondary({
|
|||||||
</Link>
|
</Link>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
))}
|
))}
|
||||||
|
{children}
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
</SidebarGroupContent>
|
</SidebarGroupContent>
|
||||||
</SidebarGroup>
|
</SidebarGroup>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { createFileRoute, Outlet, useRouterState } from '@tanstack/react-router';
|
import { createFileRoute, Outlet, useRouterState } from '@tanstack/react-router';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
|
import { CheckinScannerNavContainer } from '@/components/checkin/checkin-scanner-nav.container';
|
||||||
import { AppSidebar } from '@/components/sidebar/app-sidebar.view';
|
import { AppSidebar } from '@/components/sidebar/app-sidebar.view';
|
||||||
import { NavUserContainer } from '@/components/sidebar/nav-user.container';
|
import { NavUserContainer } from '@/components/sidebar/nav-user.container';
|
||||||
import { NavUserSkeleton } from '@/components/sidebar/nav-user.skeletion';
|
import { NavUserSkeleton } from '@/components/sidebar/nav-user.skeletion';
|
||||||
@@ -37,6 +38,11 @@ function RouteComponent() {
|
|||||||
<NavUserContainer />
|
<NavUserContainer />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
)}
|
)}
|
||||||
|
secondaryNavExtra={(
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<CheckinScannerNavContainer />
|
||||||
|
</Suspense>
|
||||||
|
)}
|
||||||
variant="inset"
|
variant="inset"
|
||||||
/>
|
/>
|
||||||
<SidebarInset>
|
<SidebarInset>
|
||||||
|
|||||||
66
client/cms/tests/checkin-scanner.spec.ts
Normal file
66
client/cms/tests/checkin-scanner.spec.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
test.describe('Check-in Scanner Navigation', () => {
|
||||||
|
test('should be visible for users with high permission', async ({ page }) => {
|
||||||
|
await page.route('**/user/info', async (route) => {
|
||||||
|
await route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: JSON.stringify({
|
||||||
|
status: 'success',
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
user_id: 'test-user-id',
|
||||||
|
username: 'testuser',
|
||||||
|
nickname: 'Test User',
|
||||||
|
permission_level: 21,
|
||||||
|
avatar: 'https://example.com/avatar.png',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto('/');
|
||||||
|
|
||||||
|
const sidebar = page.locator('[data-slot="sidebar"]');
|
||||||
|
await expect(sidebar).toBeVisible();
|
||||||
|
|
||||||
|
const scannerButton = page.getByRole('button', { name: '扫码签到' });
|
||||||
|
await expect(scannerButton).toBeVisible();
|
||||||
|
|
||||||
|
await scannerButton.click();
|
||||||
|
|
||||||
|
const dialog = page.getByRole('dialog');
|
||||||
|
await expect(dialog).toBeVisible();
|
||||||
|
|
||||||
|
await expect(page.getByText('扫描签到码')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should NOT be visible for users with low permission', async ({ page }) => {
|
||||||
|
await page.route('**/user/info', async (route) => {
|
||||||
|
await route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: JSON.stringify({
|
||||||
|
status: 'success',
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
user_id: 'test-user-id',
|
||||||
|
username: 'testuser',
|
||||||
|
nickname: 'Test User',
|
||||||
|
permission_level: 10,
|
||||||
|
avatar: 'https://example.com/avatar.png',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto('/');
|
||||||
|
|
||||||
|
const sidebar = page.locator('[data-slot="sidebar"]');
|
||||||
|
await expect(sidebar).toBeVisible();
|
||||||
|
|
||||||
|
const scannerButton = page.getByRole('button', { name: '扫码签到' });
|
||||||
|
await expect(scannerButton).not.toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user