Compare commits
No commits in common. "main" and "test" have entirely different histories.
@ -4556,27 +4556,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/integrations/withdrawal-applications": {
|
||||
"post": {
|
||||
"operationId": "createExternalWithdrawalApplication",
|
||||
"description": "来源钱包使用 App 独立 Bearer token 幂等提交提现申请,不使用后台用户 JWT。",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ExternalWithdrawalApplicationInput"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"$ref": "#/components/responses/FinanceWithdrawalApplicationResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/admin/finance/withdrawal-applications": {
|
||||
"get": {
|
||||
"operationId": "listFinanceWithdrawalApplications",
|
||||
@ -14309,14 +14288,6 @@
|
||||
"appName": {
|
||||
"type": "string"
|
||||
},
|
||||
"sourceSystem": {
|
||||
"type": "string",
|
||||
"description": "资金执行来源;hyapp_wallet 为平台钱包,likei 为 Aslan legacy 钱包"
|
||||
},
|
||||
"sourceApplicationId": {
|
||||
"type": "string",
|
||||
"description": "来源钱包的提现申请号,用于跨系统审计和幂等回调"
|
||||
},
|
||||
"userId": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -14391,32 +14362,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ExternalWithdrawalApplicationInput": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"appCode",
|
||||
"sourceSystem",
|
||||
"sourceApplicationId",
|
||||
"userId",
|
||||
"salaryAssetType",
|
||||
"withdrawAmount",
|
||||
"withdrawAmountMinor",
|
||||
"withdrawMethod",
|
||||
"withdrawAddress"
|
||||
],
|
||||
"properties": {
|
||||
"appCode": { "type": "string" },
|
||||
"sourceSystem": { "type": "string" },
|
||||
"sourceApplicationId": { "type": "string" },
|
||||
"userId": { "type": "string" },
|
||||
"salaryAssetType": { "type": "string" },
|
||||
"withdrawAmount": { "type": "string" },
|
||||
"withdrawAmountMinor": { "type": "integer", "minimum": 1 },
|
||||
"withdrawMethod": { "type": "string" },
|
||||
"withdrawAddress": { "type": "string" },
|
||||
"createdAtMs": { "type": "integer", "format": "int64" }
|
||||
}
|
||||
},
|
||||
"FinanceCoinSellerRechargeOrderInput": {
|
||||
"type": "object",
|
||||
"required": ["appCode", "targetUserId", "usdAmount", "providerCode", "externalOrderNo"],
|
||||
|
||||
@ -73,7 +73,6 @@ export function FinanceWithdrawalApplicationList({
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>APP</TableCell>
|
||||
<TableCell>来源单号</TableCell>
|
||||
<TableCell>用户ID</TableCell>
|
||||
<TableCell align="right">提现金额</TableCell>
|
||||
<TableCell>提现方式</TableCell>
|
||||
@ -108,12 +107,6 @@ export function FinanceWithdrawalApplicationList({
|
||||
size="small"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Stacked
|
||||
primary={sourceSystemLabel(item.sourceSystem)}
|
||||
secondary={item.sourceApplicationId || ""}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>{item.userId || "-"}</TableCell>
|
||||
<TableCell align="right">$ {formatAmount(item.withdrawAmount)}</TableCell>
|
||||
<TableCell>{item.withdrawMethod || "-"}</TableCell>
|
||||
@ -221,7 +214,7 @@ export function FinanceWithdrawalApplicationList({
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell align="center" colSpan={isOperationsReview ? 11 : 12}>
|
||||
<TableCell align="center" colSpan={isOperationsReview ? 10 : 11}>
|
||||
当前无数据
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@ -247,16 +240,6 @@ export function FinanceWithdrawalApplicationList({
|
||||
);
|
||||
}
|
||||
|
||||
function sourceSystemLabel(value) {
|
||||
if (!value || value === "hyapp_wallet") {
|
||||
return "HY 钱包";
|
||||
}
|
||||
if (value === "likei") {
|
||||
return "Likei";
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function ReviewContext({ application }) {
|
||||
return (
|
||||
<span className="finance-review-context">
|
||||
|
||||
@ -50,8 +50,6 @@ test("renders withdrawal application table with requested finance columns", () =
|
||||
operationsReviewerName: "运营一",
|
||||
operationsReviewerUserId: 11,
|
||||
operationsStatus: "approved",
|
||||
sourceApplicationId: "991001",
|
||||
sourceSystem: "likei",
|
||||
status: "approved",
|
||||
userId: "10001",
|
||||
withdrawAddress: "TRX-address",
|
||||
@ -64,7 +62,6 @@ test("renders withdrawal application table with requested finance columns", () =
|
||||
});
|
||||
|
||||
expect(screen.getByRole("columnheader", { name: "APP" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("columnheader", { name: "来源单号" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("columnheader", { name: "用户ID" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("columnheader", { name: "提现金额" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("columnheader", { name: "提现方式" })).toBeInTheDocument();
|
||||
@ -76,8 +73,6 @@ test("renders withdrawal application table with requested finance columns", () =
|
||||
expect(screen.getByRole("columnheader", { name: "财务审核原因" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("columnheader", { name: "操作" })).toBeInTheDocument();
|
||||
expect(screen.getByText("lalu")).toBeInTheDocument();
|
||||
expect(screen.getByText("Likei")).toBeInTheDocument();
|
||||
expect(screen.getByText("991001")).toBeInTheDocument();
|
||||
expect(screen.getByText("10001")).toBeInTheDocument();
|
||||
expect(screen.getByText("$ 12.5")).toBeInTheDocument();
|
||||
expect(screen.getByText("USDT-TRC20")).toBeInTheDocument();
|
||||
@ -93,7 +88,7 @@ test("renders withdrawal application table with requested finance columns", () =
|
||||
test("keeps empty withdrawal table colspan aligned with visible columns", () => {
|
||||
const { container } = renderList(baseProps);
|
||||
|
||||
expect(container.querySelector("tbody td")?.colSpan).toBe(12);
|
||||
expect(container.querySelector("tbody td")?.colSpan).toBe(11);
|
||||
expect(screen.getByText("当前无数据")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
||||
@ -31,8 +31,6 @@ export function normalizeWithdrawalApplication(item = {}) {
|
||||
operationsReviewerUserId: item.operationsReviewerUserId ?? item.operations_reviewer_user_id,
|
||||
operationsStatus: stringValue(item.operationsStatus ?? item.operations_status),
|
||||
salaryAssetType: stringValue(item.salaryAssetType ?? item.salary_asset_type),
|
||||
sourceApplicationId: stringValue(item.sourceApplicationId ?? item.source_application_id),
|
||||
sourceSystem: stringValue(item.sourceSystem ?? item.source_system),
|
||||
status: stringValue(item.status),
|
||||
updatedAtMs: numberOrNull(item.updatedAtMs ?? item.updated_at_ms),
|
||||
userId: stringValue(item.userId ?? item.user_id),
|
||||
|
||||
@ -520,8 +520,8 @@ export function OpsCenterApp({ can = denyPermission }) {
|
||||
<UserProfilesView
|
||||
apps={data.apps}
|
||||
defaultAppCode={defaultAppCode}
|
||||
key={profileRefreshKey}
|
||||
poolIDsByApp={poolIDsByApp}
|
||||
refreshKey={profileRefreshKey}
|
||||
/>
|
||||
</Suspense>
|
||||
) : null}
|
||||
|
||||
@ -9,8 +9,6 @@ import {
|
||||
fetchLuckyGiftDraws,
|
||||
fetchLuckyGiftConfigVersions,
|
||||
fetchLuckyGiftExperiments,
|
||||
fetchLuckyGiftUserProfile,
|
||||
fetchLuckyGiftUserProfiles,
|
||||
fetchOpsDashboard,
|
||||
rollbackLuckyGiftConfig,
|
||||
saveLuckyGiftConfig,
|
||||
@ -27,8 +25,6 @@ vi.mock("./api.js", () => ({
|
||||
fetchLuckyGiftDraws: vi.fn(),
|
||||
fetchLuckyGiftConfigVersions: vi.fn(),
|
||||
fetchLuckyGiftExperiments: vi.fn(),
|
||||
fetchLuckyGiftUserProfile: vi.fn(),
|
||||
fetchLuckyGiftUserProfiles: vi.fn(),
|
||||
fetchOpsDashboard: vi.fn(),
|
||||
luckyGiftPoolKey: (item = {}) =>
|
||||
`${item.app_code || item.appCode}:${item.pool_id || item.poolId}:${item.strategy_version || item.strategyVersion || "fixed_v2"}`,
|
||||
@ -87,16 +83,6 @@ beforeEach(() => {
|
||||
pageSize: 20,
|
||||
total: 1,
|
||||
});
|
||||
fetchLuckyGiftUserProfile.mockResolvedValue({});
|
||||
fetchLuckyGiftUserProfiles.mockResolvedValue({
|
||||
hasMore: false,
|
||||
items: [],
|
||||
nextCursor: "",
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
snapshotAtMs: 1767000000000,
|
||||
total: 0,
|
||||
});
|
||||
fetchOpsDashboard.mockResolvedValue({
|
||||
experiments: [mockRunningExperiment()],
|
||||
giftHostDiamondRatiosByApp: {
|
||||
@ -311,34 +297,6 @@ test("opens the requested config view from the legacy admin document redirect",
|
||||
expect(screen.getAllByText("lucky").length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test("refreshes user profiles with the active filters instead of resetting the view", async () => {
|
||||
renderApp();
|
||||
await screen.findByText("运营应用");
|
||||
fireEvent.click(screen.getByRole("button", { name: /用户画像/ }));
|
||||
|
||||
await waitFor(() => expect(fetchLuckyGiftUserProfiles).toHaveBeenCalledTimes(1));
|
||||
fireEvent.mouseDown(screen.getByLabelText("用户来源"));
|
||||
fireEvent.click(await screen.findByRole("option", { name: "内部用户" }));
|
||||
const keywordInput = screen.getByPlaceholderText("短号 / 长号 / 靓号");
|
||||
fireEvent.change(keywordInput, { target: { value: "163001" } });
|
||||
fireEvent.click(screen.getByRole("button", { name: "查询" }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(fetchLuckyGiftUserProfiles).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({ identityType: "internal", keyword: "163001" }),
|
||||
),
|
||||
);
|
||||
const requestCountBeforeRefresh = fetchLuckyGiftUserProfiles.mock.calls.length;
|
||||
fireEvent.click(screen.getByRole("button", { name: "刷新" }));
|
||||
|
||||
await waitFor(() => expect(fetchLuckyGiftUserProfiles).toHaveBeenCalledTimes(requestCountBeforeRefresh + 1));
|
||||
expect(fetchLuckyGiftUserProfiles).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({ identityType: "internal", keyword: "163001", page: 1 }),
|
||||
);
|
||||
expect(screen.getByLabelText("用户来源")).toHaveTextContent("内部用户");
|
||||
expect(keywordInput).toHaveValue("163001");
|
||||
});
|
||||
|
||||
test("lists every pool config including published non-default pools", async () => {
|
||||
renderApp();
|
||||
await screen.findByText("运营应用");
|
||||
|
||||
@ -44,7 +44,7 @@ const SORT_DIRECTION_OPTIONS = [
|
||||
["asc", "从低到高 / 从旧到新"],
|
||||
];
|
||||
|
||||
export function UserProfilesView({ apps, defaultAppCode, poolIDsByApp, refreshKey = 0 }) {
|
||||
export function UserProfilesView({ apps, defaultAppCode, poolIDsByApp }) {
|
||||
const [appCode, setAppCode] = useState(defaultAppCode);
|
||||
const [poolId, setPoolId] = useState("");
|
||||
const [identityType, setIdentityType] = useState("all");
|
||||
@ -58,7 +58,6 @@ export function UserProfilesView({ apps, defaultAppCode, poolIDsByApp, refreshKe
|
||||
const [reloadKey, setReloadKey] = useState(0);
|
||||
const [expandedKey, setExpandedKey] = useState("");
|
||||
const [details, setDetails] = useState({});
|
||||
const handledRefreshKey = useRef(refreshKey);
|
||||
// 页码只负责兼容通用 DataTable 的滚动触发;真正翻页使用 owner 返回的不可解释 cursor,
|
||||
// 因此并发开奖插入新画像时不会因为 OFFSET 位移而把同一用户追加两次。
|
||||
const cursorsByPage = useRef({ 1: "" });
|
||||
@ -168,16 +167,6 @@ export function UserProfilesView({ apps, defaultAppCode, poolIDsByApp, refreshKe
|
||||
setReloadKey((current) => current + 1);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (handledRefreshKey.current === refreshKey) {
|
||||
return;
|
||||
}
|
||||
handledRefreshKey.current = refreshKey;
|
||||
// 顶部刷新只重建游标和当前结果,筛选与已提交关键词继续由本组件状态持有,
|
||||
// 避免通过 React key 重挂载整页后把运营正在排查的条件恢复成默认值。
|
||||
reloadProfiles();
|
||||
}, [refreshKey, reloadProfiles]);
|
||||
|
||||
const changeAppCode = useCallback(
|
||||
(nextAppCode) => {
|
||||
setAppCode(nextAppCode);
|
||||
|
||||
@ -1036,21 +1036,11 @@ function ResourceFormDialog({ disabled, form, loading, mode, onClose, onSubmit,
|
||||
const handleAnimationSelected = async (file) => {
|
||||
let detectedMp4Layout = null;
|
||||
if (isMp4File(file)) {
|
||||
try {
|
||||
detectedMp4Layout = await detectMp4AlphaLayout(file);
|
||||
} catch {
|
||||
// 布局解析只负责补充客户端渲染元数据,失败不能把 MP4 变成不可上传格式。
|
||||
detectedMp4Layout = null;
|
||||
}
|
||||
detectedMp4Layout = await detectMp4AlphaLayout(file);
|
||||
}
|
||||
let detectedProfileCardLayout = null;
|
||||
if (form.resourceType === "profile_card") {
|
||||
try {
|
||||
detectedProfileCardLayout = await detectProfileCardLayout(file);
|
||||
} catch {
|
||||
// 部分 PAG 版本无法被浏览器侧 libpag 解码,但原文件仍可供客户端消费,继续走通用上传。
|
||||
detectedProfileCardLayout = null;
|
||||
}
|
||||
detectedProfileCardLayout = await detectProfileCardLayout(file);
|
||||
}
|
||||
// 只返回当次素材的检测增量,上传成功后再合并到最新表单,避免异步旧快照覆盖用户新修改。
|
||||
return {
|
||||
|
||||
@ -54,7 +54,6 @@ export const API_OPERATIONS = {
|
||||
createEmojiPack: "createEmojiPack",
|
||||
createExploreTab: "createExploreTab",
|
||||
createExternalAdminUser: "createExternalAdminUser",
|
||||
createExternalWithdrawalApplication: "createExternalWithdrawalApplication",
|
||||
createFinanceCoinSellerRechargeOrder: "createFinanceCoinSellerRechargeOrder",
|
||||
createFullServerNoticeFanout: "createFullServerNoticeFanout",
|
||||
createGift: "createGift",
|
||||
@ -713,11 +712,6 @@ export const API_ENDPOINTS: Record<ApiOperationId, ApiEndpoint> = {
|
||||
permission: "external-admin-user:create",
|
||||
permissions: ["external-admin-user:create","external-admin-user:permissions"]
|
||||
},
|
||||
createExternalWithdrawalApplication: {
|
||||
method: "POST",
|
||||
operationId: API_OPERATIONS.createExternalWithdrawalApplication,
|
||||
path: "/v1/integrations/withdrawal-applications"
|
||||
},
|
||||
createFinanceCoinSellerRechargeOrder: {
|
||||
method: "POST",
|
||||
operationId: API_OPERATIONS.createFinanceCoinSellerRechargeOrder,
|
||||
|
||||
50
src/shared/api/generated/schema.d.ts
vendored
50
src/shared/api/generated/schema.d.ts
vendored
@ -2636,23 +2636,6 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/integrations/withdrawal-applications": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** @description 来源钱包使用 App 独立 Bearer token 幂等提交提现申请,不使用后台用户 JWT。 */
|
||||
post: operations["createExternalWithdrawalApplication"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/admin/finance/withdrawal-applications": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@ -6669,10 +6652,6 @@ export interface components {
|
||||
id: number;
|
||||
appCode: string;
|
||||
appName?: string;
|
||||
/** @description 资金执行来源;hyapp_wallet 为平台钱包,likei 为 Aslan legacy 钱包 */
|
||||
sourceSystem?: string;
|
||||
/** @description 来源钱包的提现申请号,用于跨系统审计和幂等回调 */
|
||||
sourceApplicationId?: string;
|
||||
userId: string;
|
||||
salaryAssetType?: string;
|
||||
withdrawAmount: string;
|
||||
@ -6700,19 +6679,6 @@ export interface components {
|
||||
createdAtMs: number;
|
||||
updatedAtMs?: number;
|
||||
};
|
||||
ExternalWithdrawalApplicationInput: {
|
||||
appCode: string;
|
||||
sourceSystem: string;
|
||||
sourceApplicationId: string;
|
||||
userId: string;
|
||||
salaryAssetType: string;
|
||||
withdrawAmount: string;
|
||||
withdrawAmountMinor: number;
|
||||
withdrawMethod: string;
|
||||
withdrawAddress: string;
|
||||
/** Format: int64 */
|
||||
createdAtMs?: number;
|
||||
};
|
||||
FinanceCoinSellerRechargeOrderInput: {
|
||||
appCode: string;
|
||||
targetUserId: string;
|
||||
@ -11516,22 +11482,6 @@ export interface operations {
|
||||
200: components["responses"]["FinanceScopeCatalogResponse"];
|
||||
};
|
||||
};
|
||||
createExternalWithdrawalApplication: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["ExternalWithdrawalApplicationInput"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
201: components["responses"]["FinanceWithdrawalApplicationResponse"];
|
||||
};
|
||||
};
|
||||
listFinanceWithdrawalApplications: {
|
||||
parameters: {
|
||||
query?: {
|
||||
|
||||
@ -19,6 +19,7 @@ import { Mp4Preview } from "@/shared/ui/Mp4Preview.jsx";
|
||||
import { useToast } from "@/shared/ui/ToastProvider.jsx";
|
||||
import styles from "./UploadField.module.css";
|
||||
|
||||
const imageAccept = "image/jpeg,image/png,image/webp,.jpg,.jpeg,.png,.webp,.svga,.pag";
|
||||
const pagWasmURL = "/vendor/libpag.wasm";
|
||||
let pagModulePromise;
|
||||
|
||||
@ -50,10 +51,12 @@ export function UploadField({
|
||||
const [downloading, setDownloading] = useState(false);
|
||||
const [videoPreviewOpen, setVideoPreviewOpen] = useState(false);
|
||||
const isImage = kind === "image";
|
||||
const uploadMode = uploadKind || kind;
|
||||
const useImageUpload = uploadMode === "image";
|
||||
const source = localPreview?.url || value;
|
||||
const assetKind = localPreview?.assetKind || getAssetKind(value, kind);
|
||||
const displayName = localPreview?.name || getDisplayValue(value);
|
||||
const inputAccept = accept || undefined;
|
||||
const inputAccept = accept !== undefined ? accept : useImageUpload && isImage ? imageAccept : undefined;
|
||||
const canPreviewVideo = Boolean(source && assetKind === "mp4");
|
||||
const localPreviewURL = localPreview?.url;
|
||||
const previewMp4Layout = Object.prototype.hasOwnProperty.call(localPreview?.selectionResult || {}, "mp4Layout")
|
||||
@ -112,9 +115,7 @@ export function UploadField({
|
||||
return;
|
||||
}
|
||||
setLocalPreview((previous) => (previous ? { ...previous, selectionResult } : previous));
|
||||
// 图片接口会校验固定的位图格式;资源素材不能被这层校验变成格式白名单,
|
||||
// 因此仅 JPEG/PNG/WebP 走图片接口,其余 PAG/SVGA/GIF/AVIF 或未知格式走通用文件接口。
|
||||
const result = shouldUploadAsImage(file, kind, uploadKind) ? await uploadImage(file) : await uploadFile(file);
|
||||
const result = useImageUpload ? await uploadImage(file) : await uploadFile(file);
|
||||
if (uploadGeneration !== uploadGenerationRef.current) {
|
||||
return;
|
||||
}
|
||||
@ -549,20 +550,6 @@ function getAssetKind(value, kind, contentType = "") {
|
||||
return kind === "image" ? "image" : "file";
|
||||
}
|
||||
|
||||
function shouldUploadAsImage(file, kind, uploadKind) {
|
||||
if (uploadKind !== undefined) {
|
||||
return uploadKind === "image";
|
||||
}
|
||||
if (kind !== "image") {
|
||||
return false;
|
||||
}
|
||||
const extension = getExtension(file?.name);
|
||||
if (extension) {
|
||||
return ["jpg", "jpeg", "png", "webp"].includes(extension);
|
||||
}
|
||||
return ["image/jpeg", "image/png", "image/webp"].includes(String(file?.type || "").toLowerCase());
|
||||
}
|
||||
|
||||
function isRasterImageExtension(extension) {
|
||||
return ["avif", "gif", "jpg", "jpeg", "png", "webp"].includes(extension);
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { beforeEach, expect, test, vi } from "vitest";
|
||||
import { expect, test, vi } from "vitest";
|
||||
import { downloadResponse } from "@/shared/api/download";
|
||||
import { uploadFile, uploadImage } from "@/shared/api/upload";
|
||||
import { ToastProvider } from "@/shared/ui/ToastProvider.jsx";
|
||||
@ -14,10 +14,6 @@ vi.mock("@/shared/api/download", () => ({
|
||||
downloadResponse: vi.fn(async () => undefined),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test("file upload field does not restrict picker type and uses generic upload endpoint", async () => {
|
||||
const onChange = vi.fn();
|
||||
const onFileSelected = vi.fn();
|
||||
@ -42,40 +38,6 @@ test("file upload field does not restrict picker type and uses generic upload en
|
||||
expect(onChange).toHaveBeenCalledWith("https://media.haiyihy.com/admin/files/effect.bin");
|
||||
});
|
||||
|
||||
test("image-style resource field accepts PAG and routes it through generic upload", async () => {
|
||||
const onChange = vi.fn();
|
||||
const { container } = render(
|
||||
<ToastProvider>
|
||||
<UploadField kind="image" label="动效素材" value="" onChange={onChange} />
|
||||
</ToastProvider>,
|
||||
);
|
||||
const input = container.querySelector('input[type="file"]');
|
||||
const file = new File(["pag"], "effect.pag", { type: "application/octet-stream" });
|
||||
|
||||
expect(input).not.toHaveAttribute("accept");
|
||||
fireEvent.change(input, { target: { files: [file] } });
|
||||
|
||||
await waitFor(() => expect(uploadFile).toHaveBeenCalledWith(file));
|
||||
expect(uploadImage).not.toHaveBeenCalled();
|
||||
expect(onChange).toHaveBeenCalledWith("https://media.haiyihy.com/admin/files/effect.bin");
|
||||
});
|
||||
|
||||
test("standard raster image keeps the strict image upload endpoint", async () => {
|
||||
const onChange = vi.fn();
|
||||
const { container } = render(
|
||||
<ToastProvider>
|
||||
<UploadField kind="image" label="封面" value="" onChange={onChange} />
|
||||
</ToastProvider>,
|
||||
);
|
||||
const file = new File(["png"], "cover.png", { type: "image/png" });
|
||||
|
||||
fireEvent.change(container.querySelector('input[type="file"]'), { target: { files: [file] } });
|
||||
|
||||
await waitFor(() => expect(uploadImage).toHaveBeenCalledWith(file));
|
||||
expect(uploadFile).not.toHaveBeenCalled();
|
||||
expect(onChange).toHaveBeenCalledWith("https://media.haiyihy.com/admin/images/effect.png");
|
||||
});
|
||||
|
||||
test("mp4 file upload field can open video preview dialog", async () => {
|
||||
const source = "https://media.haiyihy.com/admin/files/gift.mp4?sign=1";
|
||||
const onChange = vi.fn();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user