继续收口首页分类异步状态
扩展 PlatformAsyncStatePanel 接入首页分类分支的外层与筛选空态 合并桌面分类 section 并补充首页分类状态回归测试 修正 FlowShell 发现频道切换测试的 tab 语义断言 更新 PlatformUiKit 收口计划与共享决策记录
This commit is contained in:
@@ -7561,7 +7561,7 @@ test('published puzzle works appear on home and mobile game category channel', a
|
||||
});
|
||||
|
||||
await clickFirstButtonByName(user, '发现');
|
||||
await user.click(screen.getByRole('button', { name: '分类' }));
|
||||
await user.click(screen.getByRole('tab', { name: '分类' }));
|
||||
|
||||
const discoverPanel = getPlatformTabPanel('category');
|
||||
expect(within(discoverPanel).getAllByText('星桥机关').length).toBeGreaterThan(
|
||||
@@ -8533,7 +8533,7 @@ test('published big fish works stay hidden from platform home and game category
|
||||
expect(screen.queryByText('机械深海 大鱼吃小鱼')).toBeNull();
|
||||
|
||||
await clickFirstButtonByName(user, '发现');
|
||||
await user.click(screen.getByRole('button', { name: '分类' }));
|
||||
await user.click(screen.getByRole('tab', { name: '分类' }));
|
||||
|
||||
const discoverPanel = getPlatformTabPanel('category');
|
||||
expect(within(discoverPanel).queryByText('机械深海 大鱼吃小鱼')).toBeNull();
|
||||
@@ -8573,7 +8573,7 @@ test('published puzzle detail returns to the ranking platform tab', async () =>
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await clickFirstButtonByName(user, '发现');
|
||||
await user.click(await screen.findByRole('button', { name: '排行' }));
|
||||
await user.click(await screen.findByRole('tab', { name: '排行' }));
|
||||
await waitFor(() => {
|
||||
expect(document.getElementById('platform-tab-panel-category')).toBeTruthy();
|
||||
});
|
||||
|
||||
@@ -911,6 +911,7 @@ function renderLoggedOutHomeView(
|
||||
| 'recommendRuntimeError'
|
||||
| 'onSelectNextRecommendEntry'
|
||||
| 'onSelectPreviousRecommendEntry'
|
||||
| 'isLoadingPlatform'
|
||||
>
|
||||
> = {},
|
||||
activeTab: RpgEntryHomeViewProps['activeTab'] = 'home',
|
||||
@@ -949,7 +950,7 @@ function renderLoggedOutHomeView(
|
||||
myEntries={[]}
|
||||
historyEntries={overrides.historyEntries ?? []}
|
||||
profileDashboard={null}
|
||||
isLoadingPlatform={false}
|
||||
isLoadingPlatform={overrides.isLoadingPlatform ?? false}
|
||||
isLoadingDashboard={false}
|
||||
isResumingSaveWorldKey={null}
|
||||
platformError={null}
|
||||
@@ -5488,6 +5489,52 @@ test('mobile game category filter dialog filters by play type', async () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('mobile game category keeps filter controls when current filter becomes empty', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
renderStatefulLoggedOutHomeView({
|
||||
latestEntries: [puzzlePublicEntry],
|
||||
});
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '发现' }));
|
||||
await user.click(screen.getByRole('tab', { name: '分类' }));
|
||||
await user.click(screen.getByRole('button', { name: '奇幻' }));
|
||||
await user.click(screen.getByRole('button', { name: /筛选/u }));
|
||||
await user.click(screen.getByRole('button', { name: '抓鹅' }));
|
||||
|
||||
expect(screen.getByText('当前筛选下没有作品。')).toBeTruthy();
|
||||
expect(screen.getByRole('button', { name: /筛选/u })).toBeTruthy();
|
||||
expect(screen.getByRole('button', { name: '奇幻' })).toBeTruthy();
|
||||
expect(document.querySelector('.platform-category-sort-button')).toBeTruthy();
|
||||
expect(screen.queryByRole('button', { name: /奇幻拼图,试玩/u })).toBeNull();
|
||||
expect(document.querySelector('.platform-category-game-item')).toBeNull();
|
||||
});
|
||||
|
||||
test('desktop discover category shows loading state before category data is ready', async () => {
|
||||
const user = userEvent.setup();
|
||||
mockDesktopLayout();
|
||||
|
||||
renderLoggedOutHomeView(
|
||||
vi.fn(),
|
||||
{
|
||||
isLoadingPlatform: true,
|
||||
},
|
||||
'category',
|
||||
true,
|
||||
);
|
||||
|
||||
const channelBar = document.querySelector('.platform-mobile-home-channelbar');
|
||||
if (!channelBar) {
|
||||
throw new Error('缺少发现频道栏');
|
||||
}
|
||||
|
||||
await user.click(within(channelBar as HTMLElement).getByRole('tab', { name: '分类' }));
|
||||
|
||||
expect(screen.getByText('正在读取作品分类...')).toBeTruthy();
|
||||
expect(screen.queryByRole('button', { name: /筛选/u })).toBeNull();
|
||||
expect(document.querySelector('.platform-category-game-item')).toBeNull();
|
||||
});
|
||||
|
||||
test('bottom category tab becomes ranking and switches ranking metrics', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
|
||||
@@ -3239,6 +3239,149 @@ export function RpgEntryHomeView({
|
||||
);
|
||||
const desktopFeaturedGrid = desktopRecommendEntries.slice(0, 4);
|
||||
const desktopCategoryGrid = activeCategoryEntries.slice(0, 6);
|
||||
const mobileCategoryPanelContent = activeCategoryGroup ? (
|
||||
<>
|
||||
<div className="platform-category-filter-row">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsCategoryFilterPanelOpen(true)}
|
||||
aria-haspopup="dialog"
|
||||
className="platform-category-filter-button"
|
||||
>
|
||||
<SlidersHorizontal className="h-4 w-4" />
|
||||
<span>{categoryFilterApplied ? activeCategoryFilterLabel : '筛选'}</span>
|
||||
<span className="platform-category-filter-button__count">
|
||||
{activeCategoryFilterCount}
|
||||
</span>
|
||||
</button>
|
||||
<span className="platform-category-filter-divider" />
|
||||
<PlatformSegmentedTabs
|
||||
items={categoryGroupTabs}
|
||||
activeId={activeCategoryGroup.tag}
|
||||
onChange={handleCategoryGroupChange}
|
||||
layout="scroll"
|
||||
gap="md"
|
||||
frame="bare"
|
||||
surface="transparent"
|
||||
size="sm"
|
||||
tone="neutral"
|
||||
className="platform-category-chip-scroll min-w-0 flex-1"
|
||||
itemClassName={(_, active) =>
|
||||
[
|
||||
'platform-category-chip shrink-0 !min-h-[2.35rem] !rounded-none !border-0 !bg-transparent !px-0 !shadow-none hover:!bg-transparent',
|
||||
active ? 'platform-category-chip--active' : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={cycleCategorySortMode}
|
||||
className="platform-category-sort-button"
|
||||
>
|
||||
<span>按{activeCategorySortLabel}排序</span>
|
||||
<ChevronDown className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
|
||||
<PlatformAsyncStatePanel
|
||||
isEmpty={activeCategoryEntries.length === 0}
|
||||
emptyState={<PlatformEmptyState>当前筛选下没有作品。</PlatformEmptyState>}
|
||||
>
|
||||
<div className="platform-category-game-list">
|
||||
{activeCategoryEntries.map((entry) => (
|
||||
<PlatformCategoryGameItem
|
||||
key={`${buildPublicGalleryCardKey(entry)}:mobile-category:${activeCategoryGroup.tag}:${categoryKindFilter}:${categorySortMode}`}
|
||||
entry={entry}
|
||||
categoryTag={activeCategoryGroup.tag}
|
||||
onClick={() => onOpenGalleryDetail(entry)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</PlatformAsyncStatePanel>
|
||||
</>
|
||||
) : null;
|
||||
const renderDesktopCategorySection = (cardKeyPrefix: string) => {
|
||||
const desktopCategoryPanelContent = activeCategoryGroup ? (
|
||||
<>
|
||||
<div className="mb-4 flex min-w-0 items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsCategoryFilterPanelOpen(true)}
|
||||
aria-haspopup="dialog"
|
||||
className="platform-category-filter-button"
|
||||
>
|
||||
<SlidersHorizontal className="h-4 w-4" />
|
||||
<span>{categoryFilterApplied ? activeCategoryFilterLabel : '筛选'}</span>
|
||||
<span className="platform-category-filter-button__count">
|
||||
{activeCategoryFilterCount}
|
||||
</span>
|
||||
</button>
|
||||
<PlatformSegmentedTabs
|
||||
items={categoryGroupTabs}
|
||||
activeId={activeCategoryGroup.tag}
|
||||
onChange={handleCategoryGroupChange}
|
||||
layout="scroll"
|
||||
gap="md"
|
||||
frame="bare"
|
||||
surface="transparent"
|
||||
size="sm"
|
||||
tone="neutral"
|
||||
className="min-w-0 flex-1 pb-1"
|
||||
itemClassName={(_, active) =>
|
||||
[
|
||||
'platform-category-chip shrink-0 !min-h-[2.35rem] !rounded-none !border-0 !bg-transparent !px-0 !shadow-none hover:!bg-transparent',
|
||||
active ? 'platform-category-chip--active' : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={cycleCategorySortMode}
|
||||
className="platform-category-sort-button shrink-0"
|
||||
>
|
||||
<span>{activeCategorySortLabel}</span>
|
||||
<ChevronDown className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
<PlatformAsyncStatePanel
|
||||
isEmpty={desktopCategoryGrid.length === 0}
|
||||
emptyState={<PlatformEmptyState>当前筛选下没有作品。</PlatformEmptyState>}
|
||||
>
|
||||
<div className="grid gap-4 xl:grid-cols-3">
|
||||
{desktopCategoryGrid.map((entry) => (
|
||||
<WorldCard
|
||||
key={`${buildPublicGalleryCardKey(entry)}:${cardKeyPrefix}:${activeCategoryGroup.tag}:${categoryKindFilter}:${categorySortMode}`}
|
||||
entry={entry}
|
||||
onClick={() => openRecommendGalleryDetail(entry)}
|
||||
className="w-full min-w-0"
|
||||
authorAvatarUrl={getPublicEntryAuthorAvatarUrl(entry)}
|
||||
authorSummary={getPublicEntryAuthorSummary(entry)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</PlatformAsyncStatePanel>
|
||||
</>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<section className="platform-desktop-panel px-5 py-5">
|
||||
<SectionHeader title="作品分类" detail="GAME CATEGORY" />
|
||||
<PlatformAsyncStatePanel
|
||||
isLoading={isLoadingPlatform}
|
||||
loadingState={<PlatformEmptyState>正在读取作品分类...</PlatformEmptyState>}
|
||||
isEmpty={!activeCategoryGroup || activeCategoryRawCount === 0}
|
||||
emptyState={<PlatformEmptyState>暂时还没有可分类的作品。</PlatformEmptyState>}
|
||||
>
|
||||
{desktopCategoryPanelContent}
|
||||
</PlatformAsyncStatePanel>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
const desktopLibraryPreview = myEntries.slice(0, 2);
|
||||
const resolvedRecommendCoverUrls = useResolvedRecommendCoverImages(
|
||||
recommendedFeedEntries,
|
||||
@@ -3753,81 +3896,18 @@ export function RpgEntryHomeView({
|
||||
mobileRankingPanel
|
||||
) : discoverChannel === 'category' ? (
|
||||
<section className="platform-category-list-panel">
|
||||
{isLoadingPlatform ? (
|
||||
<PlatformEmptyState>正在读取公开作品...</PlatformEmptyState>
|
||||
) : categoryGroups.length > 0 && activeCategoryGroup ? (
|
||||
<>
|
||||
<div className="platform-category-filter-row">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsCategoryFilterPanelOpen(true)}
|
||||
aria-haspopup="dialog"
|
||||
className="platform-category-filter-button"
|
||||
>
|
||||
<SlidersHorizontal className="h-4 w-4" />
|
||||
<span>
|
||||
{categoryFilterApplied
|
||||
? activeCategoryFilterLabel
|
||||
: '筛选'}
|
||||
</span>
|
||||
<span className="platform-category-filter-button__count">
|
||||
{activeCategoryFilterCount}
|
||||
</span>
|
||||
</button>
|
||||
<span className="platform-category-filter-divider" />
|
||||
<PlatformSegmentedTabs
|
||||
items={categoryGroupTabs}
|
||||
activeId={activeCategoryGroup.tag}
|
||||
onChange={handleCategoryGroupChange}
|
||||
layout="scroll"
|
||||
gap="md"
|
||||
frame="bare"
|
||||
surface="transparent"
|
||||
size="sm"
|
||||
tone="neutral"
|
||||
className="platform-category-chip-scroll min-w-0 flex-1"
|
||||
itemClassName={(_, active) =>
|
||||
[
|
||||
'platform-category-chip shrink-0 !min-h-[2.35rem] !rounded-none !border-0 !bg-transparent !px-0 !shadow-none hover:!bg-transparent',
|
||||
active ? 'platform-category-chip--active' : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={cycleCategorySortMode}
|
||||
className="platform-category-sort-button"
|
||||
>
|
||||
<span>按{activeCategorySortLabel}排序</span>
|
||||
<ChevronDown className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
|
||||
{activeCategoryEntries.length > 0 ? (
|
||||
<div className="platform-category-game-list">
|
||||
{activeCategoryEntries.map((entry) => (
|
||||
<PlatformCategoryGameItem
|
||||
key={`${buildPublicGalleryCardKey(entry)}:mobile-category:${activeCategoryGroup.tag}:${categoryKindFilter}:${categorySortMode}`}
|
||||
entry={entry}
|
||||
categoryTag={activeCategoryGroup.tag}
|
||||
onClick={() => onOpenGalleryDetail(entry)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<PlatformEmptyState>
|
||||
当前筛选下没有作品。
|
||||
</PlatformEmptyState>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<PlatformEmptyState>
|
||||
公开广场暂时还没有可分类的作品。
|
||||
</PlatformEmptyState>
|
||||
)}
|
||||
<PlatformAsyncStatePanel
|
||||
isLoading={isLoadingPlatform}
|
||||
loadingState={<PlatformEmptyState>正在读取公开作品...</PlatformEmptyState>}
|
||||
isEmpty={!activeCategoryGroup || categoryGroups.length === 0}
|
||||
emptyState={
|
||||
<PlatformEmptyState>
|
||||
公开广场暂时还没有可分类的作品。
|
||||
</PlatformEmptyState>
|
||||
}
|
||||
>
|
||||
{mobileCategoryPanelContent}
|
||||
</PlatformAsyncStatePanel>
|
||||
</section>
|
||||
) : discoverChannel === 'edutainment' ? (
|
||||
<section className="platform-mobile-home-feed">
|
||||
@@ -3983,77 +4063,7 @@ export function RpgEntryHomeView({
|
||||
{discoverChannel === 'ranking' ? (
|
||||
mobileRankingPanel
|
||||
) : discoverChannel === 'category' ? (
|
||||
<section className="platform-desktop-panel px-5 py-5">
|
||||
<SectionHeader title="作品分类" detail="GAME CATEGORY" />
|
||||
{isLoadingPlatform ? (
|
||||
<PlatformEmptyState>正在读取作品分类...</PlatformEmptyState>
|
||||
) : activeCategoryGroup && activeCategoryRawCount > 0 ? (
|
||||
<>
|
||||
<div className="mb-4 flex min-w-0 items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsCategoryFilterPanelOpen(true)}
|
||||
aria-haspopup="dialog"
|
||||
className="platform-category-filter-button"
|
||||
>
|
||||
<SlidersHorizontal className="h-4 w-4" />
|
||||
<span>
|
||||
{categoryFilterApplied ? activeCategoryFilterLabel : '筛选'}
|
||||
</span>
|
||||
<span className="platform-category-filter-button__count">
|
||||
{activeCategoryFilterCount}
|
||||
</span>
|
||||
</button>
|
||||
<PlatformSegmentedTabs
|
||||
items={categoryGroupTabs}
|
||||
activeId={activeCategoryGroup.tag}
|
||||
onChange={handleCategoryGroupChange}
|
||||
layout="scroll"
|
||||
gap="md"
|
||||
frame="bare"
|
||||
surface="transparent"
|
||||
size="sm"
|
||||
tone="neutral"
|
||||
className="min-w-0 flex-1 pb-1"
|
||||
itemClassName={(_, active) =>
|
||||
[
|
||||
'platform-category-chip shrink-0 !min-h-[2.35rem] !rounded-none !border-0 !bg-transparent !px-0 !shadow-none hover:!bg-transparent',
|
||||
active ? 'platform-category-chip--active' : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={cycleCategorySortMode}
|
||||
className="platform-category-sort-button shrink-0"
|
||||
>
|
||||
<span>{activeCategorySortLabel}</span>
|
||||
<ChevronDown className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
{desktopCategoryGrid.length > 0 ? (
|
||||
<div className="grid gap-4 xl:grid-cols-3">
|
||||
{desktopCategoryGrid.map((entry) => (
|
||||
<WorldCard
|
||||
key={`${buildPublicGalleryCardKey(entry)}:desktop-discover-category:${activeCategoryGroup.tag}:${categoryKindFilter}:${categorySortMode}`}
|
||||
entry={entry}
|
||||
onClick={() => openRecommendGalleryDetail(entry)}
|
||||
className="w-full min-w-0"
|
||||
authorAvatarUrl={getPublicEntryAuthorAvatarUrl(entry)}
|
||||
authorSummary={getPublicEntryAuthorSummary(entry)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<PlatformEmptyState>当前筛选下没有作品。</PlatformEmptyState>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<PlatformEmptyState>暂时还没有可分类的作品。</PlatformEmptyState>
|
||||
)}
|
||||
</section>
|
||||
renderDesktopCategorySection('desktop-discover-category')
|
||||
) : discoverChannel === 'edutainment' ? (
|
||||
<section className="platform-desktop-panel px-5 py-5">
|
||||
<SectionHeader title={EDUTAINMENT_WORK_TAG} detail="EDUTAINMENT" />
|
||||
@@ -4816,79 +4826,7 @@ export function RpgEntryHomeView({
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<section className="platform-desktop-panel px-5 py-5">
|
||||
<SectionHeader title="作品分类" detail="GAME CATEGORY" />
|
||||
{isLoadingPlatform ? (
|
||||
<PlatformEmptyState>正在读取作品分类...</PlatformEmptyState>
|
||||
) : activeCategoryGroup && activeCategoryRawCount > 0 ? (
|
||||
<>
|
||||
<div className="mb-4 flex min-w-0 items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsCategoryFilterPanelOpen(true)}
|
||||
aria-haspopup="dialog"
|
||||
className="platform-category-filter-button"
|
||||
>
|
||||
<SlidersHorizontal className="h-4 w-4" />
|
||||
<span>
|
||||
{categoryFilterApplied
|
||||
? activeCategoryFilterLabel
|
||||
: '筛选'}
|
||||
</span>
|
||||
<span className="platform-category-filter-button__count">
|
||||
{activeCategoryFilterCount}
|
||||
</span>
|
||||
</button>
|
||||
<PlatformSegmentedTabs
|
||||
items={categoryGroupTabs}
|
||||
activeId={activeCategoryGroup.tag}
|
||||
onChange={handleCategoryGroupChange}
|
||||
layout="scroll"
|
||||
gap="md"
|
||||
frame="bare"
|
||||
surface="transparent"
|
||||
size="sm"
|
||||
tone="neutral"
|
||||
className="min-w-0 flex-1 pb-1"
|
||||
itemClassName={(_, active) =>
|
||||
[
|
||||
'platform-category-chip shrink-0 !min-h-[2.35rem] !rounded-none !border-0 !bg-transparent !px-0 !shadow-none hover:!bg-transparent',
|
||||
active ? 'platform-category-chip--active' : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={cycleCategorySortMode}
|
||||
className="platform-category-sort-button shrink-0"
|
||||
>
|
||||
<span>{activeCategorySortLabel}</span>
|
||||
<ChevronDown className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
{desktopCategoryGrid.length > 0 ? (
|
||||
<div className="grid gap-4 xl:grid-cols-3">
|
||||
{desktopCategoryGrid.map((entry) => (
|
||||
<WorldCard
|
||||
key={`${buildPublicGalleryCardKey(entry)}:desktop-category:${activeCategoryGroup.tag}:${categoryKindFilter}:${categorySortMode}`}
|
||||
entry={entry}
|
||||
onClick={() => openRecommendGalleryDetail(entry)}
|
||||
className="w-full min-w-0"
|
||||
authorAvatarUrl={getPublicEntryAuthorAvatarUrl(entry)}
|
||||
authorSummary={getPublicEntryAuthorSummary(entry)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<PlatformEmptyState>当前筛选下没有作品。</PlatformEmptyState>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<PlatformEmptyState>暂时还没有可分类的作品。</PlatformEmptyState>
|
||||
)}
|
||||
</section>
|
||||
{renderDesktopCategorySection('desktop-category')}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user