mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
fix: harden skill market theme and loading states
This commit is contained in:
parent
3fc6b7f7df
commit
977cd6e87a
@ -2086,6 +2086,7 @@ export const en = {
|
||||
'skillCenter.marketplace.drawer.metadata': 'Metadata',
|
||||
'skillCenter.marketplace.drawer.links': 'Links',
|
||||
'skillCenter.marketplace.loadingCard': 'Loading marketplace skills',
|
||||
'skillCenter.marketplace.loadingDetail': 'Loading skill detail',
|
||||
'skillCenter.marketplace.previewTruncated': 'Truncated',
|
||||
'skillCenter.marketplace.previewUnavailable': 'Preview unavailable',
|
||||
'skillCenter.marketplace.previewUnavailable.skillhub': 'SkillHub does not expose a safe raw file preview endpoint yet.',
|
||||
|
||||
@ -2088,6 +2088,7 @@ export const jp: Record<TranslationKey, string> = {
|
||||
'skillCenter.marketplace.drawer.metadata': 'メタデータ',
|
||||
'skillCenter.marketplace.drawer.links': 'リンク',
|
||||
'skillCenter.marketplace.loadingCard': 'マーケットスキルを読み込み中',
|
||||
'skillCenter.marketplace.loadingDetail': 'スキル詳細を読み込み中',
|
||||
'skillCenter.marketplace.previewTruncated': '切り詰め済み',
|
||||
'skillCenter.marketplace.previewUnavailable': 'プレビュー不可',
|
||||
'skillCenter.marketplace.previewUnavailable.skillhub': 'SkillHub はまだ安全な raw ファイルプレビュー API を提供していません。',
|
||||
|
||||
@ -2088,6 +2088,7 @@ export const kr: Record<TranslationKey, string> = {
|
||||
'skillCenter.marketplace.drawer.metadata': '메타데이터',
|
||||
'skillCenter.marketplace.drawer.links': '링크',
|
||||
'skillCenter.marketplace.loadingCard': '마켓 스킬 로드 중',
|
||||
'skillCenter.marketplace.loadingDetail': '스킬 상세 로드 중',
|
||||
'skillCenter.marketplace.previewTruncated': '잘림',
|
||||
'skillCenter.marketplace.previewUnavailable': '미리보기 불가',
|
||||
'skillCenter.marketplace.previewUnavailable.skillhub': 'SkillHub는 아직 안전한 원본 파일 미리보기 API를 제공하지 않습니다.',
|
||||
|
||||
@ -2088,6 +2088,7 @@ export const zh: Record<TranslationKey, string> = {
|
||||
'skillCenter.marketplace.drawer.metadata': '中繼資料',
|
||||
'skillCenter.marketplace.drawer.links': '連結',
|
||||
'skillCenter.marketplace.loadingCard': '正在載入市場技能',
|
||||
'skillCenter.marketplace.loadingDetail': '正在載入技能詳情',
|
||||
'skillCenter.marketplace.previewTruncated': '已截斷',
|
||||
'skillCenter.marketplace.previewUnavailable': '暫時無法預覽',
|
||||
'skillCenter.marketplace.previewUnavailable.skillhub': 'SkillHub 目前尚未提供安全的原始檔案預覽介面。',
|
||||
|
||||
@ -2088,6 +2088,7 @@ export const zh: Record<TranslationKey, string> = {
|
||||
'skillCenter.marketplace.drawer.metadata': '元数据',
|
||||
'skillCenter.marketplace.drawer.links': '链接',
|
||||
'skillCenter.marketplace.loadingCard': '正在加载市场技能',
|
||||
'skillCenter.marketplace.loadingDetail': '正在加载技能详情',
|
||||
'skillCenter.marketplace.previewTruncated': '已截断',
|
||||
'skillCenter.marketplace.previewUnavailable': '暂时无法预览',
|
||||
'skillCenter.marketplace.previewUnavailable.skillhub': 'SkillHub 目前还没有提供安全的原始文件预览接口。',
|
||||
|
||||
@ -245,10 +245,36 @@ describe('SkillCenter', () => {
|
||||
|
||||
render(<SkillCenter />)
|
||||
|
||||
expect(screen.getByTestId('skill-marketplace-loading')).toBeInTheDocument()
|
||||
const loadingRegion = screen.getByTestId('skill-marketplace-loading')
|
||||
expect(loadingRegion).toBeInTheDocument()
|
||||
expect(loadingRegion).toHaveAttribute('role', 'status')
|
||||
expect(loadingRegion).toHaveAttribute('aria-busy', 'true')
|
||||
expect(screen.getByRole('status', { name: 'Loading marketplace skills' })).toBe(loadingRegion)
|
||||
expect(screen.getAllByTestId('skill-card-skeleton')).toHaveLength(8)
|
||||
})
|
||||
|
||||
it('keeps the detail drawer in the body modal layer for uniform theme masking', async () => {
|
||||
render(<SkillCenter />)
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'PPT Generator' }))
|
||||
|
||||
const detailLayer = await screen.findByTestId('skill-market-detail-layer')
|
||||
expect(detailLayer.parentElement).toBe(document.body)
|
||||
expect(detailLayer).toHaveClass('fixed', 'inset-0', 'z-50')
|
||||
expect(detailLayer.querySelector('.skill-market-detail-scrim')).not.toBeNull()
|
||||
})
|
||||
|
||||
it('announces detail drawer skeleton loading without visible copy', async () => {
|
||||
mockedSkillMarketApi.detail.mockReturnValue(new Promise(() => {}))
|
||||
|
||||
render(<SkillCenter />)
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'PPT Generator' }))
|
||||
|
||||
const detailLayer = screen.getByTestId('skill-market-detail-layer')
|
||||
const detailLoading = within(detailLayer).getByRole('status', { name: 'Loading skill detail' })
|
||||
expect(detailLoading).toHaveAttribute('aria-busy', 'true')
|
||||
expect(within(detailLoading).getByText('Loading skill detail')).toHaveClass('sr-only')
|
||||
})
|
||||
|
||||
it('places install decision, blocking reason, and file preview in the drawer decision area', async () => {
|
||||
mockedSkillMarketApi.detail.mockResolvedValue({
|
||||
detail: makeDetail({
|
||||
@ -267,6 +293,7 @@ describe('SkillCenter', () => {
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'PPT Generator' }))
|
||||
|
||||
const dialog = await screen.findByRole('dialog', { name: 'PPT Generator' })
|
||||
expect(within(dialog).getByRole('heading', { name: 'Install decision' })).toBeInTheDocument()
|
||||
expect(within(dialog).getByRole('button', { name: 'Blocked' })).toBeDisabled()
|
||||
expect(within(dialog).getByText(/full package safety scan/i)).toBeInTheDocument()
|
||||
expect(within(dialog).getByText('File preview')).toBeInTheDocument()
|
||||
|
||||
@ -787,7 +787,13 @@ function SkillMarketDetailPanel({
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="border-b border-[var(--color-border)] bg-[var(--color-surface)] p-5">
|
||||
<div
|
||||
aria-labelledby="skill-market-detail-decision-title"
|
||||
className="border-b border-[var(--color-border)] bg-[var(--color-surface)] p-5"
|
||||
>
|
||||
<h4 id="skill-market-detail-decision-title" className="sr-only">
|
||||
{t('skillCenter.marketplace.drawer.decision')}
|
||||
</h4>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => (canOpenInstalled ? onOpenInstalled(detail) : onInstall())}
|
||||
@ -1097,6 +1103,8 @@ function MarketplaceSkeletonGrid() {
|
||||
return (
|
||||
<div
|
||||
data-testid="skill-marketplace-loading"
|
||||
role="status"
|
||||
aria-busy="true"
|
||||
aria-label={t('skillCenter.marketplace.loadingCard')}
|
||||
className="grid gap-2.5 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5"
|
||||
>
|
||||
@ -1120,8 +1128,16 @@ function MarketplaceSkeletonGrid() {
|
||||
}
|
||||
|
||||
function DetailDrawerSkeleton() {
|
||||
const t = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
<div
|
||||
role="status"
|
||||
aria-busy="true"
|
||||
aria-label={t('skillCenter.marketplace.loadingDetail')}
|
||||
className="flex h-full flex-col"
|
||||
>
|
||||
<span className="sr-only">{t('skillCenter.marketplace.loadingDetail')}</span>
|
||||
<div className="border-b border-[var(--color-border)] px-5 py-4">
|
||||
<div className="h-5 w-28 animate-pulse rounded bg-[var(--color-surface-container-high)]" />
|
||||
<div className="mt-3 h-6 w-2/3 animate-pulse rounded bg-[var(--color-surface-container-high)]" />
|
||||
|
||||
@ -1082,6 +1082,13 @@ button, input, textarea, select, a, [role="button"] {
|
||||
-webkit-backdrop-filter: blur(1.5px);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.skill-market-detail-scrim {
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-shell {
|
||||
width: var(--sidebar-width);
|
||||
flex-shrink: 0;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user