移动端仅保留我的页签

移动端底部导航只展示我的页签

隐藏移动端活动页签时回落到我的页

更新首页页签相关组件测试
This commit is contained in:
2026-06-24 21:50:33 +08:00
parent 4eb7bca536
commit 829ff53526
2 changed files with 83 additions and 98 deletions
@@ -1142,14 +1142,19 @@ function renderStatefulLoggedOutHomeView(
>
> = {},
isDesktopLayout = false,
options: {
initialActiveTab?: RpgEntryHomeViewProps['activeTab'];
allowHiddenActiveTab?: boolean;
} = {},
) {
const authSpies = {
openLoginModal: vi.fn(),
};
function StatefulLoggedOutHomeView() {
const [activeTab, setActiveTab] =
useState<RpgEntryHomeViewProps['activeTab']>('category');
const [activeTab, setActiveTab] = useState<
RpgEntryHomeViewProps['activeTab']
>(options.initialActiveTab ?? 'category');
return (
<AuthUiContext.Provider
@@ -1175,6 +1180,7 @@ function renderStatefulLoggedOutHomeView(
activeTab={activeTab}
isDesktopLayout={isDesktopLayout}
onTabChange={setActiveTab}
allowHiddenActiveTab={options.allowHiddenActiveTab ?? true}
hasSavedGame={false}
savedSnapshot={null}
saveEntries={[]}
@@ -3782,22 +3788,23 @@ test('shows a reachable login entry outside mobile recommend tab', async () => {
expect(openLoginModal).toHaveBeenCalledTimes(1);
});
test('logged out bottom nav turns active recommend tab into next action', () => {
const { container } = renderLoggedOutHomeView(vi.fn());
test('logged out mobile bottom nav only shows profile tab', () => {
const { container } = renderLoggedOutHomeView(vi.fn(), {}, 'profile');
const nav = container.querySelector('.platform-bottom-nav');
expect(nav).toBeTruthy();
expect(nav?.classList.contains('platform-bottom-nav')).toBe(true);
expect(nav?.classList.contains('grid-cols-1')).toBe(true);
const buttons = within(nav as HTMLElement).getAllByRole('button');
expect(buttons.map((button) => button.textContent)).toEqual([
'下一个',
'发现',
'我的',
]);
expect(buttons[0]?.querySelector('.lucide-chevron-down')).toBeTruthy();
expect(buttons[1]?.querySelector('.lucide-compass')).toBeTruthy();
expect(buttons[2]?.querySelector('.lucide-user-round')).toBeTruthy();
expect(buttons.map((button) => button.textContent)).toEqual(['我的']);
expect(buttons[0]?.querySelector('.lucide-user-round')).toBeTruthy();
expect(
within(nav as HTMLElement).queryByRole('button', { name: '推荐' }),
).toBeNull();
expect(
within(nav as HTMLElement).queryByRole('button', { name: '发现' }),
).toBeNull();
expect(
within(nav as HTMLElement).queryByRole('button', { name: '创作' }),
).toBeNull();
@@ -3809,24 +3816,24 @@ test('logged out bottom nav turns active recommend tab into next action', () =>
).toBeTruthy();
});
test('logged in mobile bottom nav hides draft and project entries', () => {
test('logged in mobile bottom nav only shows profile tab', () => {
const { container } = renderLoggedInHomeView({
activeTab: 'profile',
hasUnreadDraftUpdate: true,
draftTabContent: <div>稿</div>,
});
const nav = container.querySelector('.platform-bottom-nav');
expect(nav).toBeTruthy();
expect(nav?.classList.contains('grid-cols-1')).toBe(true);
const buttons = within(nav as HTMLElement).getAllByRole('button');
expect(buttons.map((button) => button.textContent)).toEqual(['我的']);
expect(
within(nav as HTMLElement).getByRole('button', { name: '推荐' }),
).toBeTruthy();
within(nav as HTMLElement).queryByRole('button', { name: '推荐' }),
).toBeNull();
expect(
within(nav as HTMLElement).getByRole('button', { name: '发现' }),
).toBeTruthy();
expect(
within(nav as HTMLElement).getByRole('button', { name: '我的' }),
).toBeTruthy();
within(nav as HTMLElement).queryByRole('button', { name: '发现' }),
).toBeNull();
expect(
within(nav as HTMLElement).queryByRole('button', {
name: '草稿,有新草稿',
@@ -4106,7 +4113,6 @@ test('mobile discover search submits public work code', async () => {
const onSearchPublicCode = vi.fn();
renderStatefulLoggedOutHomeView({ onSearchPublicCode });
await user.click(screen.getByRole('button', { name: '发现' }));
const searchInput =
screen.getByPlaceholderText('搜索作品号、名称、作者、描述');
@@ -4145,7 +4151,6 @@ test('discover search fuzzy matches public work id, name, author and description
onOpenGalleryDetail,
onSearchPublicCode,
});
await user.click(screen.getByRole('button', { name: '发现' }));
const discoverPanel = document.getElementById('platform-tab-panel-category');
if (!discoverPanel) {
throw new Error('缺少发现面板');
@@ -4212,7 +4217,6 @@ test('mobile discover keeps edutainment works in the last dedicated channel only
onOpenChildMotionDemo,
onSearchPublicCode,
});
await user.click(screen.getByRole('button', { name: '发现' }));
const discoverPanel = document.getElementById('platform-tab-panel-category');
if (!discoverPanel) {
throw new Error('缺少发现面板');
@@ -4285,10 +4289,12 @@ test('desktop discover shows child motion demo in edutainment channel', async ()
const user = userEvent.setup();
const onOpenChildMotionDemo = vi.fn();
renderStatefulLoggedOutHomeView({
onOpenChildMotionDemo,
});
await user.click(screen.getByRole('button', { name: '发现' }));
renderStatefulLoggedOutHomeView(
{
onOpenChildMotionDemo,
},
true,
);
await user.click(screen.getByRole('tab', { name: '寓教于乐' }));
const warmupButton = screen.getByRole('button', { name: //u });
@@ -4316,7 +4322,6 @@ test('mobile discover hides edutainment channel and work when switch is disabled
latestEntries: [edutainmentEntry],
onSearchPublicCode,
});
await user.click(screen.getByRole('button', { name: '发现' }));
const discoverPanel = document.getElementById('platform-tab-panel-category');
if (!discoverPanel) {
throw new Error('缺少发现面板');
@@ -4353,7 +4358,6 @@ test('mobile discover keeps baby object match works in edutainment channel only'
onOpenGalleryDetail,
onSearchPublicCode,
});
await user.click(screen.getByRole('button', { name: '发现' }));
const discoverPanel = document.getElementById('platform-tab-panel-category');
if (!discoverPanel) {
throw new Error('缺少发现面板');
@@ -4388,7 +4392,6 @@ test('discover search keeps public code fallback when local works do not match',
latestEntries: [puzzlePublicEntry],
onSearchPublicCode,
});
await user.click(screen.getByRole('button', { name: '发现' }));
const searchInput =
screen.getByPlaceholderText('搜索作品号、名称、作者、描述');
@@ -4406,7 +4409,6 @@ test('public gallery cards hide work code until detail is opened', async () => {
latestEntries: [puzzlePublicEntry],
onOpenGalleryDetail,
});
await user.click(screen.getByRole('button', { name: '发现' }));
expect(screen.queryByText('PZ-EPUBLIC1')).toBeNull();
expect(
@@ -4436,26 +4438,34 @@ test('public gallery cards hide phone masked author and public user code', async
},
true,
);
await user.click(screen.getByRole('button', { name: '发现' }));
const card = screen.getByRole('button', { name: //u });
expect(card).toBeTruthy();
expect(within(card).getByText('公开作者')).toBeTruthy();
expect(await within(card).findByText('公开作者')).toBeTruthy();
expect(within(card).queryByText('158****3533 · SY-00000003')).toBeNull();
expect(within(card).queryByText('158****3533')).toBeNull();
expect(within(card).queryByText('SY-00000003')).toBeNull();
});
test('logged out mobile shell defaults to discover tab', () => {
const { container } = renderStatefulLoggedOutHomeView({
latestEntries: [puzzlePublicEntry],
});
test('logged out mobile shell defaults to profile tab when active tab is hidden', async () => {
const { container } = renderStatefulLoggedOutHomeView(
{
latestEntries: [puzzlePublicEntry],
},
false,
{
allowHiddenActiveTab: false,
},
);
const activePanel = container.querySelector('.platform-tab-panel--active');
expect(activePanel?.id).toBe('platform-tab-panel-category');
await waitFor(() => {
expect(container.querySelector('.platform-tab-panel--active')?.id).toBe(
'platform-tab-panel-profile',
);
});
expect(
screen.getByPlaceholderText('搜索作品号、名称、作者、描述'),
).toBeTruthy();
screen.queryByPlaceholderText('搜索作品号、名称、作者、描述'),
).toBeNull();
expect(container.querySelector('.platform-mobile-topbar')).toBeTruthy();
expect(
container.querySelector('.platform-mobile-entry-shell--recommend'),
@@ -4463,18 +4473,15 @@ test('logged out mobile shell defaults to discover tab', () => {
});
test('logged out recommend tab opens embedded runtime without login modal', async () => {
const user = userEvent.setup();
const { container, openLoginModal } = renderStatefulLoggedOutHomeView({
latestEntries: [puzzlePublicEntry],
activeRecommendEntryKey: 'puzzle:user-2:puzzle-profile-public-1',
});
const bottomNav = container.querySelector('.platform-bottom-nav');
if (!bottomNav) {
throw new Error('缺少底部导航');
}
await user.click(
within(bottomNav as HTMLElement).getByRole('button', { name: '推荐' }),
const { container, openLoginModal } = renderStatefulLoggedOutHomeView(
{
latestEntries: [puzzlePublicEntry],
activeRecommendEntryKey: 'puzzle:user-2:puzzle-profile-public-1',
},
false,
{
initialActiveTab: 'home',
},
);
expect(openLoginModal).not.toHaveBeenCalled();
@@ -4489,20 +4496,17 @@ test('logged out recommend tab opens embedded runtime without login modal', asyn
});
test('logged out recommend runtime keeps detail callback idle', async () => {
const user = userEvent.setup();
const onOpenGalleryDetail = vi.fn();
const { openLoginModal } = renderStatefulLoggedOutHomeView({
latestEntries: [puzzlePublicEntry],
activeRecommendEntryKey: 'puzzle:user-2:puzzle-profile-public-1',
onOpenGalleryDetail,
});
const bottomNav = document.querySelector('.platform-bottom-nav');
if (!bottomNav) {
throw new Error('缺少底部导航');
}
await user.click(
within(bottomNav as HTMLElement).getByRole('button', { name: '推荐' }),
const { openLoginModal } = renderStatefulLoggedOutHomeView(
{
latestEntries: [puzzlePublicEntry],
activeRecommendEntryKey: 'puzzle:user-2:puzzle-profile-public-1',
onOpenGalleryDetail,
},
false,
{
initialActiveTab: 'home',
},
);
expect(openLoginModal).not.toHaveBeenCalled();
@@ -4596,9 +4600,8 @@ test('mobile recommend meta matches active wooden fish runtime entry', () => {
expect(within(meta).getByText('莲台木鱼')).toBeTruthy();
});
test('logged out desktop recommend rail enters runtime without login modal', async () => {
test('logged out desktop rail hides recommend entry', () => {
mockDesktopLayout();
const user = userEvent.setup();
const openLoginModal = vi.fn();
const { container } = renderLoggedOutHomeView(
@@ -4616,10 +4619,11 @@ test('logged out desktop recommend rail enters runtime without login modal', asy
throw new Error('缺少桌面侧边栏');
}
await user.click(
within(desktopRail as HTMLElement).getByRole('button', { name: '推荐' }),
);
expect(
within(desktopRail as HTMLElement).queryByRole('button', {
name: '推荐',
}),
).toBeNull();
expect(openLoginModal).not.toHaveBeenCalled();
expect(screen.queryByTestId('recommend-runtime')).toBeNull();
expect(container.querySelector('.platform-desktop-shell')).toBeTruthy();
@@ -5379,8 +5383,7 @@ test('logged in recommend runtime preloads adjacent work previews and drag switc
vi.useRealTimers();
});
test('logged out active recommend bottom tab selects next work without login', async () => {
const user = userEvent.setup();
test('logged out active recommend bottom nav hides next action', () => {
const onSelectNextRecommendEntry = vi.fn();
const openLoginModal = vi.fn();
@@ -5390,9 +5393,8 @@ test('logged out active recommend bottom tab selects next work without login', a
onSelectNextRecommendEntry,
});
await user.click(screen.getByRole('button', { name: '下一个' }));
expect(onSelectNextRecommendEntry).toHaveBeenCalledTimes(1);
expect(screen.queryByRole('button', { name: '下一个' })).toBeNull();
expect(onSelectNextRecommendEntry).not.toHaveBeenCalled();
expect(openLoginModal).not.toHaveBeenCalled();
});
@@ -5487,9 +5489,6 @@ test('mobile discover recommend feed only rotates the card closest to screen cen
renderStatefulLoggedOutHomeView({
latestEntries: [firstEntry, secondEntry],
});
act(() => {
screen.getByRole('button', { name: '发现' }).click();
});
const tabPanel = document.querySelector('.platform-tab-panel--active');
const firstCard = screen.getByRole('button', { name: //u });
@@ -5662,7 +5661,6 @@ test('mobile today channel only shows newly published works from today', async (
latestEntries: [yesterdayEntry, updatedTodayEntry, todayEntry],
});
await user.click(screen.getByRole('button', { name: '发现' }));
await user.click(screen.getByRole('tab', { name: '今日' }));
const discoverPanel = document.getElementById('platform-tab-panel-category');
if (!discoverPanel) {
@@ -5816,7 +5814,6 @@ test('mobile home moves category shelf into game category channel', async () =>
expect(screen.queryByRole('button', { name: 'PC游戏' })).toBeNull();
expect(screen.queryByRole('button', { name: '即点即玩' })).toBeNull();
await user.click(screen.getByRole('button', { name: '发现' }));
await user.click(screen.getByRole('tab', { name: '分类' }));
expect(screen.getAllByText('分类').length).toBeGreaterThan(0);
@@ -5842,7 +5839,6 @@ test('mobile game category list orders works by composite public metric', async
latestEntries: [puzzlePublicEntry, hotRankEntry],
});
await user.click(screen.getByRole('button', { name: '发现' }));
await user.click(screen.getByRole('tab', { name: '分类' }));
await user.click(screen.getByRole('button', { name: '奇幻' }));
@@ -5874,7 +5870,6 @@ test('mobile game category filter dialog filters by play type', async () => {
latestEntries: [puzzlePublicEntry, match3dEntry],
});
await user.click(screen.getByRole('button', { name: '发现' }));
await user.click(screen.getByRole('tab', { name: '分类' }));
await user.click(screen.getByRole('button', { name: '奇幻' }));
@@ -5909,7 +5904,6 @@ test('mobile game category keeps filter controls when current filter becomes emp
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 }));
@@ -5955,7 +5949,6 @@ test('bottom category tab becomes ranking and switches ranking metrics', async (
latestEntries: [remixRankEntry, hotRankEntry, newRankEntry],
});
await user.click(screen.getByRole('button', { name: '发现' }));
await user.click(screen.getByRole('tab', { name: '排行' }));
expect(await screen.findByRole('tab', { name: '热门榜' })).toBeTruthy();
@@ -5989,7 +5982,6 @@ test('ranking rows limit displayed work name and show two short tags on the thir
latestEntries: [longTextRankEntry],
});
await user.click(screen.getByRole('button', { name: '发现' }));
await user.click(screen.getByRole('tab', { name: '排行' }));
const rankingPanel = document.getElementById('platform-tab-panel-category');
+3 -10
View File
@@ -2899,13 +2899,7 @@ export function RpgEntryHomeView({
hasManualCategoryTagSelectionRef.current = true;
setSelectedCategoryTag(tag);
}, []);
const visibleMobileTabs = useMemo<PlatformHomeTab[]>(
() =>
isAuthenticated
? ['home', 'category', 'profile']
: ['home', 'category', 'profile'],
[isAuthenticated],
);
const visibleMobileTabs = useMemo<PlatformHomeTab[]>(() => ['profile'], []);
const visibleDesktopNavTabs = useMemo<PlatformNavTab[]>(
() =>
(isAuthenticated
@@ -2962,12 +2956,11 @@ export function RpgEntryHomeView({
return;
}
if (!visibleMobileTabs.includes(activeTab) && !isDesktopLayout) {
onTabChange(isAuthenticated ? 'home' : 'category');
onTabChange('profile');
}
}, [
activeTab,
allowHiddenActiveTab,
isAuthenticated,
isDesktopLayout,
onTabChange,
visibleMobileTabs,
@@ -5115,7 +5108,7 @@ export function RpgEntryHomeView({
<div className="platform-mobile-bottom-dock min-w-0 shrink-0">
<div
className={`platform-bottom-nav grid ${visibleMobileTabs.length === 5 ? 'grid-cols-5' : visibleMobileTabs.length === 4 ? 'grid-cols-4' : visibleMobileTabs.length === 3 ? 'grid-cols-3' : 'grid-cols-2'}`}
className={`platform-bottom-nav grid ${visibleMobileTabs.length === 5 ? 'grid-cols-5' : visibleMobileTabs.length === 4 ? 'grid-cols-4' : visibleMobileTabs.length === 3 ? 'grid-cols-3' : visibleMobileTabs.length === 2 ? 'grid-cols-2' : 'grid-cols-1'}`}
>
{visibleMobileTabs.map((tab) => (
<PlatformTabButton