@simplysm/core-common 13.0.69 → 13.0.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -267
- package/dist/common.types.d.ts +14 -14
- package/dist/errors/argument-error.d.ts +10 -10
- package/dist/errors/argument-error.d.ts.map +1 -1
- package/dist/errors/argument-error.js +2 -2
- package/dist/errors/argument-error.js.map +1 -1
- package/dist/errors/not-implemented-error.d.ts +8 -8
- package/dist/errors/not-implemented-error.js +2 -2
- package/dist/errors/not-implemented-error.js.map +1 -1
- package/dist/errors/sd-error.d.ts +10 -10
- package/dist/errors/sd-error.d.ts.map +1 -1
- package/dist/errors/timeout-error.d.ts +10 -10
- package/dist/errors/timeout-error.js +3 -3
- package/dist/errors/timeout-error.js.map +1 -1
- package/dist/extensions/arr-ext.d.ts +2 -2
- package/dist/extensions/arr-ext.helpers.d.ts +8 -8
- package/dist/extensions/arr-ext.helpers.js +1 -1
- package/dist/extensions/arr-ext.helpers.js.map +1 -1
- package/dist/extensions/arr-ext.js +13 -13
- package/dist/extensions/arr-ext.js.map +1 -1
- package/dist/extensions/arr-ext.types.d.ts +57 -57
- package/dist/extensions/arr-ext.types.d.ts.map +1 -1
- package/dist/extensions/map-ext.d.ts +16 -16
- package/dist/extensions/set-ext.d.ts +11 -11
- package/dist/features/debounce-queue.d.ts +17 -15
- package/dist/features/debounce-queue.d.ts.map +1 -1
- package/dist/features/debounce-queue.js +6 -6
- package/dist/features/debounce-queue.js.map +1 -1
- package/dist/features/event-emitter.d.ts +20 -20
- package/dist/features/event-emitter.js +17 -17
- package/dist/features/serial-queue.d.ts +11 -11
- package/dist/features/serial-queue.js +5 -5
- package/dist/features/serial-queue.js.map +1 -1
- package/dist/globals.d.ts +4 -4
- package/dist/types/date-only.d.ts +64 -64
- package/dist/types/date-only.d.ts.map +1 -1
- package/dist/types/date-only.js +63 -63
- package/dist/types/date-time.d.ts +37 -37
- package/dist/types/date-time.d.ts.map +1 -1
- package/dist/types/date-time.js +54 -37
- package/dist/types/date-time.js.map +1 -1
- package/dist/types/lazy-gc-map.d.ts +26 -26
- package/dist/types/lazy-gc-map.d.ts.map +1 -1
- package/dist/types/lazy-gc-map.js +26 -26
- package/dist/types/lazy-gc-map.js.map +1 -1
- package/dist/types/time.d.ts +25 -25
- package/dist/types/time.d.ts.map +1 -1
- package/dist/types/time.js +25 -25
- package/dist/types/time.js.map +1 -1
- package/dist/types/uuid.d.ts +11 -11
- package/dist/types/uuid.d.ts.map +1 -1
- package/dist/types/uuid.js +12 -12
- package/dist/types/uuid.js.map +1 -1
- package/dist/utils/bytes.d.ts +17 -17
- package/dist/utils/bytes.js +4 -4
- package/dist/utils/bytes.js.map +1 -1
- package/dist/utils/date-format.d.ts +45 -45
- package/dist/utils/date-format.js +1 -1
- package/dist/utils/date-format.js.map +1 -1
- package/dist/utils/error.d.ts +4 -4
- package/dist/utils/json.d.ts +17 -17
- package/dist/utils/json.js +3 -3
- package/dist/utils/json.js.map +1 -1
- package/dist/utils/num.d.ts +23 -23
- package/dist/utils/obj.d.ts +111 -111
- package/dist/utils/obj.d.ts.map +1 -1
- package/dist/utils/obj.js +3 -3
- package/dist/utils/obj.js.map +1 -1
- package/dist/utils/path.d.ts +10 -10
- package/dist/utils/primitive.d.ts +5 -5
- package/dist/utils/primitive.js +1 -1
- package/dist/utils/primitive.js.map +1 -1
- package/dist/utils/str.d.ts +46 -46
- package/dist/utils/str.d.ts.map +1 -1
- package/dist/utils/str.js +5 -5
- package/dist/utils/str.js.map +1 -1
- package/dist/utils/template-strings.d.ts +26 -26
- package/dist/utils/transferable.d.ts +18 -18
- package/dist/utils/transferable.js +1 -1
- package/dist/utils/transferable.js.map +1 -1
- package/dist/utils/wait.d.ts +9 -9
- package/dist/utils/xml.d.ts +13 -13
- package/dist/utils/xml.d.ts.map +1 -1
- package/dist/utils/xml.js +1 -0
- package/dist/utils/xml.js.map +1 -1
- package/dist/zip/sd-zip.d.ts +22 -22
- package/dist/zip/sd-zip.js +16 -16
- package/package.json +4 -4
- package/src/common.types.ts +17 -17
- package/src/errors/argument-error.ts +15 -15
- package/src/errors/not-implemented-error.ts +9 -9
- package/src/errors/sd-error.ts +12 -12
- package/src/errors/timeout-error.ts +12 -12
- package/src/extensions/arr-ext.helpers.ts +10 -10
- package/src/extensions/arr-ext.ts +57 -57
- package/src/extensions/arr-ext.types.ts +59 -59
- package/src/extensions/map-ext.ts +16 -16
- package/src/extensions/set-ext.ts +11 -11
- package/src/features/debounce-queue.ts +21 -19
- package/src/features/event-emitter.ts +25 -25
- package/src/features/serial-queue.ts +13 -13
- package/src/globals.ts +4 -4
- package/src/index.ts +1 -1
- package/src/types/date-only.ts +83 -83
- package/src/types/date-time.ts +64 -44
- package/src/types/lazy-gc-map.ts +45 -45
- package/src/types/time.ts +34 -34
- package/src/types/uuid.ts +17 -17
- package/src/utils/bytes.ts +35 -35
- package/src/utils/date-format.ts +65 -65
- package/src/utils/error.ts +4 -4
- package/src/utils/json.ts +39 -39
- package/src/utils/num.ts +23 -23
- package/src/utils/obj.ts +138 -138
- package/src/utils/path.ts +10 -10
- package/src/utils/primitive.ts +6 -6
- package/src/utils/str.ts +260 -261
- package/src/utils/template-strings.ts +29 -29
- package/src/utils/transferable.ts +284 -284
- package/src/utils/wait.ts +10 -10
- package/src/utils/xml.ts +20 -19
- package/src/zip/sd-zip.ts +25 -25
- package/tests/errors/errors.spec.ts +80 -0
- package/tests/extensions/array-extension.spec.ts +796 -0
- package/tests/extensions/map-extension.spec.ts +147 -0
- package/tests/extensions/set-extension.spec.ts +74 -0
- package/tests/types/date-only.spec.ts +638 -0
- package/tests/types/date-time.spec.ts +391 -0
- package/tests/types/lazy-gc-map.spec.ts +692 -0
- package/tests/types/time.spec.ts +559 -0
- package/tests/types/uuid.spec.ts +74 -0
- package/tests/utils/bytes-utils.spec.ts +230 -0
- package/tests/utils/date-format.spec.ts +373 -0
- package/tests/utils/debounce-queue.spec.ts +272 -0
- package/tests/utils/json.spec.ts +486 -0
- package/tests/utils/number.spec.ts +157 -0
- package/tests/utils/object.spec.ts +829 -0
- package/tests/utils/path.spec.ts +78 -0
- package/tests/utils/primitive.spec.ts +43 -0
- package/tests/utils/sd-event-emitter.spec.ts +216 -0
- package/tests/utils/serial-queue.spec.ts +365 -0
- package/tests/utils/string.spec.ts +281 -0
- package/tests/utils/template-strings.spec.ts +57 -0
- package/tests/utils/transferable.spec.ts +703 -0
- package/tests/utils/wait.spec.ts +145 -0
- package/tests/utils/xml.spec.ts +146 -0
- package/tests/zip/sd-zip.spec.ts +238 -0
- package/docs/extensions.md +0 -503
- package/docs/features.md +0 -109
- package/docs/types.md +0 -486
- package/docs/utils.md +0 -780
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { waitTime as time, waitUntil as until, TimeoutError } from "@simplysm/core-common";
|
|
3
|
+
|
|
4
|
+
describe("Wait", () => {
|
|
5
|
+
//#region time
|
|
6
|
+
|
|
7
|
+
describe("time()", () => {
|
|
8
|
+
it("Waits for specified time", async () => {
|
|
9
|
+
const start = Date.now();
|
|
10
|
+
await time(100);
|
|
11
|
+
const elapsed = Date.now() - start;
|
|
12
|
+
|
|
13
|
+
// 100ms ± tolerance - CI environment load and timer precision considered
|
|
14
|
+
expect(elapsed).toBeGreaterThanOrEqual(95);
|
|
15
|
+
expect(elapsed).toBeLessThan(250);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("Works normally with 0ms wait", async () => {
|
|
19
|
+
const start = Date.now();
|
|
20
|
+
await time(0);
|
|
21
|
+
const elapsed = Date.now() - start;
|
|
22
|
+
|
|
23
|
+
expect(elapsed).toBeLessThan(50);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
|
|
29
|
+
//#region until
|
|
30
|
+
|
|
31
|
+
describe("until()", () => {
|
|
32
|
+
it("Waits until condition becomes true", async () => {
|
|
33
|
+
let count = 0;
|
|
34
|
+
|
|
35
|
+
await until(() => {
|
|
36
|
+
count++;
|
|
37
|
+
return count >= 3;
|
|
38
|
+
}, 10);
|
|
39
|
+
|
|
40
|
+
expect(count).toBe(3);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("Supports async condition function", async () => {
|
|
44
|
+
let count = 0;
|
|
45
|
+
|
|
46
|
+
await until(async () => {
|
|
47
|
+
await time(10);
|
|
48
|
+
count++;
|
|
49
|
+
return count >= 3;
|
|
50
|
+
}, 10);
|
|
51
|
+
|
|
52
|
+
expect(count).toBe(3);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("Returns immediately if condition already true", async () => {
|
|
56
|
+
const start = Date.now();
|
|
57
|
+
await until(() => true, 100);
|
|
58
|
+
const elapsed = Date.now() - start;
|
|
59
|
+
|
|
60
|
+
expect(elapsed).toBeLessThan(50);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("Throws TimeoutError on max attempts exceeded", async () => {
|
|
64
|
+
let count = 0;
|
|
65
|
+
|
|
66
|
+
await expect(async () => {
|
|
67
|
+
await until(
|
|
68
|
+
() => {
|
|
69
|
+
count++;
|
|
70
|
+
return false;
|
|
71
|
+
},
|
|
72
|
+
10,
|
|
73
|
+
5,
|
|
74
|
+
);
|
|
75
|
+
}).rejects.toThrow(TimeoutError);
|
|
76
|
+
|
|
77
|
+
expect(count).toBe(5);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("Waits indefinitely if maxCount undefined", async () => {
|
|
81
|
+
let count = 0;
|
|
82
|
+
|
|
83
|
+
// Unlimited wait but returns when condition true
|
|
84
|
+
await until(
|
|
85
|
+
() => {
|
|
86
|
+
count++;
|
|
87
|
+
return count >= 10;
|
|
88
|
+
},
|
|
89
|
+
10,
|
|
90
|
+
undefined,
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
expect(count).toBe(10);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("Default milliseconds is 100ms", async () => {
|
|
97
|
+
let count = 0;
|
|
98
|
+
const start = Date.now();
|
|
99
|
+
|
|
100
|
+
await until(() => {
|
|
101
|
+
count++;
|
|
102
|
+
return count >= 3;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const elapsed = Date.now() - start;
|
|
106
|
+
// 100ms * 2 waits = 200ms (first check immediate), timer tolerance considered
|
|
107
|
+
expect(elapsed).toBeGreaterThanOrEqual(190);
|
|
108
|
+
expect(elapsed).toBeLessThan(350);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("With maxCount=1, tries once then errors", async () => {
|
|
112
|
+
let count = 0;
|
|
113
|
+
|
|
114
|
+
await expect(async () => {
|
|
115
|
+
await until(
|
|
116
|
+
() => {
|
|
117
|
+
count++;
|
|
118
|
+
return false;
|
|
119
|
+
},
|
|
120
|
+
10,
|
|
121
|
+
1,
|
|
122
|
+
);
|
|
123
|
+
}).rejects.toThrow(TimeoutError);
|
|
124
|
+
|
|
125
|
+
expect(count).toBe(1);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("Success if condition true within maxCount", async () => {
|
|
129
|
+
let count = 0;
|
|
130
|
+
|
|
131
|
+
await until(
|
|
132
|
+
() => {
|
|
133
|
+
count++;
|
|
134
|
+
return count >= 3;
|
|
135
|
+
},
|
|
136
|
+
10,
|
|
137
|
+
5,
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
expect(count).toBe(3);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
//#endregion
|
|
145
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { xmlParse as parse, xmlStringify as stringify } from "@simplysm/core-common";
|
|
3
|
+
|
|
4
|
+
describe("XmlConvert", () => {
|
|
5
|
+
//#region parse
|
|
6
|
+
|
|
7
|
+
describe("parse()", () => {
|
|
8
|
+
it("Parses basic XML", () => {
|
|
9
|
+
const xml = "<root><child>value</child></root>";
|
|
10
|
+
const result = parse(xml) as Record<string, unknown>;
|
|
11
|
+
|
|
12
|
+
expect(result).toHaveProperty("root");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("Parses XML with attributes", () => {
|
|
16
|
+
const xml = '<root id="1"><child name="test">value</child></root>';
|
|
17
|
+
const result = parse(xml) as {
|
|
18
|
+
root: {
|
|
19
|
+
$: { id: string };
|
|
20
|
+
child: Array<{ $: { name: string }; _: string }>;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
expect(result.root.$.id).toBe("1");
|
|
25
|
+
expect(result.root.child[0].$.name).toBe("test");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("Parses nested XML", () => {
|
|
29
|
+
const xml = "<root><parent><child>value</child></parent></root>";
|
|
30
|
+
const result = parse(xml) as {
|
|
31
|
+
root: { parent: Array<{ child: string[] }> };
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
expect(result.root.parent[0].child[0]).toBe("value");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("Parses text nodes as _ key", () => {
|
|
38
|
+
const xml = '<item id="1">text content</item>';
|
|
39
|
+
const result = parse(xml) as {
|
|
40
|
+
item: { $: { id: string }; _: string };
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
expect(result.item._).toBe("text content");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("Removes namespace prefix (stripTagPrefix: true)", () => {
|
|
47
|
+
const xml = "<ns:root><ns:child>value</ns:child></ns:root>";
|
|
48
|
+
const result = parse(xml, { stripTagPrefix: true }) as {
|
|
49
|
+
root: { child: string[] };
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
expect(result).toHaveProperty("root");
|
|
53
|
+
expect(result.root).toHaveProperty("child");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("Preserves namespace prefix (default)", () => {
|
|
57
|
+
const xml = "<ns:root><ns:child>value</ns:child></ns:root>";
|
|
58
|
+
const result = parse(xml) as Record<string, unknown>;
|
|
59
|
+
|
|
60
|
+
expect(result).toHaveProperty("ns:root");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("Parses multiple same tags as array", () => {
|
|
64
|
+
const xml = "<root><item>1</item><item>2</item><item>3</item></root>";
|
|
65
|
+
const result = parse(xml) as { root: { item: string[] } };
|
|
66
|
+
|
|
67
|
+
expect(result.root.item).toEqual(["1", "2", "3"]);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("Does not remove namespace prefix from attributes", () => {
|
|
71
|
+
const xml = '<ns:root xmlns:ns="http://example.com"><ns:child>value</ns:child></ns:root>';
|
|
72
|
+
const result = parse(xml, { stripTagPrefix: true }) as {
|
|
73
|
+
root: { $: Record<string, string>; child: string[] };
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
expect(result.root.$).toHaveProperty("xmlns:ns");
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
//#endregion
|
|
81
|
+
|
|
82
|
+
//#region stringify
|
|
83
|
+
|
|
84
|
+
describe("stringify()", () => {
|
|
85
|
+
it("Serializes object to XML", () => {
|
|
86
|
+
const obj = { root: { child: "value" } };
|
|
87
|
+
const result = stringify(obj);
|
|
88
|
+
|
|
89
|
+
expect(result).toContain("<root>");
|
|
90
|
+
expect(result).toContain("<child>value</child>");
|
|
91
|
+
expect(result).toContain("</root>");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("Serializes object with attributes", () => {
|
|
95
|
+
const obj = { root: { $: { id: "1" }, child: "value" } };
|
|
96
|
+
const result = stringify(obj);
|
|
97
|
+
|
|
98
|
+
expect(result).toContain('id="1"');
|
|
99
|
+
expect(result).toContain("<child>value</child>");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("Serializes array as multiple tags", () => {
|
|
103
|
+
const obj = { root: { item: ["1", "2", "3"] } };
|
|
104
|
+
const result = stringify(obj);
|
|
105
|
+
|
|
106
|
+
expect(result).toContain("<item>1</item>");
|
|
107
|
+
expect(result).toContain("<item>2</item>");
|
|
108
|
+
expect(result).toContain("<item>3</item>");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("Serializes text node as _ key", () => {
|
|
112
|
+
const obj = { item: { $: { id: "1" }, _: "text content" } };
|
|
113
|
+
const result = stringify(obj);
|
|
114
|
+
|
|
115
|
+
expect(result).toContain('id="1"');
|
|
116
|
+
expect(result).toContain("text content");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("Serializes nested object", () => {
|
|
120
|
+
const obj = { root: { parent: { child: "value" } } };
|
|
121
|
+
const result = stringify(obj);
|
|
122
|
+
|
|
123
|
+
expect(result).toContain("<parent>");
|
|
124
|
+
expect(result).toContain("<child>value</child>");
|
|
125
|
+
expect(result).toContain("</parent>");
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
//#endregion
|
|
130
|
+
|
|
131
|
+
//#region roundtrip
|
|
132
|
+
|
|
133
|
+
describe("parse/stringify roundtrip", () => {
|
|
134
|
+
it("Structure preserved after parse then stringify", () => {
|
|
135
|
+
const xml = "<root><child>value</child></root>";
|
|
136
|
+
const parsed = parse(xml);
|
|
137
|
+
const result = stringify(parsed);
|
|
138
|
+
|
|
139
|
+
expect(result).toContain("<root>");
|
|
140
|
+
expect(result).toContain("<child>value</child>");
|
|
141
|
+
expect(result).toContain("</root>");
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
//#endregion
|
|
146
|
+
});
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { ZipArchive } from "@simplysm/core-common";
|
|
3
|
+
|
|
4
|
+
const encoder = new TextEncoder();
|
|
5
|
+
const decoder = new TextDecoder();
|
|
6
|
+
|
|
7
|
+
describe("ZipArchive", () => {
|
|
8
|
+
//#region write + compress
|
|
9
|
+
|
|
10
|
+
describe("write + compress", () => {
|
|
11
|
+
it("adds files with write and creates ZIP with compress", async () => {
|
|
12
|
+
const zip = new ZipArchive();
|
|
13
|
+
zip.write("file1.txt", encoder.encode("content 1"));
|
|
14
|
+
zip.write("file2.txt", encoder.encode("content 2"));
|
|
15
|
+
|
|
16
|
+
const zipBuffer = await zip.compress();
|
|
17
|
+
|
|
18
|
+
expect(zipBuffer instanceof Uint8Array).toBe(true);
|
|
19
|
+
expect(zipBuffer.length).toBeGreaterThan(0);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("handles filenames with special characters", async () => {
|
|
23
|
+
const zip = new ZipArchive();
|
|
24
|
+
zip.write("파일 이름.txt", encoder.encode("한글 내용"));
|
|
25
|
+
zip.write("file (1).txt", encoder.encode("괄호 포함"));
|
|
26
|
+
zip.write("file@#$.txt", encoder.encode("특수문자"));
|
|
27
|
+
|
|
28
|
+
const zipBuffer = await zip.compress();
|
|
29
|
+
const result = new ZipArchive(zipBuffer);
|
|
30
|
+
|
|
31
|
+
const content1 = await result.get("파일 이름.txt");
|
|
32
|
+
const content2 = await result.get("file (1).txt");
|
|
33
|
+
const content3 = await result.get("file@#$.txt");
|
|
34
|
+
|
|
35
|
+
expect(content1 != null ? decoder.decode(content1) : undefined).toBe("한글 내용");
|
|
36
|
+
expect(content2 != null ? decoder.decode(content2) : undefined).toBe("괄호 포함");
|
|
37
|
+
expect(content3 != null ? decoder.decode(content3) : undefined).toBe("특수문자");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("can compress empty ZIP", async () => {
|
|
41
|
+
const zip = new ZipArchive();
|
|
42
|
+
const zipBuffer = await zip.compress();
|
|
43
|
+
|
|
44
|
+
expect(zipBuffer instanceof Uint8Array).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("overwrites when writing same filename multiple times", async () => {
|
|
48
|
+
const zip = new ZipArchive();
|
|
49
|
+
zip.write("file.txt", encoder.encode("first"));
|
|
50
|
+
zip.write("file.txt", encoder.encode("second"));
|
|
51
|
+
|
|
52
|
+
const zipBuffer = await zip.compress();
|
|
53
|
+
const result = new ZipArchive(zipBuffer);
|
|
54
|
+
const content = await result.get("file.txt");
|
|
55
|
+
|
|
56
|
+
expect(content != null ? decoder.decode(content) : undefined).toBe("second");
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
//#endregion
|
|
61
|
+
|
|
62
|
+
//#region extractAll
|
|
63
|
+
|
|
64
|
+
describe("extractAll", () => {
|
|
65
|
+
it("extracts all compressed files", async () => {
|
|
66
|
+
const zip = new ZipArchive();
|
|
67
|
+
zip.write("file1.txt", encoder.encode("content 1"));
|
|
68
|
+
zip.write("file2.txt", encoder.encode("content 2"));
|
|
69
|
+
const zipBuffer = await zip.compress();
|
|
70
|
+
|
|
71
|
+
const result = new ZipArchive(zipBuffer);
|
|
72
|
+
const files = await result.extractAll();
|
|
73
|
+
|
|
74
|
+
expect(files.size).toBe(2);
|
|
75
|
+
expect(
|
|
76
|
+
files.get("file1.txt") != null ? decoder.decode(files.get("file1.txt")) : undefined,
|
|
77
|
+
).toBe("content 1");
|
|
78
|
+
expect(
|
|
79
|
+
files.get("file2.txt") != null ? decoder.decode(files.get("file2.txt")) : undefined,
|
|
80
|
+
).toBe("content 2");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("calls progress callback", async () => {
|
|
84
|
+
const zip = new ZipArchive();
|
|
85
|
+
zip.write("file1.txt", encoder.encode("a".repeat(1000)));
|
|
86
|
+
zip.write("file2.txt", encoder.encode("b".repeat(1000)));
|
|
87
|
+
const zipBuffer = await zip.compress();
|
|
88
|
+
|
|
89
|
+
const result = new ZipArchive(zipBuffer);
|
|
90
|
+
const progressCalls: Array<{ fileName: string; extractedSize: number }> = [];
|
|
91
|
+
|
|
92
|
+
await result.extractAll((progress) => {
|
|
93
|
+
progressCalls.push({
|
|
94
|
+
fileName: progress.fileName,
|
|
95
|
+
extractedSize: progress.extractedSize,
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
expect(progressCalls.length).toBeGreaterThan(0);
|
|
100
|
+
expect(progressCalls.some((p) => p.fileName === "file1.txt")).toBe(true);
|
|
101
|
+
expect(progressCalls.some((p) => p.fileName === "file2.txt")).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("returns empty cache when reader is not available", async () => {
|
|
105
|
+
const zip = new ZipArchive();
|
|
106
|
+
const files = await zip.extractAll();
|
|
107
|
+
|
|
108
|
+
expect(files.size).toBe(0);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
//#endregion
|
|
113
|
+
|
|
114
|
+
//#region get
|
|
115
|
+
|
|
116
|
+
describe("get", () => {
|
|
117
|
+
it("extracts specific file only", async () => {
|
|
118
|
+
const zip = new ZipArchive();
|
|
119
|
+
zip.write("file1.txt", encoder.encode("content 1"));
|
|
120
|
+
zip.write("file2.txt", encoder.encode("content 2"));
|
|
121
|
+
const zipBuffer = await zip.compress();
|
|
122
|
+
|
|
123
|
+
const result = new ZipArchive(zipBuffer);
|
|
124
|
+
const content = await result.get("file1.txt");
|
|
125
|
+
|
|
126
|
+
expect(content != null ? decoder.decode(content) : undefined).toBe("content 1");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("returns undefined for non-existent file", async () => {
|
|
130
|
+
const zip = new ZipArchive();
|
|
131
|
+
zip.write("file.txt", encoder.encode("content"));
|
|
132
|
+
const zipBuffer = await zip.compress();
|
|
133
|
+
|
|
134
|
+
const result = new ZipArchive(zipBuffer);
|
|
135
|
+
const content = await result.get("nonexistent.txt");
|
|
136
|
+
|
|
137
|
+
expect(content).toBe(undefined);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("does not re-extract cached file", async () => {
|
|
141
|
+
const zip = new ZipArchive();
|
|
142
|
+
zip.write("file.txt", encoder.encode("content"));
|
|
143
|
+
const zipBuffer = await zip.compress();
|
|
144
|
+
|
|
145
|
+
const result = new ZipArchive(zipBuffer);
|
|
146
|
+
const content1 = await result.get("file.txt");
|
|
147
|
+
const content2 = await result.get("file.txt");
|
|
148
|
+
|
|
149
|
+
expect(content1).toBe(content2); // Same reference
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("returns undefined when reader is not available", async () => {
|
|
153
|
+
const zip = new ZipArchive();
|
|
154
|
+
const content = await zip.get("file.txt");
|
|
155
|
+
|
|
156
|
+
expect(content).toBe(undefined);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
//#endregion
|
|
161
|
+
|
|
162
|
+
//#region exists
|
|
163
|
+
|
|
164
|
+
describe("exists", () => {
|
|
165
|
+
it("checks file existence", async () => {
|
|
166
|
+
const zip = new ZipArchive();
|
|
167
|
+
zip.write("file.txt", encoder.encode("content"));
|
|
168
|
+
const zipBuffer = await zip.compress();
|
|
169
|
+
|
|
170
|
+
const result = new ZipArchive(zipBuffer);
|
|
171
|
+
const exists = await result.exists("file.txt");
|
|
172
|
+
const notExists = await result.exists("nonexistent.txt");
|
|
173
|
+
|
|
174
|
+
expect(exists).toBe(true);
|
|
175
|
+
expect(notExists).toBe(false);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("returns true for cached file", async () => {
|
|
179
|
+
const zip = new ZipArchive();
|
|
180
|
+
zip.write("file.txt", encoder.encode("content"));
|
|
181
|
+
const zipBuffer = await zip.compress();
|
|
182
|
+
|
|
183
|
+
const result = new ZipArchive(zipBuffer);
|
|
184
|
+
await result.get("file.txt"); // Load into cache
|
|
185
|
+
const exists = await result.exists("file.txt");
|
|
186
|
+
|
|
187
|
+
expect(exists).toBe(true);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("returns false when reader is not available", async () => {
|
|
191
|
+
const zip = new ZipArchive();
|
|
192
|
+
const exists = await zip.exists("file.txt");
|
|
193
|
+
|
|
194
|
+
expect(exists).toBe(false);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
//#endregion
|
|
199
|
+
|
|
200
|
+
//#region close
|
|
201
|
+
|
|
202
|
+
describe("close", () => {
|
|
203
|
+
it("closes reader", async () => {
|
|
204
|
+
const zip = new ZipArchive();
|
|
205
|
+
zip.write("file.txt", encoder.encode("content"));
|
|
206
|
+
const zipBuffer = await zip.compress();
|
|
207
|
+
|
|
208
|
+
const result = new ZipArchive(zipBuffer);
|
|
209
|
+
// Call extractAll to load into cache
|
|
210
|
+
await result.extractAll();
|
|
211
|
+
await result.close();
|
|
212
|
+
|
|
213
|
+
// Cached data is still available after close
|
|
214
|
+
const content = await result.get("file.txt");
|
|
215
|
+
expect(content != null ? decoder.decode(content) : undefined).toBe("content");
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("works without error when reader is not available", async () => {
|
|
219
|
+
const zip = new ZipArchive();
|
|
220
|
+
await expect(zip.close()).resolves.not.toThrow();
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("automatically closes with await using statement", async () => {
|
|
224
|
+
const zip = new ZipArchive();
|
|
225
|
+
zip.write("file.txt", encoder.encode("content"));
|
|
226
|
+
const zipBuffer = await zip.compress();
|
|
227
|
+
|
|
228
|
+
{
|
|
229
|
+
await using result = new ZipArchive(zipBuffer);
|
|
230
|
+
await result.extractAll();
|
|
231
|
+
const content = await result.get("file.txt");
|
|
232
|
+
expect(decoder.decode(content)).toBe("content");
|
|
233
|
+
} // close called automatically when await using block ends
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
//#endregion
|
|
238
|
+
});
|