feat(viewer): hide like feature — action bar like slot, photo like badges, hot sort tab, others' like bubbles (user request 2026-09-23)
- PhotoFeed: cell like badge not rendered - AlbumHeader: sort tabs row removed (hot depends on likes; latest-only has no toggle value) - PhotoActionBar: like slot v-if=false (share/download remain, auto re-center) - ActivityPage: LikeBubbles hidden - specs updated to hidden assertions; 244 green Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -233,21 +233,11 @@ describe('activity page (Task 6)', () => {
|
||||
|
||||
// —— UI-02 V02:排序/分类经 AlbumHeader 上抛,沿用本页序列重置机制 ——
|
||||
|
||||
it('selecting the Popular tab refetches the sequence with sort=hot (active state moves)', async () => {
|
||||
it('sort tabs removed per user request (2026-09-23): no popular tab, sequence stays latest', async () => {
|
||||
const wrapper = mountPage();
|
||||
await flushPromises();
|
||||
mockedGetPublicPhotos.mockClear();
|
||||
|
||||
const tabs = wrapper.findAll('.sort-tab');
|
||||
expect(tabs.map((tab) => tab.text())).toEqual(['最新', '热门']);
|
||||
expect(tabs[0].classes()).toContain('active');
|
||||
expect(tabs[0].attributes('aria-pressed')).toBe('true');
|
||||
|
||||
await tabs[1].trigger('click');
|
||||
await flushPromises();
|
||||
|
||||
expect(mockedGetPublicPhotos).toHaveBeenLastCalledWith('tok-activity', expect.objectContaining({ sort: 'hot' }));
|
||||
expect(wrapper.findAll('.sort-tab')[1].classes()).toContain('active');
|
||||
expect(wrapper.findAll('.sort-tab')).toHaveLength(0);
|
||||
expect(wrapper.find('.sort-tabs').exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('category chips filter the sequence and the selection survives a locale switch', async () => {
|
||||
|
||||
@@ -81,7 +81,7 @@ describe('album header info area (UI-02 V02)', () => {
|
||||
expect(wrapper.find('.meta').text()).toContain('10月1日');
|
||||
|
||||
// 工具栏:排序 tab + 分类胶囊(All + 接口分类)。
|
||||
expect(wrapper.findAll('.sort-tab').map((tab) => tab.text())).toEqual(['最新', '热门']);
|
||||
expect(wrapper.findAll('.sort-tab')).toHaveLength(0); // 排序 tab 已移除(2026-09-23)
|
||||
expect(wrapper.findAll('.categories .chip').map((chip) => chip.text())).toEqual(['全部', '签到墙', '开场']);
|
||||
// jsdom 无布局:未溢出 → 不渲染展开按钮。
|
||||
expect(wrapper.findAll('.text-expand')).toHaveLength(0);
|
||||
@@ -153,19 +153,10 @@ describe('album header info area (UI-02 V02)', () => {
|
||||
expect(wrapper.find('.title').classes()).not.toContain('clamped');
|
||||
});
|
||||
|
||||
it('sort tabs: latest active by default, clicking popular emits select-sort hot, locked disables', async () => {
|
||||
it('sort tabs removed per user request (2026-09-23): whole row hidden, mode stays latest', () => {
|
||||
const wrapper = mountHeader({ album: makeAlbum(), viewCount: null });
|
||||
|
||||
const tabs = wrapper.findAll('.sort-tab');
|
||||
expect(tabs[0].classes()).toContain('active');
|
||||
expect(tabs[0].attributes('aria-pressed')).toBe('true');
|
||||
expect(tabs[1].classes()).not.toContain('active');
|
||||
|
||||
await tabs[1].trigger('click');
|
||||
expect(wrapper.emitted('selectSort')).toEqual([['hot']]);
|
||||
|
||||
const locked = mountHeader({ album: makeAlbum(), viewCount: null, sortLocked: true });
|
||||
expect(locked.findAll('.sort-tab').every((tab) => tab.attributes('disabled') !== undefined)).toBe(true);
|
||||
expect(wrapper.find('.sort-tabs').exists()).toBe(false);
|
||||
expect(wrapper.find('.sort-tab').exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('category chips: selected state from prop, clicks emit the category id and All emits null', async () => {
|
||||
|
||||
@@ -343,37 +343,14 @@ describe('photo feed (Task 7 / Task 8 sequence props)', () => {
|
||||
expect(wrapper.findAll('.cell')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders the like badge overlay with the real count and no card chrome', async () => {
|
||||
const likes = {
|
||||
displayFor: (photo: PublicPhoto) => photo.likeCount,
|
||||
popNonce: () => 0,
|
||||
like: vi.fn(),
|
||||
} as unknown as LikesController;
|
||||
mockedGetPublicPhotos.mockResolvedValueOnce(
|
||||
makeList([{ ...makePhoto('p1'), likeCount: 1286 }, makePhoto('p2')], null),
|
||||
);
|
||||
const { wrapper } = mountFeed({ likes });
|
||||
await flushPromises();
|
||||
|
||||
const badge = wrapper.find('.cell .cell-like');
|
||||
expect(badge.exists()).toBe(true);
|
||||
expect(badge.find('.heart').exists()).toBe(true);
|
||||
// 真实计数(useLikes displayFor 值)经 Intl 分组。
|
||||
expect(badge.find('.count').text()).toBe('1,286');
|
||||
// 无白色卡片包裹/无常驻文件名/无额外标题:单元格可见文本只有点赞计数
|
||||
//(图片 alt 非可见文本,懒加载绑定不影响该断言)。
|
||||
expect(wrapper.findAll('.cell')[0].text()).toBe('1,286');
|
||||
it('like badge overlay hidden per user request (2026-09-23)', () => {
|
||||
const { wrapper } = mountFeed();
|
||||
expect(wrapper.find('.cell-like').exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('clicking the like badge likes the photo without opening the lightbox', async () => {
|
||||
const like = vi.fn();
|
||||
const likes = { displayFor: () => 0, popNonce: () => 0, like } as unknown as LikesController;
|
||||
const { wrapper, opened } = mountFeed({ likes });
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('.cell-like').trigger('click');
|
||||
expect(like).toHaveBeenCalledWith('p1');
|
||||
expect(opened).toEqual([]);
|
||||
it('like badge interactions removed with the badge (hidden 2026-09-23)', () => {
|
||||
const { wrapper } = mountFeed();
|
||||
expect(wrapper.find('.cell-like').exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('shows the new-photo pill on hasNew; clicking refreshes without scrolling to top', async () => {
|
||||
|
||||
@@ -139,9 +139,9 @@ describe('photo lightbox (Task 8)', () => {
|
||||
expect(img(wrapper).attributes('src')).toBe('http://img.test/p2-browse.jpg?s=1');
|
||||
// 3 张全部加载完成(无更多页)→ 筛选序列 i / n(§5.2)。
|
||||
expect(wrapper.find('.position-indicator').text()).toBe('2 / 3');
|
||||
// 底部操作栏(V04):Like 计数 + Like/Share 标签(无下载控制器 → 下载位省略)。
|
||||
expect(wrapper.find('.like-count').text()).toBe('7');
|
||||
expect(wrapper.findAll('.action-label').map((label) => label.text())).toEqual(['点赞', '分享']);
|
||||
// 底部操作栏:点赞已按用户要求隐藏(2026-09-23)→ 仅分享(无下载控制器)。
|
||||
expect(wrapper.find('.like-count').exists()).toBe(false);
|
||||
expect(wrapper.findAll('.action-label').map((label) => label.text())).toEqual(['分享']);
|
||||
});
|
||||
|
||||
it('navigates with buttons and arrow keys, keeping the list order', async () => {
|
||||
@@ -553,14 +553,12 @@ describe('photo lightbox interactions (UI-02 V05)', () => {
|
||||
document.documentElement.className = '';
|
||||
});
|
||||
|
||||
it('bursts heart particles for a local like click on the current photo', async () => {
|
||||
it('like entry hidden (2026-09-23): no local like click path, no particles', async () => {
|
||||
const { wrapper } = mountV05('p1');
|
||||
await flushPromises();
|
||||
|
||||
expect(wrapper.find('.like-slot').exists()).toBe(false);
|
||||
expect(wrapper.findAll('.heart-particles-zone .particle')).toHaveLength(0);
|
||||
await wrapper.find('.like-slot .action-btn').trigger('click');
|
||||
await nextTick();
|
||||
expect(wrapper.findAll('.heart-particles-zone .particle')).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('routes realtime bursts: particles for the current photo, a notice for others; notice click navigates', async () => {
|
||||
|
||||
@@ -39,36 +39,30 @@ describe('photo action bar (UI-02 V04)', () => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('renders like/share/download as evenly distributed slots with formatted count (no tap-more hint)', () => {
|
||||
it('like slot hidden (2026-09-23): share/download slots remain, no like count', () => {
|
||||
const wrapper = mountBar({ likeCount: 1286, canDownload: true });
|
||||
|
||||
// 三个 action-slot flex 均分(下载隐藏时由剩余 slot 自然重排,见下例)。
|
||||
expect(wrapper.findAll('.action-slot')).toHaveLength(3);
|
||||
expect(wrapper.findAll('.action-slot')).toHaveLength(2);
|
||||
const labels = wrapper.findAll('.action-label').map((label) => label.text());
|
||||
expect(labels).toEqual(['点赞', '分享', '下载']);
|
||||
// 计数 Intl 分组 + tabular-nums 容器;「连续点击」提示已按用户要求移除。
|
||||
expect(wrapper.find('.like-count').text()).toBe('1,286');
|
||||
expect(wrapper.find('.like-hint').exists()).toBe(false);
|
||||
expect(labels).toEqual(['分享', '下载']);
|
||||
expect(wrapper.find('.like-slot').exists()).toBe(false);
|
||||
expect(wrapper.find('.like-count').exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('removes the download action entirely when downloads are disabled', () => {
|
||||
it('download disabled leaves share only (like hidden 2026-09-23)', () => {
|
||||
const wrapper = mountBar({ likeCount: 0, canDownload: false });
|
||||
|
||||
expect(wrapper.find('.download-group').exists()).toBe(false);
|
||||
expect(wrapper.findAll('.action-slot')).toHaveLength(2); // 剩余两键均分栏宽
|
||||
expect(wrapper.findAll('.action-label').map((label) => label.text())).toEqual(['点赞', '分享']);
|
||||
expect(wrapper.findAll('.action-slot')).toHaveLength(1);
|
||||
expect(wrapper.findAll('.action-label').map((label) => label.text())).toEqual(['分享']);
|
||||
});
|
||||
|
||||
it('emits like and share from the large tap targets', async () => {
|
||||
const wrapper = mountBar({ likeCount: 3 });
|
||||
const buttons = wrapper.findAll('.action-btn');
|
||||
expect(buttons).toHaveLength(2);
|
||||
it('emits share from the large tap target (like hidden 2026-09-23)', async () => {
|
||||
const wrapper = mountBar({ likeCount: 0 });
|
||||
|
||||
await buttons[0].trigger('click');
|
||||
await buttons[1].trigger('click');
|
||||
expect(wrapper.emitted('like')).toHaveLength(1);
|
||||
await wrapper.get('.action-btn[aria-label="分享照片"]').trigger('click');
|
||||
expect(wrapper.emitted('share')).toHaveLength(1);
|
||||
expect(wrapper.emitted('download')).toBeUndefined();
|
||||
expect(wrapper.emitted('like')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('downloads the browse variant directly when original download is disabled', async () => {
|
||||
|
||||
@@ -256,16 +256,16 @@ onBeforeUnmount(() => {
|
||||
decoding="async"
|
||||
@error="onImgError(photos[placement.index])"
|
||||
>
|
||||
<!-- 点赞角标:独立交互区(@click.stop),不随图片点击打开大图。 -->
|
||||
<!-- 点赞角标:已按用户要求隐藏(2026-09-23,先隐藏点赞功能)。 -->
|
||||
<button
|
||||
v-if="likes !== undefined"
|
||||
v-if="false && likes !== undefined"
|
||||
type="button"
|
||||
class="cell-like"
|
||||
:aria-label="t('viewer.like')"
|
||||
@click.stop="likeCell(photos[placement.index])"
|
||||
>
|
||||
<svg
|
||||
:key="likes.popNonce(photos[placement.index].photoId)"
|
||||
:key="likes?.popNonce(photos[placement.index].photoId) ?? 0"
|
||||
class="heart"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
|
||||
@@ -220,29 +220,8 @@ watch(
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 排序 tab(§4.2):高 44px;选中蓝字 + 底部 2px 线;大图打开期间锁定。 -->
|
||||
<nav class="sort-tabs" :aria-label="t('album.sortLabel')">
|
||||
<button
|
||||
type="button"
|
||||
class="sort-tab"
|
||||
:class="{ active: props.sortMode === 'latest' }"
|
||||
:aria-pressed="props.sortMode === 'latest'"
|
||||
:disabled="props.sortLocked"
|
||||
@click="emit('selectSort', 'latest')"
|
||||
>
|
||||
{{ t('viewer.latest') }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="sort-tab"
|
||||
:class="{ active: props.sortMode === 'hot' }"
|
||||
:aria-pressed="props.sortMode === 'hot'"
|
||||
:disabled="props.sortLocked"
|
||||
@click="emit('selectSort', 'hot')"
|
||||
>
|
||||
{{ t('viewer.popular') }}
|
||||
</button>
|
||||
</nav>
|
||||
<!-- 排序 tab:热门已按用户要求移除(2026-09-23,其排序依赖点赞数);
|
||||
仅剩「最新」单一排序无切换意义 → 整条隐藏(sortMode 恒 latest)。 -->
|
||||
|
||||
<!-- 分类胶囊:来自接口;无分类省略整条;All = 不传分类筛选。 -->
|
||||
<nav v-if="album.categories.length > 0" class="categories" :aria-label="t('album.categories')">
|
||||
|
||||
@@ -75,7 +75,9 @@ onBeforeUnmount(cancelPress);
|
||||
|
||||
<template>
|
||||
<div class="action-bar">
|
||||
<div class="action-slot like-slot">
|
||||
<!-- 点赞槽:已按用户要求隐藏(2026-09-23,先隐藏点赞功能——含计数)。
|
||||
v-if 编译期常量 false,构建后整槽不进 DOM。 -->
|
||||
<div v-if="false" class="action-slot like-slot">
|
||||
<button
|
||||
type="button"
|
||||
class="action-btn"
|
||||
|
||||
@@ -372,7 +372,8 @@ onBeforeUnmount(() => {
|
||||
|
||||
<!-- 他人点赞气泡(Task 10):点击定位打开该照片大图。大图打开期间改由
|
||||
查看器内互动提示承载(§6.2:避免页面级气泡被模态框盖住或遮住下载)。 -->
|
||||
<LikeBubbles :bubbles="lightboxPhotoId === null ? realtime.bubbles : []" @open="openFromFeed" />
|
||||
<!-- 他人点赞气泡:已按用户要求隐藏(2026-09-23,先隐藏点赞功能)。 -->
|
||||
<LikeBubbles v-if="false" :bubbles="lightboxPhotoId === null ? realtime.bubbles : []" @open="openFromFeed" />
|
||||
|
||||
<!-- 照片下线轻提示(大图 404 刷新 / WS hidden/deleted 推送移除时)。 -->
|
||||
<p v-if="photoRemovedNotice" class="removed-notice" role="status">{{ t('viewer.photoRemoved') }}</p>
|
||||
|
||||
Reference in New Issue
Block a user