Skip to content

Commit 22f09f2

Browse files
committed
Merge branch 'release/v1.4' into pr/1329
2 parents 56c3dfe + 03cc6e3 commit 22f09f2

14 files changed

Lines changed: 605 additions & 1058 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ yarn.lock
3636
.claude
3737

3838
CLAUDE.md
39+
.omc
3940

4041
test-results
4142
playwright-report

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@
6161
"@eslint/compat": "^1.4.1",
6262
"@eslint/js": "9.39.2",
6363
"@playwright/test": "^1.58.2",
64-
"@rspack/cli": "^1.7.6",
65-
"@rspack/core": "^1.6.8",
64+
"@rspack/cli": "^1.7.11",
65+
"@rspack/core": "^1.7.11",
6666
"@swc/helpers": "^0.5.17",
6767
"@testing-library/jest-dom": "^6.9.1",
6868
"@testing-library/react": "^16.3.0",
@@ -103,7 +103,7 @@
103103
"unocss": "66.5.4",
104104
"vitest": "^4.0.18"
105105
},
106-
"packageManager": "pnpm@10.12.4",
106+
"packageManager": "pnpm@10.33.0",
107107
"sideEffects": [
108108
"**/*.css",
109109
"**/*.scss",

pnpm-lock.yaml

Lines changed: 424 additions & 1022 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pnpm-workspace.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
minimumReleaseAge: 10080
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { initTestEnv } from "@Tests/utils";
2+
import { ResourceService } from "./resource";
3+
import { vi, describe, it, expect, beforeEach } from "vitest";
4+
import type { Group } from "@Packages/message/server";
5+
import type { IMessageQueue } from "@Packages/message/message_queue";
6+
7+
initTestEnv();
8+
9+
// mock fetch
10+
const mockFetch = vi.fn();
11+
vi.stubGlobal("fetch", mockFetch);
12+
13+
// 创建文本 blob 和二进制 blob 的辅助函数
14+
function textBlob(content: string, contentType = "text/plain") {
15+
return new Blob([content], { type: contentType });
16+
}
17+
18+
function binaryBlob(bytes: number[]) {
19+
return new Blob([new Uint8Array(bytes)], { type: "application/octet-stream" });
20+
}
21+
22+
function mockResponse(blob: Blob, status = 200, contentType?: string) {
23+
return {
24+
status,
25+
blob: () => Promise.resolve(blob),
26+
headers: new Headers(contentType ? { "content-type": contentType } : {}),
27+
} as unknown as Response;
28+
}
29+
30+
describe("ResourceService - loadByUrl", () => {
31+
let service: ResourceService;
32+
33+
beforeEach(() => {
34+
vi.clearAllMocks();
35+
const mockGroup = {} as Group;
36+
const mockMQ = {} as IMessageQueue;
37+
service = new ResourceService(mockGroup, mockMQ);
38+
// calculateHash 不影响核心逻辑,直接 mock
39+
vi.spyOn(service, "calculateHash").mockResolvedValue({
40+
md5: "mock-md5",
41+
sha1: "",
42+
sha256: "",
43+
sha384: "",
44+
sha512: "",
45+
});
46+
});
47+
48+
it("加载文本资源(require)时应设置 content", async () => {
49+
const jsCode = "console.log('hello');";
50+
mockFetch.mockResolvedValue(mockResponse(textBlob(jsCode), 200, "application/javascript; charset=utf-8"));
51+
52+
const res = await service.loadByUrl("https://example.com/lib.js", "require");
53+
54+
expect(res.url).toBe("https://example.com/lib.js");
55+
expect(res.content).toBeTruthy();
56+
expect(res.contentType).toBe("application/javascript");
57+
expect(res.base64).toBeTruthy();
58+
expect(res.type).toBe("require");
59+
});
60+
61+
it("加载文本资源(resource)时应通过 blob.text() 设置 content", async () => {
62+
const text = "plain text content";
63+
mockFetch.mockResolvedValue(mockResponse(textBlob(text), 200, "text/plain"));
64+
65+
const res = await service.loadByUrl("https://example.com/data.txt", "resource");
66+
67+
expect(res.content).toBe(text);
68+
expect(res.type).toBe("resource");
69+
});
70+
71+
it("加载二进制资源时 content 应为空", async () => {
72+
// 包含 null 字节的二进制数据,isText 会返回 false
73+
const bytes = [0x89, 0x50, 0x4e, 0x47, 0x00, 0x00, 0x00, 0x00];
74+
mockFetch.mockResolvedValue(mockResponse(binaryBlob(bytes), 200, "image/png"));
75+
76+
const res = await service.loadByUrl("https://example.com/img.png", "resource");
77+
78+
expect(res.content).toBe("");
79+
expect(res.base64).toBeTruthy();
80+
expect(res.contentType).toBe("image/png");
81+
});
82+
83+
it("响应非200时应抛出异常", async () => {
84+
mockFetch.mockResolvedValue(mockResponse(textBlob(""), 404));
85+
86+
await expect(service.loadByUrl("https://example.com/404", "require")).rejects.toThrow(
87+
"resource response status not 200: 404"
88+
);
89+
});
90+
91+
it("没有 content-type 时应默认为 application/octet-stream", async () => {
92+
mockFetch.mockResolvedValue(mockResponse(textBlob("data"), 200));
93+
94+
const res = await service.loadByUrl("https://example.com/noct", "resource");
95+
96+
expect(res.contentType).toBe("application/octet-stream");
97+
});
98+
});

src/app/service/service_worker/resource.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ export class ResourceService {
264264
throw new Error(`resource response status not 200: ${resp.status}`);
265265
}
266266
const data = await resp.blob();
267-
const [hash, arrayBuffer, base64] = await Promise.all([
267+
const [hash, uint8Array, base64] = await Promise.all([
268268
this.calculateHash(data),
269269
blobToUint8Array(data),
270270
blobToBase64(data),
@@ -280,7 +280,6 @@ export class ResourceService {
280280
type,
281281
createtime: Date.now(),
282282
};
283-
const uint8Array = new Uint8Array(arrayBuffer);
284283
if (isText(uint8Array)) {
285284
if (type === "require" || type === "require-css") {
286285
resource.content = await readBlobContent(data, contentType); // @require和@require-css 是会转换成代码运行的,可以进行解码

src/pages/components/CodeEditor/index.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ const CodeEditor = React.forwardRef<{ editor: editor.IStandaloneCodeEditor | und
149149
colorDecorators: true,
150150
} as const;
151151

152+
let originalModel: editor.ITextModel | undefined;
153+
let modifiedModel: editor.ITextModel | undefined;
152154
if (diffCode) {
153155
edit = editor.createDiffEditor(container, {
154156
hideUnchangedRegions: { enabled: true },
@@ -158,9 +160,12 @@ const CodeEditor = React.forwardRef<{ editor: editor.IStandaloneCodeEditor | und
158160
diffWordWrap: "off",
159161
...commonEditorOptions,
160162
});
163+
// standalone model 不会随 editor.dispose 自动清理,需手动跟踪并在 cleanup 释放
164+
originalModel = editor.createModel(diffCode, "javascript");
165+
modifiedModel = editor.createModel(code, "javascript");
161166
edit.setModel({
162-
original: editor.createModel(diffCode, "javascript"),
163-
modified: editor.createModel(code, "javascript"),
167+
original: originalModel,
168+
modified: modifiedModel,
164169
});
165170
} else {
166171
edit = editor.create(container, {
@@ -177,6 +182,8 @@ const CodeEditor = React.forwardRef<{ editor: editor.IStandaloneCodeEditor | und
177182
// 目前会出现:Uncaught (in promise) Canceled: Canceled
178183
// 问题追踪:https://github.com/microsoft/monaco-editor/issues/4702
179184
edit?.dispose();
185+
originalModel?.dispose();
186+
modifiedModel?.dispose();
180187
};
181188
}, [id, code, diffCode, editable]);
182189

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.modal-config .arco-tabs {
2+
position: static;
3+
}
4+
5+
.modal-config .arco-tabs-pane {
6+
max-height: calc(100vh - 400px);
7+
overflow: auto;
8+
}
9+
10+
.modal-config .arco-form-item:last-child {
11+
margin-bottom: 0;
12+
}

src/pages/components/UserConfigPanel/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { ValueClient } from "@App/app/service/service_worker/client";
2020
import { message } from "@App/pages/store/global";
2121
import type { TKeyValuePair } from "@App/pkg/utils/message_value";
2222
import { encodeRValue } from "@App/pkg/utils/message_value";
23+
import "./index.css";
2324

2425
const FormItem = Form.Item;
2526

@@ -52,6 +53,7 @@ const UserConfigPanel: React.FC<{
5253
return (
5354
<Modal
5455
visible={visible}
56+
className={"modal-config"}
5557
title={`${script.name} ${t("config")}`} // 替换为键值对应的英文文本
5658
okText={<Popover content={t("save_only_current_group")}>{t("save")}</Popover>}
5759
cancelText={t("close")} // 替换为键值对应的英文文本

src/pages/components/layout/MainLayout.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ const importByUrls = async (urls: string[]): Promise<TImportStat | undefined> =>
122122
const getSafePopupParent = (p: Element) => {
123123
p = (p.closest("button")?.parentNode as Element) || p; // 確保 ancestor 沒有 button 元素
124124
p = (p.closest("span")?.parentNode as Element) || p; // 確保 ancestor 沒有 span 元素
125+
p = (p.closest(".arco-form-item-control-children")?.parentNode as Element) || p; // 確保 ancestor 沒有 .form-item-control-children 元素
125126
p = (p.closest(".arco-collapse-item-content")?.parentNode as Element) || p; // 確保 ancestor 沒有 .arco-collapse-item-content 元素
126127
p = (p.closest(".arco-card")?.parentNode as Element) || p; // 確保 ancestor 沒有 .arco-card 元素
127128
p = (p.closest("aside")?.parentNode as Element) || p; // 確保 ancestor 沒有 aside 元素

0 commit comments

Comments
 (0)