跟上主题详情契约变更:memberCount 为真实可见数 + rootsTruncated 截断信号
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
- 客户端:GameDistributionThemeDetail 增加必填 rootsTruncated;getTheme 的响应类型与返回值透传该键(缺键按 false,空成员时服务端也是 false 而不是缺键) - 主题页:新增「共 N 棵作品树」,总数一律用 memberCount(真实可见成员数,不受 50 上限截断),不再用 roots.length;rootsTruncated 为 true 时给出明确提示「这里只显示前 A 个,共 B 个」,与族谱页的 truncated 同一约定 - 顺带确认:全仓没有别处按 memberCount === roots.length 做不变式(此前只有主题列表页展示 memberCount,主题页未展示计数) - 测试:新增一条——rootsTruncated=true 且 memberCount=120 / roots 只有 1 个时,截断提示出现且总数展示 120,不得出现用 roots.length 算出的「共 1 棵作品树」
This commit is contained in:
@@ -1911,6 +1911,36 @@ describe('GameThemePage', () => {
|
||||
expect(await screen.findByText('主题不存在')).toBeTruthy();
|
||||
expect(screen.getByRole('button', { name: '重试' })).toBeTruthy();
|
||||
});
|
||||
|
||||
it('roots 被截断时给出明确提示,且总数用 memberCount 而不是 roots 长度', async () => {
|
||||
getThemeMock.mockResolvedValue({
|
||||
themeId: 'theme-3',
|
||||
name: '大主题',
|
||||
summary: '',
|
||||
badge: '',
|
||||
memberCount: 120,
|
||||
rootsTruncated: true,
|
||||
roots: [rootGame('game-root-1', '根一')],
|
||||
});
|
||||
getGameLineageMock.mockResolvedValue({
|
||||
rootGameId: 'game-root-1',
|
||||
root: null,
|
||||
nodes: [],
|
||||
truncated: false,
|
||||
});
|
||||
render(
|
||||
<GameThemePage
|
||||
themeId="theme-3"
|
||||
onBack={vi.fn()}
|
||||
onOpenDetail={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
// 总数是真实可见成员数(120),不是这一页拿到的 1 个。
|
||||
expect(await screen.findByText('共 120 棵作品树')).toBeTruthy();
|
||||
expect(screen.getByText(/这里只显示前 1 个,共 120 个/u)).toBeTruthy();
|
||||
expect(screen.queryByText('共 1 棵作品树')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('GameDetailPage 共创主题入口', () => {
|
||||
|
||||
@@ -158,6 +158,22 @@ export function GameThemePage({
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{theme ? (
|
||||
<p className="game-theme-summary__meta">
|
||||
共 {theme.memberCount} 棵作品树
|
||||
</p>
|
||||
) : null}
|
||||
{theme?.rootsTruncated ? (
|
||||
/*
|
||||
总数一律用 `memberCount`(真实可见成员数,不截断);`roots.length` 只是这一页拿到几个。
|
||||
两者不再保证相等,所以这里显式区分「显示前 N 个」与「共 M 个」。
|
||||
*/
|
||||
<p className="game-lineage-notice" role="status">
|
||||
这个主题的根较多:这里只显示前 {theme.roots.length} 个,共{' '}
|
||||
{theme.memberCount} 个。
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
{isLoading && !theme ? (
|
||||
<div className="game-loading-panel">正在加载共创主题…</div>
|
||||
) : null}
|
||||
|
||||
@@ -636,6 +636,12 @@
|
||||
line-height: 1.6;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.game-theme-summary__meta {
|
||||
margin: 0;
|
||||
color: var(--platform-text-soft);
|
||||
font-size: 0.8rem;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.game-theme-trees {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -400,6 +400,13 @@ export type GameDistributionThemeSummary = {
|
||||
/** 主题详情:摘要 + 成员根清单(逐条是公开目录同一份投影)。 */
|
||||
export type GameDistributionThemeDetail = GameDistributionThemeSummary & {
|
||||
roots: GameDistributionGame[];
|
||||
/**
|
||||
* `roots` 是否被服务端的响应体积上限截断(与族谱响应的 `truncated` 同一种约定)。
|
||||
*
|
||||
* **`memberCount` 与 `roots.length` 不再保证相等**:`memberCount` 是真实可见成员数(不截断),
|
||||
* `roots` 可能只含前 50 个。展示总数一律用 `memberCount`,这个布尔只负责决定要不要提示。
|
||||
*/
|
||||
rootsTruncated: boolean;
|
||||
};
|
||||
|
||||
function normalizeThemeList(value: unknown): GameDistributionThemeSummary[] {
|
||||
@@ -464,6 +471,8 @@ export async function getTheme(
|
||||
summary?: string;
|
||||
badge?: string;
|
||||
memberCount?: number;
|
||||
/** `roots` 被响应体积上限截断:与 `roots` 同层,空成员时也是 `false`(不是缺键)。 */
|
||||
rootsTruncated?: boolean;
|
||||
roots?: unknown;
|
||||
}>(
|
||||
`/themes/${encodeURIComponent(normalizedThemeId)}`,
|
||||
@@ -477,6 +486,7 @@ export async function getTheme(
|
||||
summary: payload.summary ?? '',
|
||||
badge: payload.badge ?? '',
|
||||
memberCount: payload.memberCount ?? 0,
|
||||
rootsTruncated: payload.rootsTruncated === true,
|
||||
roots: normalizeGameList(payload.roots),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user