fix(游戏共创): 源码公开确认改回默认勾选(与「允许共创即开放源码」口径一致)

按产品裁定回落默认值:保留主动取消能力与全部知情告知,但确认项**默认已勾选**——
「允许共创本身就意味着源码开放,不设独立开关」,默认不勾选等于第二个开关,与本面板
自己写着的那句「没有单独的源码可见性开关」自相矛盾。

- `projectBundleConfirmed` 初值 true;每次重新选择授权档位都重置为「非禁止档位即已勾选」,
  避免把上一次的取消状态带到新的档位选择上(禁止共创时不显示该确认项,值本身无意义)。
- 告知文案一字不减(整个项目目录原样公开、含文档/截图/设计稿/笔记、只自动排除依赖/版本库/
  构建产物/常见凭据、一经发布不可撤销、无法收回已下载内容)。
- 新增一句提示说明默认值来源:「上面这项默认已勾选(平台按「允许共创即开放源码」的默认帮你
  勾上,不是替你做决定——你可以随时取消)。」——避免作者以为平台偷偷替他决定。
- 未勾选时的后果提示保留,仍写清「不勾选则本次不上传工程源码……」。

测试(面板 23 条):默认「禁止共创」无确认项;切到非禁止档位后确认项出现且**已勾选**,
并逐条断言告知文案 + 「默认已勾选/可随时取消」;默认状态提交不携带 `includeProjectBundle`
(交给原生按授权执行上传);作者主动取消后提交携带 `includeProjectBundle: false`(这是
「作者能退出」的证据);重新选择档位回到已勾选;切回「禁止共创」确认项消失。
This commit is contained in:
2026-10-05 16:31:02 +08:00
parent 7972089a5f
commit 18d19d5d54
2 changed files with 51 additions and 18 deletions
@@ -254,13 +254,14 @@ export function GameDistributionPublishPanel({
GAME_DISTRIBUTION_DEFAULT_FORK_AUTHORIZATION,
);
/**
* 作者是否已**主动确认**随本次发布公开整个项目目录(默认不勾选)。
* 作者是否确认随本次发布公开整个项目目录(**默认已勾选**,作者可主动取消)。
*
* 产品口径:授权非「禁止共创」即允许他人改编,但工程源码的拒绝规则是黑名单——作者的
* `docs/`、截图、设计稿、笔记等不在任何拒绝集内,会随包原样外发。所以这里不做「默认帮作者
* 上传」,而是要求作者确认后才能上传;不确认则作品照常发布,只是退化为「只能被产物级改编」。
* 产品口径:选择「允许共创」本身就意味着源码开放,不设独立开关——因此默认值必须与授权语义
* 一致(默认勾选)。但工程源包的拒绝规则是黑名单,作者的 `docs/`、截图、设计稿、笔记等会随包
* 原样外发,所以知情告知一字不减,并且作者可以随时取消:取消即本次不上传源码,作品照常发布,
* 只是退化为「只能被产物级改编」。
*/
const [projectBundleConfirmed, setProjectBundleConfirmed] = useState(false);
const [projectBundleConfirmed, setProjectBundleConfirmed] = useState(true);
const [busy, setBusy] = useState(false);
const [error, setError] = useState('');
const [result, setResult] = useState<GameDistributionPublishResult | null>(
@@ -960,7 +961,12 @@ export function GameDistributionPublishPanel({
value={value}
checked={forkAuthorization === value}
disabled={forkAuthorizationLocked}
onChange={() => setForkAuthorization(value)}
onChange={() => {
setForkAuthorization(value);
// 每次选择授权档位都回到「默认已勾选」:避免把上一次的取消状态带到
// 新的档位选择上(禁止共创时不显示也不上传,值本身无意义)。
setProjectBundleConfirmed(value !== 'forbidden');
}}
/>
<span>
{GAME_DISTRIBUTION_FORK_AUTHORIZATION_LABELS[value]}
@@ -999,8 +1005,9 @@ export function GameDistributionPublishPanel({
) : null}
{forkAuthorization !== 'forbidden' && !forkAuthorizationLocked ? (
<p className="game-distribution-publish-panel__fork-hint">
不勾选则本次不上传工程源码:作品仍可正常发布,但他人只能拿到已构建成品试玩与
参考,不能直接重新发布。
{'上面这项'}
<strong>默认已勾选</strong>
{'(平台按「允许共创即开放源码」的默认帮你勾上,不是替你做决定——你可以随时取消)。不勾选则本次不上传工程源码:作品仍可正常发布,但他人只能拿到已构建成品试玩与参考,不能直接重新发布。'}
</p>
) : null}
</fieldset>
@@ -382,7 +382,7 @@ describe('GameDistributionPublishPanel', () => {
expect(args?.metadata?.forkAuthorization).toBe('full');
});
test('授权开放后才出现源码公开确认,默认不勾选并如实告知后果', async () => {
test('授权开放后确认项出现且默认已勾选,并如实告知后果', async () => {
installTauriInvoke(async () => undefined);
vi.mocked(publishLocalProjectGame).mockResolvedValue({
gameId: 'game_1',
@@ -400,8 +400,8 @@ describe('GameDistributionPublishPanel', () => {
fireEvent.click(screen.getByRole('radio', { name: '允许非商用共创' }));
const consent = await screen.findByRole('checkbox');
// 知情同意:必须由作者主动确认,默认不勾选。
expect(consent).toHaveProperty('checked', false);
// 产品口径:允许共创即开放源码,不设独立开关 → 默认已勾选(作者可主动取消)。
expect(consent).toHaveProperty('checked', true);
// 告知必须如实:整个项目目录会外发、只自动排除少数几类、且不可撤销。
expect(screen.getByText(/我已确认项目里没有不愿公开的内容/u)).not.toBeNull();
expect(screen.getByText(/整个项目目录/u)).not.toBeNull();
@@ -409,7 +409,10 @@ describe('GameDistributionPublishPanel', () => {
expect(screen.getByText(/只会自动排除依赖目录、版本库、构建产物与常见凭据文件/u)).not.toBeNull();
expect(screen.getByText(/不可撤销/u)).not.toBeNull();
expect(screen.getByText(/无法收回别人已经下载的内容/u)).not.toBeNull();
// 不勾选的后果也要在发布前就说清楚。
// 默认值是平台替你勾上的,这一点要写出来,避免作者以为平台偷偷替他决定。
expect(screen.getByText(/默认已勾选/u)).not.toBeNull();
expect(screen.getByText(/你可以随时取消/u)).not.toBeNull();
// 取消的后果也要在发布前就说清楚。
expect(
screen.getByText(/不勾选则本次不上传工程源码/u),
).not.toBeNull();
@@ -418,7 +421,7 @@ describe('GameDistributionPublishPanel', () => {
screen.getByText(/没有单独的源码可见性开关/u),
).not.toBeNull();
// 未确认 → 明确告诉原生不要把工程源码交出去(作品照常发布)。
// 默认勾选 → 不携带 includeProjectBundle,交给原生按「授权非禁止」执行上传。
await selectCover();
fireEvent.click(screen.getByRole('button', { name: '发布游戏' }));
await waitFor(() =>
@@ -426,10 +429,10 @@ describe('GameDistributionPublishPanel', () => {
);
expect(
vi.mocked(publishLocalProjectGame).mock.calls[0]?.[0],
).toMatchObject({ includeProjectBundle: false });
).not.toHaveProperty('includeProjectBundle');
});
test('确认公开整个项目目录后才上传工程源码', async () => {
test('作者主动取消确认后,本次不上传工程源码(作品照常发布)', async () => {
installTauriInvoke(async () => undefined);
vi.mocked(publishLocalProjectGame).mockResolvedValue({
gameId: 'game_1',
@@ -445,7 +448,7 @@ describe('GameDistributionPublishPanel', () => {
fireEvent.click(screen.getByRole('radio', { name: '允许全开放共创' }));
fireEvent.click(await screen.findByRole('checkbox'));
await waitFor(() =>
expect(screen.queryByRole('checkbox')).toHaveProperty('checked', true),
expect(screen.queryByRole('checkbox')).toHaveProperty('checked', false),
);
await selectCover();
@@ -453,10 +456,33 @@ describe('GameDistributionPublishPanel', () => {
await waitFor(() =>
expect(publishLocalProjectGame).toHaveBeenCalledTimes(1),
);
// 已确认时不带 includeProjectBundle:由原生按「授权非禁止」执行上传。
// 这是「作者能退出」的证据:显式 false 表示源码不外发。
expect(
vi.mocked(publishLocalProjectGame).mock.calls[0]?.[0],
).not.toHaveProperty('includeProjectBundle');
).toMatchObject({ includeProjectBundle: false });
});
test('重新选择授权档位会回到「默认已勾选」,切回禁止共创则不出现确认项', async () => {
installTauriInvoke(async () => undefined);
renderPanel();
// 取消后换一个档位:不应把上一次的取消带过去。
fireEvent.click(screen.getByRole('radio', { name: '允许非商用共创' }));
fireEvent.click(await screen.findByRole('checkbox'));
await waitFor(() =>
expect(screen.queryByRole('checkbox')).toHaveProperty('checked', false),
);
fireEvent.click(screen.getByRole('radio', { name: '允许全开放共创' }));
await waitFor(() =>
expect(screen.queryByRole('checkbox')).toHaveProperty('checked', true),
);
// 切回「禁止共创」:确认项消失(源码谁也取不走,不需要这个动作)。
fireEvent.click(screen.getByRole('radio', { name: '禁止共创' }));
await waitFor(() => expect(screen.queryByRole('checkbox')).toBeNull());
expect(
screen.getByRole('radio', { name: '禁止共创' }),
).toHaveProperty('checked', true);
});
test('工程源包未上传时成功页如实提示只能产物级改编', async () => {