修复 M3 复核问题(第一批):族谱页双重空态、节点上限常量未钉住、窄屏网格溢出
- 族谱页空态收敛为一条:nodes 为空时只给整体空态(这部作品还没有可展示的族谱节点),有节点但无衍生时才给衍生空态,两者互斥不再同时渲染 - 新增两条组件用例覆盖该组合:空树只出现一条空态且无 .game-lineage-tree;只有母版时渲染母版节点 + 衍生空态且不出现整体空态 - 纯函数测试改用 GAME_DISTRIBUTION_LINEAGE_NODE_LIMIT 常量,并新增 node_limit_constant_is_pinned_and_governs_truncation:同时钉住常量值 200(合同值)与满量/超量时的 truncated 行为,改常量或改截断实现都必须过门 - LineageTreeNode 文档写明作者名空值语义:空串/纯空白归一为 None 与 M1 的 fork_lineage_visible_identity 三态刻意保留属有意为之,两端展示层按 falsy 判断、行为等价 - 族谱节点网格列下限改为 min(13rem, 100%):桌面列宽不变,极窄视口不再横向溢出 - 未动涉及 M2a session 的 spacetime-module / shared-contracts 注释与 lineage_entries 图装配测试,留待第二批
This commit is contained in:
@@ -1022,6 +1022,67 @@ describe('GameLineagePage', () => {
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('没有任何可见节点时只渲染一条整体空态,不叠加衍生空态', async () => {
|
||||
getGameLineageMock.mockResolvedValueOnce({
|
||||
rootGameId: 'game-root',
|
||||
root: null,
|
||||
nodes: [],
|
||||
truncated: false,
|
||||
});
|
||||
render(
|
||||
<GameLineagePage
|
||||
rootGameId="game-root"
|
||||
onBack={vi.fn()}
|
||||
onOpenDetail={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(
|
||||
await screen.findByText('这部作品还没有可展示的族谱节点。'),
|
||||
).toBeTruthy();
|
||||
// 双重空态回归:整体空态已覆盖「没有节点」,不能再叠一条衍生空态。
|
||||
expect(
|
||||
screen.queryByText('这部作品还没有衍生作品,欢迎从它开始共创。'),
|
||||
).toBeNull();
|
||||
expect(document.querySelectorAll('.game-lineage-tree')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('只有母版时渲染母版节点并只给衍生空态,不给整体空态', async () => {
|
||||
getGameLineageMock.mockResolvedValueOnce({
|
||||
rootGameId: 'game-root',
|
||||
root: createLineageNode({
|
||||
gameId: 'game-root',
|
||||
title: '母版',
|
||||
generation: 0,
|
||||
parentGameId: null,
|
||||
}),
|
||||
nodes: [
|
||||
createLineageNode({
|
||||
gameId: 'game-root',
|
||||
title: '母版',
|
||||
generation: 0,
|
||||
parentGameId: null,
|
||||
}),
|
||||
],
|
||||
truncated: false,
|
||||
});
|
||||
render(
|
||||
<GameLineagePage
|
||||
rootGameId="game-root"
|
||||
onBack={vi.fn()}
|
||||
onOpenDetail={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(await screen.findByRole('button', { name: '母版' })).toBeTruthy();
|
||||
expect(
|
||||
screen.getByText('这部作品还没有衍生作品,欢迎从它开始共创。'),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
screen.queryByText('这部作品还没有可展示的族谱节点。'),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('超出节点上限时如实标注截断,不静默丢弃', async () => {
|
||||
getGameLineageMock.mockResolvedValueOnce({
|
||||
rootGameId: 'game-root',
|
||||
|
||||
@@ -211,47 +211,54 @@ export function GameLineagePage({
|
||||
母版已不可用,下面按代际关系展示仍可阅读的衍生作品。
|
||||
</p>
|
||||
)}
|
||||
{groups.length === 0 ? (
|
||||
{/*
|
||||
空态只有一条,避免「双重空态」:`nodes` 为空说明整棵树没有任何可见节点
|
||||
(连母版都不可读),此时只给整体空态;只有「有节点但没有任何衍生」时才给衍生空态。
|
||||
两者互斥,不会同时出现。
|
||||
*/}
|
||||
{nodes.length === 0 ? (
|
||||
<PlatformEmptyState surface="subpanel" size="panel">
|
||||
<span>这部作品还没有可展示的族谱节点。</span>
|
||||
</PlatformEmptyState>
|
||||
) : (
|
||||
<div className="game-lineage-tree">
|
||||
{groups.map(([generation, groupNodes]) => (
|
||||
<section
|
||||
key={generation}
|
||||
className="game-lineage-generation"
|
||||
aria-label={generationLabel(generation)}
|
||||
>
|
||||
<h3 className="game-lineage-generation__label">
|
||||
{generationLabel(generation)}
|
||||
<span className="game-lineage-generation__count">
|
||||
{groupNodes.length}
|
||||
</span>
|
||||
</h3>
|
||||
<div className="game-lineage-generation__nodes">
|
||||
{groupNodes.map((node) => (
|
||||
<LineageNodeCard
|
||||
key={node.gameId}
|
||||
node={node}
|
||||
isAnchor={node.gameId === anchorId}
|
||||
hasVisibleParent={
|
||||
!node.parentGameId ||
|
||||
visibleIds.has(node.parentGameId)
|
||||
}
|
||||
onOpenDetail={onOpenDetail}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
<>
|
||||
<div className="game-lineage-tree">
|
||||
{groups.map(([generation, groupNodes]) => (
|
||||
<section
|
||||
key={generation}
|
||||
className="game-lineage-generation"
|
||||
aria-label={generationLabel(generation)}
|
||||
>
|
||||
<h3 className="game-lineage-generation__label">
|
||||
{generationLabel(generation)}
|
||||
<span className="game-lineage-generation__count">
|
||||
{groupNodes.length}
|
||||
</span>
|
||||
</h3>
|
||||
<div className="game-lineage-generation__nodes">
|
||||
{groupNodes.map((node) => (
|
||||
<LineageNodeCard
|
||||
key={node.gameId}
|
||||
node={node}
|
||||
isAnchor={node.gameId === anchorId}
|
||||
hasVisibleParent={
|
||||
!node.parentGameId ||
|
||||
visibleIds.has(node.parentGameId)
|
||||
}
|
||||
onOpenDetail={onOpenDetail}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
{derivedCount === 0 ? (
|
||||
<PlatformEmptyState surface="subpanel" size="panel">
|
||||
<span>这部作品还没有衍生作品,欢迎从它开始共创。</span>
|
||||
</PlatformEmptyState>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
{derivedCount === 0 ? (
|
||||
<PlatformEmptyState surface="subpanel" size="panel">
|
||||
<span>这部作品还没有衍生作品,欢迎从它开始共创。</span>
|
||||
</PlatformEmptyState>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -1258,7 +1258,9 @@
|
||||
}
|
||||
.game-lineage-generation__nodes {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
|
||||
/* `min(13rem, 100%)`:桌面端列宽不变,视口窄于 13rem 时下限跟着收缩,
|
||||
避免固定下限把内容撑出容器产生横向溢出。 */
|
||||
grid-template-columns: repeat(auto-fill, minmax(min(13rem, 100%), 1fr));
|
||||
gap: 0.6rem;
|
||||
}
|
||||
.game-lineage-node {
|
||||
|
||||
Reference in New Issue
Block a user