mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-03 14:37:00 +08:00
test: introduce frontend unit testing foundation (#529)
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
import {
|
||||
ADMIN_PERMISSIONS,
|
||||
buildDefaultFeaturePermissions,
|
||||
buildPluginPermissionFeatureKey,
|
||||
buildUserPermissionContext,
|
||||
DEFAULT_PERMISSIONS,
|
||||
filterItemsByPermission,
|
||||
filterMenusByPermission,
|
||||
hasAllPermissions,
|
||||
hasAnyPermission,
|
||||
hasFeaturePermission,
|
||||
hasItemPermission,
|
||||
hasPermission,
|
||||
normalizeUserPermissions,
|
||||
PERMISSION_FEATURE,
|
||||
USER_PERMISSION_FEATURES,
|
||||
type UserPermissionFeatureMap,
|
||||
} from '@/utils/permission'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
describe('permission utilities', () => {
|
||||
it('normalizes legacy permissions and filters invalid feature values', () => {
|
||||
const normalized = normalizeUserPermissions({
|
||||
discovery: false,
|
||||
features: {
|
||||
enabled: true,
|
||||
invalid: 'yes',
|
||||
} as unknown as UserPermissionFeatureMap,
|
||||
})
|
||||
|
||||
expect(normalized).toEqual({
|
||||
...DEFAULT_PERMISSIONS,
|
||||
discovery: false,
|
||||
features: { enabled: true },
|
||||
})
|
||||
expect(normalizeUserPermissions(null)).toEqual(DEFAULT_PERMISSIONS)
|
||||
})
|
||||
|
||||
it('builds default and plugin feature contracts', () => {
|
||||
const disabledFeatures = buildDefaultFeaturePermissions(false)
|
||||
|
||||
expect(Object.keys(disabledFeatures)).toHaveLength(USER_PERMISSION_FEATURES.length)
|
||||
expect(Object.values(disabledFeatures).every(enabled => enabled === false)).toBe(true)
|
||||
expect(buildPluginPermissionFeatureKey('demo')).toBe('plugin.demo.main')
|
||||
expect(buildPluginPermissionFeatureKey('demo', 'settings')).toBe('plugin.demo.settings')
|
||||
expect(ADMIN_PERMISSIONS.features?.[PERMISSION_FEATURE.MANAGE_SITE]).toBe(true)
|
||||
})
|
||||
|
||||
it('grants every category and admin entry only to superusers', () => {
|
||||
const superuser = buildUserPermissionContext(true, {})
|
||||
|
||||
expect(hasPermission(superuser, 'admin')).toBe(true)
|
||||
expect(hasPermission(superuser, 'manage')).toBe(true)
|
||||
expect(hasFeaturePermission(superuser, PERMISSION_FEATURE.MANAGE_SITE, 'manage')).toBe(true)
|
||||
expect(hasPermission({ admin: true, manage: true }, 'admin')).toBe(false)
|
||||
})
|
||||
|
||||
it('checks category permissions as explicit booleans', () => {
|
||||
const permissions = { discovery: true, search: false, subscribe: 1, manage: undefined }
|
||||
|
||||
expect(hasPermission(permissions, 'discovery')).toBe(true)
|
||||
expect(hasPermission(permissions, 'search')).toBe(false)
|
||||
expect(hasPermission(permissions, 'subscribe')).toBe(false)
|
||||
expect(hasPermission(null, 'manage')).toBe(false)
|
||||
expect(hasAnyPermission(permissions, ['search', 'discovery'])).toBe(true)
|
||||
expect(hasAllPermissions(permissions, ['discovery', 'search'])).toBe(false)
|
||||
})
|
||||
|
||||
it('inherits missing feature flags but honors explicit denial and parent categories', () => {
|
||||
const legacyUser = { discovery: true }
|
||||
const restrictedUser = {
|
||||
discovery: true,
|
||||
features: { [PERMISSION_FEATURE.DISCOVERY_RECOMMEND]: false },
|
||||
}
|
||||
|
||||
expect(hasFeaturePermission(legacyUser)).toBe(true)
|
||||
expect(hasFeaturePermission(legacyUser, PERMISSION_FEATURE.DISCOVERY_RECOMMEND, 'discovery')).toBe(true)
|
||||
expect(hasFeaturePermission(restrictedUser, PERMISSION_FEATURE.DISCOVERY_RECOMMEND, 'discovery')).toBe(false)
|
||||
expect(
|
||||
hasFeaturePermission(
|
||||
{ discovery: false, features: { [PERMISSION_FEATURE.DISCOVERY_RECOMMEND]: true } },
|
||||
PERMISSION_FEATURE.DISCOVERY_RECOMMEND,
|
||||
'discovery',
|
||||
),
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('checks and filters permission-protected items consistently', () => {
|
||||
const permissions = {
|
||||
discovery: true,
|
||||
manage: false,
|
||||
features: { [PERMISSION_FEATURE.DISCOVERY_EXPLORE]: false },
|
||||
}
|
||||
const items = [
|
||||
{ id: 'open' },
|
||||
{ id: 'recommend', permission: 'discovery' as const, feature: PERMISSION_FEATURE.DISCOVERY_RECOMMEND },
|
||||
{ id: 'explore', permission: 'discovery' as const, feature: PERMISSION_FEATURE.DISCOVERY_EXPLORE },
|
||||
{ id: 'manage', permission: 'manage' as const },
|
||||
]
|
||||
|
||||
expect(hasItemPermission(items[1], permissions)).toBe(true)
|
||||
expect(hasItemPermission(items[2], permissions)).toBe(false)
|
||||
expect(filterItemsByPermission(items, permissions).map(item => item.id)).toEqual(['open', 'recommend'])
|
||||
expect(filterMenusByPermission(items, permissions).map(item => item.id)).toEqual(['open', 'recommend'])
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,61 @@
|
||||
import type { RecommendSource } from '@/api/types'
|
||||
import {
|
||||
createBuiltInRecommendSources,
|
||||
mergeExtraRecommendSources,
|
||||
type RecommendViewSource,
|
||||
} from '@/utils/recommendSources'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
const translate = (key: string) => `translated:${key}`
|
||||
|
||||
describe('recommendSources', () => {
|
||||
it('creates the complete built-in source contract', () => {
|
||||
const sources = createBuiltInRecommendSources(translate)
|
||||
|
||||
expect(sources).toHaveLength(13)
|
||||
expect(sources[0]).toEqual({
|
||||
apipath: 'recommend/tmdb_trending',
|
||||
linkurl: '/browse/recommend/tmdb_trending?title=translated:recommend.trendingNow',
|
||||
title: 'translated:recommend.trendingNow',
|
||||
type: 'translated:recommend.categoryRankings',
|
||||
})
|
||||
expect(sources).toContainEqual(
|
||||
expect.objectContaining({
|
||||
apipath: 'recommend/tmdb_tvs?with_original_language=zh|en|ja|ko',
|
||||
linkurl:
|
||||
'/browse/recommend/tmdb_tvs?with_original_language=zh|en|ja|ko&title=translated:recommend.tmdbHotTVShows',
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('appends extra sources in order and skips duplicate API paths', () => {
|
||||
const target = createBuiltInRecommendSources(translate).slice(0, 1)
|
||||
const extras: RecommendSource[] = [
|
||||
{ api_path: 'recommend/tmdb_trending', name: '重复来源', type: '榜单' },
|
||||
{ api_path: 'recommend/custom', name: '自定义来源', type: '扩展' },
|
||||
{ api_path: 'recommend/custom', name: '重复扩展', type: '扩展' },
|
||||
]
|
||||
|
||||
mergeExtraRecommendSources(target, extras)
|
||||
|
||||
expect(target).toHaveLength(2)
|
||||
expect(target[1]).toMatchObject({
|
||||
apipath: 'recommend/custom',
|
||||
title: '自定义来源',
|
||||
type: '扩展',
|
||||
})
|
||||
})
|
||||
|
||||
it('uses the correct query separator and encodes source names', () => {
|
||||
const target: RecommendViewSource[] = []
|
||||
const extras: RecommendSource[] = [
|
||||
{ api_path: 'recommend/custom', name: '中文 & special', type: '扩展' },
|
||||
{ api_path: 'recommend/filtered?genre=1', name: '筛选/来源', type: '扩展' },
|
||||
]
|
||||
|
||||
mergeExtraRecommendSources(target, extras)
|
||||
|
||||
expect(target[0].linkurl).toBe('/browse/recommend/custom?title=%E4%B8%AD%E6%96%87%20%26%20special')
|
||||
expect(target[1].linkurl).toBe('/browse/recommend/filtered?genre=1&title=%E7%AD%9B%E9%80%89%2F%E6%9D%A5%E6%BA%90')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user