Merge pull request '我的页签恢复邀请好友入口,AGC 客户端账号菜单接入邀请码与玩家社区' (#578) from codex/profile-invite-entry into master
Project CI / AI game creator shell Rust smoke (push) Successful in 2m22s
Project CI / AI game creator shell Rust crates (push) Successful in 1m39s
Project CI / Native shell tests (push) Failing after 55s
Project CI / Frontend tests (push) Successful in 1m44s
Project CI / Backend tests (push) Successful in 4m45s
Project CI / Repository checks (push) Successful in 2m22s
Project CI / AI game creator shell web tests (push) Successful in 1m40s
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Successful in 2m22s
Project CI / AI game creator shell Rust crates (push) Successful in 1m39s
Project CI / Native shell tests (push) Failing after 55s
Project CI / Frontend tests (push) Successful in 1m44s
Project CI / Backend tests (push) Successful in 4m45s
Project CI / Repository checks (push) Successful in 2m22s
Project CI / AI game creator shell web tests (push) Successful in 1m40s
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
This commit was merged in pull request #578.
This commit is contained in:
@@ -140,6 +140,7 @@ const allowedUncalledTauriCommands = [
|
||||
// 里以字面量出现,静态扫描仍按共享注册表核验。
|
||||
'read_profile_recharge_center',
|
||||
'read_profile_wallet_ledger',
|
||||
'read_profile_referral_invite_center',
|
||||
'create_profile_recharge_order',
|
||||
'confirm_wechat_profile_recharge_order',
|
||||
'redeem_profile_reward_code',
|
||||
|
||||
@@ -8,10 +8,12 @@ use crate::http_client::agc_main_site_client_builder;
|
||||
use crate::platform_session::{current_platform_session, PlatformSessionSnapshot};
|
||||
use reqwest::{Method, StatusCode};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
use shared_contracts::runtime::{
|
||||
ConfirmWechatProfileRechargeOrderResponse, CreateProfileRechargeOrderResponse,
|
||||
ProfileRechargeCenterResponse, ProfileWalletLedgerResponse, RedeemProfileRewardCodeResponse,
|
||||
ProfileRechargeCenterResponse, ProfileReferralInviteCenterResponse,
|
||||
ProfileWalletLedgerResponse, RedeemProfileRewardCodeResponse,
|
||||
};
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
@@ -103,6 +105,29 @@ fn unwrap_envelope(body: &str, fallback: &str) -> Result<Value, String> {
|
||||
Ok(value.get("data").cloned().unwrap_or(value))
|
||||
}
|
||||
|
||||
/// 客户端邀请码弹层需要的完整数据。
|
||||
///
|
||||
/// 后端只返回站内相对路径(`inviteLinkPath`),客户端要拿它当分享链接用,
|
||||
/// 所以这里用当前登录站点 origin 补全成完整 URL 再交给渲染层。
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct ClientProfileReferralInviteCenter {
|
||||
pub(crate) center: ProfileReferralInviteCenterResponse,
|
||||
pub(crate) invite_url: String,
|
||||
}
|
||||
|
||||
fn resolve_invite_url(api_base_url: &str, invite_link_path: &str) -> String {
|
||||
if invite_link_path.trim().is_empty() {
|
||||
return api_base_url.trim_end_matches('/').to_string();
|
||||
}
|
||||
match Url::parse(&format!("{}/", api_base_url.trim_end_matches('/')))
|
||||
.and_then(|base| base.join(invite_link_path))
|
||||
{
|
||||
Ok(url) => url.to_string(),
|
||||
Err(_) => invite_link_path.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn request_json<T: DeserializeOwned>(
|
||||
client: &reqwest::Client,
|
||||
snapshot: &PlatformSessionSnapshot,
|
||||
@@ -171,6 +196,24 @@ pub(crate) async fn read_profile_wallet_ledger() -> Result<ProfileWalletLedgerRe
|
||||
.await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) async fn read_profile_referral_invite_center(
|
||||
) -> Result<ClientProfileReferralInviteCenter, String> {
|
||||
let snapshot = require_session()?;
|
||||
let client = build_client()?;
|
||||
let center: ProfileReferralInviteCenterResponse = request_json(
|
||||
&client,
|
||||
&snapshot,
|
||||
Method::GET,
|
||||
&["api", "profile", "referrals", "invite-center"],
|
||||
None,
|
||||
"读取邀请码失败",
|
||||
)
|
||||
.await?;
|
||||
let invite_url = resolve_invite_url(&snapshot.api_base_url, ¢er.invite_link_path);
|
||||
Ok(ClientProfileReferralInviteCenter { center, invite_url })
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) async fn create_profile_recharge_order(
|
||||
product_id: String,
|
||||
@@ -314,4 +357,20 @@ mod tests {
|
||||
assert!(bounded_business_id(&"x".repeat(MAX_BUSINESS_ID_CHARS + 1), "订单").is_err());
|
||||
assert!(bounded_business_id("bad\nid", "订单").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invite_link_resolves_against_login_origin() {
|
||||
assert_eq!(
|
||||
resolve_invite_url("https://dev.genarrative.world", "/?inviteCode=SY12345678"),
|
||||
"https://dev.genarrative.world/?inviteCode=SY12345678"
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_invite_url("https://dev.genarrative.world/", "invite/SY12345678"),
|
||||
"https://dev.genarrative.world/invite/SY12345678"
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_invite_url("https://dev.genarrative.world", ""),
|
||||
"https://dev.genarrative.world"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -638,6 +638,7 @@ pub(super) fn run() {
|
||||
set_client_server_selection,
|
||||
read_profile_recharge_center,
|
||||
read_profile_wallet_ledger,
|
||||
read_profile_referral_invite_center,
|
||||
create_profile_recharge_order,
|
||||
confirm_wechat_profile_recharge_order,
|
||||
redeem_profile_reward_code,
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { PlatformProfileReferralModal } from '../../../../../packages/shared/src/components/PlatformProfileReferralModal';
|
||||
import type {
|
||||
AccountReferralController,
|
||||
AccountReferralPanel,
|
||||
} from './useAccountReferral';
|
||||
|
||||
/** 客户端账号菜单的入口文案是「邀请码 / 玩家社区」,弹层标题跟着入口走。 */
|
||||
const CLIENT_REFERRAL_PANEL_TITLE: Record<AccountReferralPanel, string> = {
|
||||
invite: '邀请码',
|
||||
community: '玩家社区',
|
||||
};
|
||||
|
||||
/**
|
||||
* 账号菜单的邀请码 / 玩家社区弹层。
|
||||
*
|
||||
* 弹层实现与主站「我的」页签共用 `packages/shared` 里的同一份
|
||||
* `PlatformProfileReferralModal`(与充值、泥点账单同口径);这里只做宿主适配:
|
||||
* 把 Tauri typed command 读到的邀请中心、会话 origin 补全后的邀请链接、宿主剪贴板
|
||||
* 复制回显和面板开关接到共享组件上,不再另写一份客户端专属弹层。
|
||||
*/
|
||||
export function AccountReferralDialogs({
|
||||
controller,
|
||||
}: {
|
||||
controller: AccountReferralController;
|
||||
}) {
|
||||
if (!controller.panel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<PlatformProfileReferralModal
|
||||
panel={controller.panel}
|
||||
title={CLIENT_REFERRAL_PANEL_TITLE[controller.panel]}
|
||||
center={controller.center}
|
||||
inviteLinkUrl={controller.inviteUrl}
|
||||
isLoading={controller.loading}
|
||||
isSubmittingRedeem={false}
|
||||
redeemCode=""
|
||||
copyInviteLinkState={controller.copyInviteLinkState}
|
||||
copyInviteCodeState={controller.copyInviteCodeState}
|
||||
error={controller.error}
|
||||
success={null}
|
||||
onClose={controller.closePanel}
|
||||
onCopyInviteLink={controller.copyInviteLink}
|
||||
onCopyInviteCode={controller.copyInviteCode}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -41,6 +41,7 @@ import { useDirectActiveTurns } from '../agent-runtime/directActiveTurns';
|
||||
import { projectPathsMatchForInvalidation } from '../project-summary/projectPath';
|
||||
import { RuntimeConfigDialog } from '../runtime-config/RuntimeConfigDialog';
|
||||
import { useTemplateLibrary } from '../template-library/useTemplateLibrary';
|
||||
import { AccountReferralDialogs } from './AccountReferral';
|
||||
import { AccountWalletBar, AccountWalletDialogs } from './AccountWallet';
|
||||
import {
|
||||
type LauncherNotice,
|
||||
@@ -52,6 +53,7 @@ import type {
|
||||
WorkspaceLauncherShellProps,
|
||||
} from './model';
|
||||
import { NonEmptyProjectDialog, ProjectsPage } from './ProjectCreation';
|
||||
import { useAccountReferral } from './useAccountReferral';
|
||||
import { useAccountWallet } from './useAccountWallet';
|
||||
import {
|
||||
DESIGN_ARTIFACTS_BUILD_PROMPT,
|
||||
@@ -73,6 +75,7 @@ export function WorkspaceLauncherShell({
|
||||
setActiveProjectRuns,
|
||||
} = useWindowChrome();
|
||||
const accountWallet = useAccountWallet(currentUser.id);
|
||||
const accountReferral = useAccountReferral(currentUser.id);
|
||||
const [status, setStatus] = useState('');
|
||||
const recentProjects = useRecentProjects(setStatus);
|
||||
const { recentProjectRows, rememberRecentWorkspace } = recentProjects;
|
||||
@@ -741,6 +744,11 @@ export function WorkspaceLauncherShell({
|
||||
}}
|
||||
onNoticeRequest={showLauncherNotice}
|
||||
onRechargeRequest={accountWallet.openRecharge}
|
||||
onReferralRequest={(panel) =>
|
||||
panel === 'invite'
|
||||
? accountReferral.openInvite()
|
||||
: accountReferral.openCommunity()
|
||||
}
|
||||
onRuntimeConfigOpen={() => setRuntimeConfigOpen(true)}
|
||||
onViewChange={navigateLauncher}
|
||||
/>
|
||||
@@ -963,6 +971,7 @@ export function WorkspaceLauncherShell({
|
||||
/>
|
||||
) : null}
|
||||
<AccountWalletDialogs controller={accountWallet} />
|
||||
<AccountReferralDialogs controller={accountReferral} />
|
||||
{walletSlot ? (
|
||||
createPortal(
|
||||
<AccountWalletBar controller={accountWallet} />,
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
import { writeText } from '@tauri-apps/plugin-clipboard-manager';
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
|
||||
import type { PlatformProfileReferralCopyState } from '../../../../../packages/shared/src/components/PlatformProfileReferralModal';
|
||||
import type { ProfileReferralInviteCenterResponse } from '../../../../../packages/shared/src/contracts/runtime';
|
||||
import { getClientProfileReferralInviteCenter } from '../../services/accountHost';
|
||||
|
||||
export type AccountReferralPanel = 'invite' | 'community';
|
||||
|
||||
/**
|
||||
* 客户端账号菜单的邀请码 / 玩家社区弹层控制器。
|
||||
*
|
||||
* 邀请码是账号级数据,和泥点余额一样按登录账号隔离:切换账号就丢弃上一位用户的
|
||||
* 邀请中心和复制回显;读取过程中换账号时,晚到的响应直接作废。
|
||||
*/
|
||||
export function useAccountReferral(currentUserId: string) {
|
||||
const [panel, setPanel] = useState<AccountReferralPanel | null>(null);
|
||||
const [center, setCenter] =
|
||||
useState<ProfileReferralInviteCenterResponse | null>(null);
|
||||
const [inviteUrl, setInviteUrl] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [copyInviteLinkState, setCopyInviteLinkState] =
|
||||
useState<PlatformProfileReferralCopyState>('idle');
|
||||
const [copyInviteCodeState, setCopyInviteCodeState] =
|
||||
useState<PlatformProfileReferralCopyState>('idle');
|
||||
const lifecycleRef = useRef(0);
|
||||
const currentUserIdRef = useRef(currentUserId);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
currentUserIdRef.current = currentUserId;
|
||||
lifecycleRef.current += 1;
|
||||
}, [currentUserId]);
|
||||
|
||||
useEffect(() => {
|
||||
lifecycleRef.current += 1;
|
||||
setPanel(null);
|
||||
setCenter(null);
|
||||
setInviteUrl(null);
|
||||
setLoading(false);
|
||||
setError(null);
|
||||
setCopyInviteLinkState('idle');
|
||||
setCopyInviteCodeState('idle');
|
||||
}, [currentUserId]);
|
||||
|
||||
async function loadInviteCenter(lifecycle: number, owner: string) {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const response = await getClientProfileReferralInviteCenter();
|
||||
if (
|
||||
lifecycleRef.current !== lifecycle ||
|
||||
currentUserIdRef.current !== owner
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setCenter(response.center);
|
||||
setInviteUrl(response.inviteUrl);
|
||||
} catch (loadError) {
|
||||
if (
|
||||
lifecycleRef.current !== lifecycle ||
|
||||
currentUserIdRef.current !== owner
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setCenter(null);
|
||||
setInviteUrl(null);
|
||||
setError(
|
||||
loadError instanceof Error ? loadError.message : '读取邀请码失败',
|
||||
);
|
||||
} finally {
|
||||
if (
|
||||
lifecycleRef.current === lifecycle &&
|
||||
currentUserIdRef.current === owner
|
||||
) {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function openPanel(nextPanel: AccountReferralPanel) {
|
||||
const lifecycle = ++lifecycleRef.current;
|
||||
setPanel(nextPanel);
|
||||
setError(null);
|
||||
setCopyInviteLinkState('idle');
|
||||
setCopyInviteCodeState('idle');
|
||||
if (nextPanel !== 'invite') {
|
||||
return;
|
||||
}
|
||||
void loadInviteCenter(lifecycle, currentUserId);
|
||||
}
|
||||
|
||||
function closePanel() {
|
||||
lifecycleRef.current += 1;
|
||||
setPanel(null);
|
||||
setLoading(false);
|
||||
setCopyInviteLinkState('idle');
|
||||
setCopyInviteCodeState('idle');
|
||||
}
|
||||
|
||||
async function copyValue(
|
||||
applyState: (state: PlatformProfileReferralCopyState) => void,
|
||||
value: string,
|
||||
) {
|
||||
try {
|
||||
await writeText(value);
|
||||
applyState('copied');
|
||||
} catch {
|
||||
applyState('failed');
|
||||
}
|
||||
}
|
||||
|
||||
function copyInviteCode() {
|
||||
if (!center?.inviteCode) {
|
||||
return;
|
||||
}
|
||||
void copyValue(setCopyInviteCodeState, center.inviteCode);
|
||||
}
|
||||
|
||||
function copyInviteLink() {
|
||||
if (!inviteUrl) {
|
||||
return;
|
||||
}
|
||||
void copyValue(setCopyInviteLinkState, inviteUrl);
|
||||
}
|
||||
|
||||
return {
|
||||
center,
|
||||
copyInviteCodeState,
|
||||
copyInviteCode,
|
||||
copyInviteLink,
|
||||
copyInviteLinkState,
|
||||
error,
|
||||
inviteUrl,
|
||||
loading,
|
||||
panel,
|
||||
openInvite: () => openPanel('invite'),
|
||||
openCommunity: () => openPanel('community'),
|
||||
closePanel,
|
||||
};
|
||||
}
|
||||
|
||||
export type AccountReferralController = ReturnType<typeof useAccountReferral>;
|
||||
@@ -2,6 +2,7 @@ import type {
|
||||
ConfirmWechatProfileRechargeOrderResponse,
|
||||
CreateProfileRechargeOrderResponse,
|
||||
ProfileRechargeCenterResponse,
|
||||
ProfileReferralInviteCenterResponse,
|
||||
ProfileWalletLedgerResponse,
|
||||
RedeemProfileRewardCodeResponse,
|
||||
} from '../../../../packages/shared/src/contracts/runtime';
|
||||
@@ -40,6 +41,17 @@ export function getClientProfileWalletLedger() {
|
||||
);
|
||||
}
|
||||
|
||||
export type ClientProfileReferralInviteCenterResponse = {
|
||||
center: ProfileReferralInviteCenterResponse;
|
||||
inviteUrl: string;
|
||||
};
|
||||
|
||||
export function getClientProfileReferralInviteCenter() {
|
||||
return invokeAccount<ClientProfileReferralInviteCenterResponse>(
|
||||
'read_profile_referral_invite_center',
|
||||
);
|
||||
}
|
||||
|
||||
export function createClientProfileRechargeOrder(productId: string) {
|
||||
return invokeAccount<CreateProfileRechargeOrderResponse>(
|
||||
'create_profile_recharge_order',
|
||||
|
||||
@@ -46,6 +46,7 @@ type LauncherSidebarProps = {
|
||||
onViewChange: (view: LauncherView) => void;
|
||||
onNoticeRequest: (title: string) => void;
|
||||
onRechargeRequest: () => void;
|
||||
onReferralRequest: (panel: 'invite' | 'community') => void;
|
||||
onRuntimeConfigOpen: () => void;
|
||||
onLogout: () => void;
|
||||
};
|
||||
@@ -56,6 +57,7 @@ type SidebarAccountMenuProps = {
|
||||
onLogout: () => void;
|
||||
onNoticeRequest: (title: string) => void;
|
||||
onRechargeRequest: () => void;
|
||||
onReferralRequest: (panel: 'invite' | 'community') => void;
|
||||
};
|
||||
|
||||
function formatMudPoints(value: number | null | undefined) {
|
||||
@@ -71,6 +73,7 @@ function SidebarAccountMenu({
|
||||
onLogout,
|
||||
onNoticeRequest,
|
||||
onRechargeRequest,
|
||||
onReferralRequest,
|
||||
}: SidebarAccountMenuProps) {
|
||||
const walletBalance = useWalletStore(
|
||||
(state) => state.mudPointBalance?.totalPoints ?? null,
|
||||
@@ -164,6 +167,28 @@ function SidebarAccountMenu({
|
||||
>
|
||||
使用指南
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="menuitem"
|
||||
className={sidebarMenuItemClass}
|
||||
onClick={() => {
|
||||
onClose();
|
||||
onReferralRequest('invite');
|
||||
}}
|
||||
>
|
||||
邀请码
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="menuitem"
|
||||
className={sidebarMenuItemClass}
|
||||
onClick={() => {
|
||||
onClose();
|
||||
onReferralRequest('community');
|
||||
}}
|
||||
>
|
||||
玩家社区
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="menuitem"
|
||||
@@ -198,6 +223,7 @@ export function Sidebar({
|
||||
onViewChange,
|
||||
onNoticeRequest,
|
||||
onRechargeRequest,
|
||||
onReferralRequest,
|
||||
onRuntimeConfigOpen,
|
||||
onLogout,
|
||||
}: LauncherSidebarProps) {
|
||||
@@ -324,6 +350,7 @@ export function Sidebar({
|
||||
onLogout={onLogout}
|
||||
onNoticeRequest={onNoticeRequest}
|
||||
onRechargeRequest={onRechargeRequest}
|
||||
onReferralRequest={onReferralRequest}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
confirmClientWechatProfileRechargeOrder,
|
||||
createClientProfileRechargeOrder,
|
||||
getClientProfileRechargeCenter,
|
||||
getClientProfileReferralInviteCenter,
|
||||
getClientProfileWalletLedger,
|
||||
redeemClientProfileRewardCode,
|
||||
} from '../src/services/accountHost';
|
||||
@@ -47,6 +48,7 @@ test('账户读取只提交结构化参数,不携带 token 或远端地址', a
|
||||
|
||||
await getClientProfileRechargeCenter();
|
||||
await getClientProfileWalletLedger();
|
||||
await getClientProfileReferralInviteCenter();
|
||||
await createClientProfileRechargeOrder('product-1');
|
||||
await confirmClientWechatProfileRechargeOrder('order-1');
|
||||
await redeemClientProfileRewardCode('CODE-1');
|
||||
@@ -54,6 +56,7 @@ test('账户读取只提交结构化参数,不携带 token 或远端地址', a
|
||||
expect(invoke.mock.calls).toEqual([
|
||||
['read_profile_recharge_center', undefined],
|
||||
['read_profile_wallet_ledger', undefined],
|
||||
['read_profile_referral_invite_center', undefined],
|
||||
['create_profile_recharge_order', { productId: 'product-1' }],
|
||||
['confirm_wechat_profile_recharge_order', { orderId: 'order-1' }],
|
||||
['redeem_profile_reward_code', { code: 'CODE-1' }],
|
||||
|
||||
@@ -120,6 +120,9 @@ vi.mock('@tauri-apps/plugin-clipboard-manager', () => ({
|
||||
throw new Error('no native clipboard image');
|
||||
}),
|
||||
readText: vi.fn(async () => nativeClipboardMock.text),
|
||||
writeText: vi.fn(async (text: string) => {
|
||||
nativeClipboardMock.text = text;
|
||||
}),
|
||||
}));
|
||||
|
||||
import type { AuthUser } from '../../../../packages/shared/src/contracts/auth';
|
||||
|
||||
@@ -1076,6 +1076,8 @@ export function registerClientHomeTests() {
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '我的' }));
|
||||
expect(screen.getByRole('menuitem', { name: '使用指南' })).not.toBeNull();
|
||||
expect(screen.getByRole('menuitem', { name: '邀请码' })).not.toBeNull();
|
||||
expect(screen.getByRole('menuitem', { name: '玩家社区' })).not.toBeNull();
|
||||
expect(screen.getByRole('menuitem', { name: '反馈联系' })).not.toBeNull();
|
||||
expect(screen.getByRole('menuitem', { name: '退出登录' })).not.toBeNull();
|
||||
fireEvent.click(screen.getByRole('button', { name: '帮助' }));
|
||||
@@ -1090,6 +1092,74 @@ export function registerClientHomeTests() {
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: '使用指南' }));
|
||||
expect(screen.getByRole('dialog', { name: '使用指南' })).not.toBeNull();
|
||||
});
|
||||
|
||||
it('opens the invite code and community panels from the account menu', async () => {
|
||||
const inviteCenter = {
|
||||
inviteCode: 'SY12345678',
|
||||
inviteLinkPath: '/?inviteCode=SY12345678',
|
||||
invitedCount: 2,
|
||||
rewardedInviteCount: 2,
|
||||
todayInviterRewardCount: 1,
|
||||
todayInviterRewardRemaining: 9,
|
||||
rewardPoints: 30,
|
||||
invitedUsers: [],
|
||||
hasRedeemedCode: false,
|
||||
boundInviterUserId: null,
|
||||
boundAt: null,
|
||||
updatedAt: '2026-10-01T00:00:00.000Z',
|
||||
};
|
||||
const inviteLink = 'https://dev.genarrative.world/?inviteCode=SY12345678';
|
||||
const invoke = vi.fn(async (command: string) => {
|
||||
if (command === 'read_profile_referral_invite_center') {
|
||||
return { center: inviteCenter, inviteUrl: inviteLink };
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
window.__TAURI__ = { core: { invoke } };
|
||||
renderLauncherAt('/?launcher');
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '我的' }));
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: '邀请码' }));
|
||||
|
||||
const inviteDialog = await screen.findByRole('dialog', { name: '邀请码' });
|
||||
expect(invoke).toHaveBeenCalledWith(
|
||||
'read_profile_referral_invite_center',
|
||||
undefined,
|
||||
);
|
||||
expect(within(inviteDialog).getByText('SY12345678')).not.toBeNull();
|
||||
expect(within(inviteDialog).getByText(inviteLink)).not.toBeNull();
|
||||
|
||||
fireEvent.click(
|
||||
within(inviteDialog).getByRole('button', { name: '复制邀请链接' }),
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(nativeClipboardMock.text).toBe(inviteLink);
|
||||
});
|
||||
|
||||
fireEvent.click(
|
||||
within(inviteDialog).getByRole('button', { name: '复制邀请码' }),
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(nativeClipboardMock.text).toBe('SY12345678');
|
||||
});
|
||||
|
||||
fireEvent.click(
|
||||
within(inviteDialog).getByRole('button', { name: '关闭邀请码' }),
|
||||
);
|
||||
expect(screen.queryByRole('dialog', { name: '邀请码' })).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '我的' }));
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: '玩家社区' }));
|
||||
const communityDialog = await screen.findByRole('dialog', {
|
||||
name: '玩家社区',
|
||||
});
|
||||
expect(
|
||||
within(communityDialog).getByAltText('玩家社区微信群二维码'),
|
||||
).not.toBeNull();
|
||||
expect(
|
||||
within(communityDialog).getByAltText('玩家社区 QQ 群二维码'),
|
||||
).not.toBeNull();
|
||||
});
|
||||
}
|
||||
|
||||
export function registerHomeProjectCreationTests() {
|
||||
|
||||
Reference in New Issue
Block a user