文案收敛:动作名统一 Fork,去掉「可玩参考 + 素材 / 不能重新发布」那套表述
- 动作名统一:「改造这个作品」→「Fork 这个作品」,「登录后可改造」→「登录后可 Fork」 - 面板指引改为「在陶泥儿客户端里打开「从平台作品开始创作」,粘贴下面的作品 ID 或作品链接。」 - 对外口径统一为「Fork = 把整个工程复制一份到你的项目里继续改造(与 git fork 同理)」,并保留一句话必要告知:整个工程(含素材与文档)复制后对他人可见,且不可撤销 - 删除「成品包只能可玩参考 + 素材」「不能直接再次发布」「参考产物在 reference/<作品ID>/」等展开说明(冻结口径 1) - 测试同步:断言新文案与「不可撤销」告知,测试名与注释不再出现「改造」
This commit is contained in:
@@ -344,8 +344,8 @@ function GameDetailContent({
|
||||
);
|
||||
const lineage = game.lineage ?? null;
|
||||
const forkCount = game.forkCount ?? 0;
|
||||
// 「改造这个作品」的获取方式:网页不能唤起客户端(未注册 deep link),所以给出可复制的
|
||||
// 作品 ID 与完整作品链接,让用户到客户端的「从平台作品开始创作」里粘贴。
|
||||
// Fork 的获取方式:主路径是唤起客户端(见下方 handleForkLaunch),失败才降级为
|
||||
// 「复制作品 ID / 链接 → 到客户端粘贴」。
|
||||
const [isForkPanelOpen, setIsForkPanelOpen] = useState(false);
|
||||
const { copyState: forkIdCopyState, copyText: copyForkId } = useCopyFeedback();
|
||||
const { copyState: forkLinkCopyState, copyText: copyForkLink } =
|
||||
@@ -445,10 +445,9 @@ function GameDetailContent({
|
||||
) : null}
|
||||
{canRemix ? (
|
||||
/*
|
||||
「改造这个作品」:服务端取件通道要求 Bearer + 已公开 + 授权非禁止。
|
||||
未登录按仓库既有登录门禁处理——入口照常出现(显示「登录后可改造」),
|
||||
Fork 入口:服务端取件通道要求 Bearer + 已公开 + 授权非禁止。
|
||||
未登录按仓库既有登录门禁处理——入口照常出现(显示「登录后可 Fork」),
|
||||
点击走登录弹窗,而不是静默消失。
|
||||
网页不能唤起客户端(未注册 deep link),所以展开的是「复制 ID/链接 → 到客户端粘贴」。
|
||||
*/
|
||||
<>
|
||||
<button
|
||||
@@ -466,7 +465,7 @@ function GameDetailContent({
|
||||
: onLogin()
|
||||
}
|
||||
>
|
||||
{isAuthenticated ? '改造这个作品' : '登录后可改造'}
|
||||
{isAuthenticated ? 'Fork 这个作品' : '登录后可 Fork'}
|
||||
</button>
|
||||
{isAuthenticated && isForkPanelOpen ? (
|
||||
<span
|
||||
@@ -474,8 +473,8 @@ function GameDetailContent({
|
||||
id="game-fork-remix-panel"
|
||||
>
|
||||
<span className="game-fork-remix-steps">
|
||||
打开陶泥儿客户端 → 首页「从平台作品开始创作」→
|
||||
粘贴下面的作品 ID 或作品链接。
|
||||
在陶泥儿客户端里打开「从平台作品开始创作」,粘贴下面的作品
|
||||
ID 或作品链接。
|
||||
</span>
|
||||
<span className="game-fork-remix-value">
|
||||
作品 ID:<code>{game.id}</code>
|
||||
@@ -502,11 +501,8 @@ function GameDetailContent({
|
||||
/>
|
||||
</span>
|
||||
<span className="game-fork-remix-note">
|
||||
客户端会把成品包作为<strong>可玩参考与素材</strong>
|
||||
放进新项目的 <code>reference/{game.id}/</code>{' '}
|
||||
目录;成品包里没有源码,
|
||||
<strong>不能直接再次发布</strong>
|
||||
,请在客户端的新工程里改造后发布。
|
||||
Fork 会把这部作品的整个工程(含素材与文档)复制到你的项目里继续改造;
|
||||
复制后对他人可见,且不可撤销。
|
||||
</span>
|
||||
</span>
|
||||
) : null}
|
||||
|
||||
@@ -344,7 +344,7 @@ describe('GameDetailPage', () => {
|
||||
expect(await screen.findByText('暂未发布可玩版本')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('展示共创授权、代际与溯源信息,并给出改造入口', async () => {
|
||||
it('展示共创授权、代际与溯源信息,并给出 Fork 入口', async () => {
|
||||
getGameMock.mockResolvedValueOnce(
|
||||
createGame({
|
||||
forkAuthorization: 'nonCommercial',
|
||||
@@ -367,9 +367,9 @@ describe('GameDetailPage', () => {
|
||||
expect(screen.getByText('第 2 代作品 · 改编自《原初之光》')).toBeTruthy();
|
||||
expect(screen.getByText('由 原作者 制作')).toBeTruthy();
|
||||
expect(screen.getByText('已被改编 3 次')).toBeTruthy();
|
||||
// 已公开 + 授权开放 → 「改造这个作品」入口出现(M1 时它还不存在,这里钉住新契约)。
|
||||
// 已公开 + 授权开放 → 「Fork 这个作品」入口出现(M1 时它还不存在,这里钉住新契约)。
|
||||
expect(
|
||||
screen.getByRole('button', { name: '改造这个作品' }),
|
||||
screen.getByRole('button', { name: 'Fork 这个作品' }),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
@@ -1355,7 +1355,7 @@ describe('GameLineagePage', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('GameDetailPage 改造入口', () => {
|
||||
describe('GameDetailPage Fork 入口', () => {
|
||||
it('已公开且授权开放时出现入口,展开后给出可复制的作品 ID 与操作指引', async () => {
|
||||
getGameMock.mockResolvedValueOnce(
|
||||
createGame({ forkAuthorization: 'nonCommercial' }),
|
||||
@@ -1364,21 +1364,21 @@ describe('GameDetailPage 改造入口', () => {
|
||||
<GameDetailPage gameId="game-1" onBack={vi.fn()} onPlay={vi.fn()} />,
|
||||
);
|
||||
|
||||
const entry = await screen.findByRole('button', { name: '改造这个作品' });
|
||||
const entry = await screen.findByRole('button', { name: 'Fork 这个作品' });
|
||||
// 未展开前不露出获取方式。
|
||||
expect(screen.queryByText(/打开陶泥儿客户端/u)).toBeNull();
|
||||
expect(screen.queryByText(/从平台作品开始创作/u)).toBeNull();
|
||||
|
||||
fireEvent.click(entry);
|
||||
expect(
|
||||
screen.getByText(
|
||||
'打开陶泥儿客户端 → 首页「从平台作品开始创作」→ 粘贴下面的作品 ID 或作品链接。',
|
||||
'在陶泥儿客户端里打开「从平台作品开始创作」,粘贴下面的作品 ID 或作品链接。',
|
||||
),
|
||||
).toBeTruthy();
|
||||
expect(screen.getByText('game-1')).toBeTruthy();
|
||||
expect(screen.getByText(/\/games\/detail\?id=game-1$/u)).toBeTruthy();
|
||||
// 诚实口径:可玩参考 + 素材、参考产物目录、不能直接再次发布。
|
||||
expect(screen.getByText(/reference\/game-1\//u)).toBeTruthy();
|
||||
expect(screen.getByText(/不能直接再次发布/u)).toBeTruthy();
|
||||
// 冻结口径:Fork = 复制整个工程继续改造;只保留一句话告知(对他人可见 + 不可撤销)。
|
||||
expect(screen.getByText(/整个工程(含素材与文档)复制到你的项目里继续改造/u)).toBeTruthy();
|
||||
expect(screen.getByText(/复制后对他人可见,且不可撤销/u)).toBeTruthy();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '复制作品 ID' }));
|
||||
await waitFor(() =>
|
||||
@@ -1399,7 +1399,7 @@ describe('GameDetailPage 改造入口', () => {
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('授权为禁止共创时不出现改造入口', async () => {
|
||||
it('授权为禁止共创时不出现 Fork 入口', async () => {
|
||||
getGameMock.mockResolvedValueOnce(
|
||||
createGame({ forkAuthorization: 'forbidden' }),
|
||||
);
|
||||
@@ -1408,11 +1408,11 @@ describe('GameDetailPage 改造入口', () => {
|
||||
);
|
||||
|
||||
await screen.findByRole('heading', { name: '星轨防线' });
|
||||
expect(screen.queryByRole('button', { name: '改造这个作品' })).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: '登录后可改造' })).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: 'Fork 这个作品' })).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: '登录后可 Fork' })).toBeNull();
|
||||
});
|
||||
|
||||
it('未公开作品不出现改造入口', async () => {
|
||||
it('未公开作品不出现 Fork 入口', async () => {
|
||||
getMyGameMock.mockResolvedValueOnce({
|
||||
...createGame({ status: 'unpublished', forkAuthorization: 'full' }),
|
||||
latestVersion: null,
|
||||
@@ -1428,11 +1428,11 @@ describe('GameDetailPage 改造入口', () => {
|
||||
);
|
||||
|
||||
await screen.findByText('作品状态');
|
||||
expect(screen.queryByRole('button', { name: '改造这个作品' })).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: '登录后可改造' })).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: 'Fork 这个作品' })).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: '登录后可 Fork' })).toBeNull();
|
||||
});
|
||||
|
||||
it('未登录时入口显示「登录后可改造」,点击走仓库既有登录门禁而不是静默隐藏', async () => {
|
||||
it('未登录时入口显示「登录后可 Fork」,点击走仓库既有登录门禁而不是静默隐藏', async () => {
|
||||
authUiMock.value.user = null;
|
||||
authUiMock.value.canAccessProtectedData = false;
|
||||
getGameMock.mockResolvedValueOnce(
|
||||
@@ -1442,12 +1442,12 @@ describe('GameDetailPage 改造入口', () => {
|
||||
<GameDetailPage gameId="game-1" onBack={vi.fn()} onPlay={vi.fn()} />,
|
||||
);
|
||||
|
||||
const gate = await screen.findByRole('button', { name: '登录后可改造' });
|
||||
expect(screen.queryByText(/打开陶泥儿客户端/u)).toBeNull();
|
||||
const gate = await screen.findByRole('button', { name: '登录后可 Fork' });
|
||||
expect(screen.queryByText(/从平台作品开始创作/u)).toBeNull();
|
||||
|
||||
fireEvent.click(gate);
|
||||
expect(authUiMock.value.openLoginModal).toHaveBeenCalledTimes(1);
|
||||
// 未登录不得展开获取方式(也就不会给出取不到的说明)。
|
||||
expect(screen.queryByText(/打开陶泥儿客户端/u)).toBeNull();
|
||||
expect(screen.queryByText(/从平台作品开始创作/u)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user