jazz-tools 0.16.5 → 0.17.0
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/.svelte-kit/__package__/index.d.ts +1 -0
- package/.svelte-kit/__package__/index.d.ts.map +1 -1
- package/.svelte-kit/__package__/index.js +1 -0
- package/.svelte-kit/__package__/media/image.svelte +131 -0
- package/.svelte-kit/__package__/media/image.svelte.d.ts +10 -0
- package/.svelte-kit/__package__/media/image.svelte.d.ts.map +1 -0
- package/.svelte-kit/__package__/media/index.d.ts +2 -0
- package/.svelte-kit/__package__/media/index.d.ts.map +1 -0
- package/.svelte-kit/__package__/media/index.js +1 -0
- package/.svelte-kit/__package__/tests/media/image.svelte.test.d.ts +2 -0
- package/.svelte-kit/__package__/tests/media/image.svelte.test.d.ts.map +1 -0
- package/.svelte-kit/__package__/tests/media/image.svelte.test.js +430 -0
- package/.svelte-kit/__package__/tests/testUtils.d.ts +11 -0
- package/.svelte-kit/__package__/tests/testUtils.d.ts.map +1 -0
- package/.svelte-kit/__package__/tests/testUtils.js +17 -0
- package/.svelte-kit/__package__/tests/types.d.ts +3 -0
- package/.turbo/turbo-build.log +44 -48
- package/CHANGELOG.md +28 -0
- package/dist/{chunk-H3BIFFQG.js → chunk-2SH44VLX.js} +35 -40
- package/dist/chunk-2SH44VLX.js.map +1 -0
- package/dist/index.js +1 -3
- package/dist/index.js.map +1 -1
- package/dist/inspector/{custom-element-TUXKXSZU.js → custom-element-I7L56H6B.js} +3 -5
- package/dist/inspector/{custom-element-TUXKXSZU.js.map → custom-element-I7L56H6B.js.map} +1 -1
- package/dist/inspector/index.js +2 -4
- package/dist/inspector/index.js.map +1 -1
- package/dist/inspector/register-custom-element.js +1 -1
- package/dist/inspector/viewer/co-plain-text-view.d.ts +1 -1
- package/dist/inspector/viewer/co-plain-text-view.d.ts.map +1 -1
- package/dist/inspector/viewer/role-display.d.ts.map +1 -1
- package/dist/media/chunk-BBSS3NEY.js +211 -0
- package/dist/media/chunk-BBSS3NEY.js.map +1 -0
- package/dist/media/create-image.d.ts +48 -0
- package/dist/media/create-image.d.ts.map +1 -0
- package/dist/media/create-image.test.d.ts +2 -0
- package/dist/media/create-image.test.d.ts.map +1 -0
- package/dist/media/index.browser.d.ts +15 -0
- package/dist/media/index.browser.d.ts.map +1 -0
- package/dist/media/index.browser.js +113 -0
- package/dist/media/index.browser.js.map +1 -0
- package/dist/media/index.d.ts +53 -0
- package/dist/media/index.d.ts.map +1 -0
- package/dist/media/index.js +13 -0
- package/dist/media/index.js.map +1 -0
- package/dist/media/index.native.d.ts +17 -0
- package/dist/media/index.native.d.ts.map +1 -0
- package/dist/media/index.native.js +126 -0
- package/dist/media/index.native.js.map +1 -0
- package/dist/media/utils.d.ts +17 -0
- package/dist/media/utils.d.ts.map +1 -0
- package/dist/media/utils.test.d.ts +2 -0
- package/dist/media/utils.test.d.ts.map +1 -0
- package/dist/react/index.d.ts +1 -2
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +176 -59
- package/dist/react/index.js.map +1 -1
- package/dist/react/media/image.d.ts +62 -0
- package/dist/react/media/image.d.ts.map +1 -0
- package/dist/react/ssr.d.ts.map +1 -1
- package/dist/react/ssr.js.map +1 -1
- package/dist/react/tests/media/image.test.d.ts +2 -0
- package/dist/react/tests/media/image.test.d.ts.map +1 -0
- package/dist/react/tests/testUtils.d.ts.map +1 -1
- package/dist/react-core/auth/PassphraseAuth.d.ts +1 -1
- package/dist/react-core/auth/PassphraseAuth.d.ts.map +1 -1
- package/dist/react-core/index.js +1 -3
- package/dist/react-core/index.js.map +1 -1
- package/dist/react-core/tests/testUtils.d.ts.map +1 -1
- package/dist/react-core/tests/useDemoAuth.test.d.ts +2 -0
- package/dist/react-core/tests/useDemoAuth.test.d.ts.map +1 -0
- package/dist/react-native-core/index.d.ts +1 -1
- package/dist/react-native-core/index.d.ts.map +1 -1
- package/dist/react-native-core/index.js +84 -66
- package/dist/react-native-core/index.js.map +1 -1
- package/dist/react-native-core/media/image.d.ts +93 -0
- package/dist/react-native-core/media/image.d.ts.map +1 -0
- package/dist/react-native-core/testing.d.ts +2 -0
- package/dist/react-native-core/testing.d.ts.map +1 -0
- package/dist/svelte/index.d.ts +1 -0
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +1 -0
- package/dist/svelte/media/image.svelte +131 -0
- package/dist/svelte/media/image.svelte.d.ts +10 -0
- package/dist/svelte/media/image.svelte.d.ts.map +1 -0
- package/dist/svelte/media/index.d.ts +2 -0
- package/dist/svelte/media/index.d.ts.map +1 -0
- package/dist/svelte/media/index.js +1 -0
- package/dist/svelte/tests/media/image.svelte.test.d.ts +2 -0
- package/dist/svelte/tests/media/image.svelte.test.d.ts.map +1 -0
- package/dist/svelte/tests/media/image.svelte.test.js +430 -0
- package/dist/svelte/tests/testUtils.d.ts +11 -0
- package/dist/svelte/tests/testUtils.d.ts.map +1 -0
- package/dist/svelte/tests/testUtils.js +17 -0
- package/dist/svelte/tests/types.d.ts +3 -0
- package/dist/testing.js +1 -1
- package/dist/testing.js.map +1 -1
- package/dist/tools/coValues/coFeed.d.ts +15 -0
- package/dist/tools/coValues/coFeed.d.ts.map +1 -1
- package/dist/tools/coValues/deepLoading.d.ts +10 -10
- package/dist/tools/coValues/deepLoading.d.ts.map +1 -1
- package/dist/tools/coValues/extensions/imageDef.d.ts +3 -9
- package/dist/tools/coValues/extensions/imageDef.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.d.ts +1 -0
- package/dist/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.d.ts.map +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/testing.d.ts.map +1 -1
- package/package.json +12 -12
- package/src/inspector/viewer/co-plain-text-view.tsx +1 -5
- package/src/inspector/viewer/co-stream-view.tsx +1 -1
- package/src/inspector/viewer/role-display.tsx +4 -1
- package/src/{browser-media-images/index.test.browser.ts → media/create-image.test.ts} +146 -24
- package/src/media/create-image.ts +180 -0
- package/src/media/index.browser.ts +150 -0
- package/src/media/index.native.ts +153 -0
- package/src/media/index.ts +61 -0
- package/src/media/utils.test.ts +327 -0
- package/src/media/utils.ts +202 -0
- package/src/react/index.ts +1 -2
- package/src/react/media/image.tsx +210 -0
- package/src/react/ssr.ts +1 -3
- package/src/react/tests/media/image.test.tsx +588 -0
- package/src/react/tests/testUtils.tsx +2 -10
- package/src/react-core/auth/PassphraseAuth.tsx +1 -5
- package/src/react-core/tests/testUtils.tsx +2 -10
- package/src/react-native-core/index.ts +1 -1
- package/src/react-native-core/media/image.tsx +159 -0
- package/src/svelte/index.ts +1 -0
- package/src/svelte/media/image.svelte +131 -0
- package/src/svelte/media/index.ts +1 -0
- package/src/svelte/tests/media/image.svelte.test.ts +583 -0
- package/src/svelte/tests/testUtils.ts +33 -0
- package/src/svelte/tests/types.d.ts +3 -0
- package/src/tools/coValues/coFeed.ts +40 -7
- package/src/tools/coValues/deepLoading.ts +46 -32
- package/src/tools/coValues/extensions/imageDef.ts +3 -49
- package/src/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.ts +6 -0
- package/src/tools/index.ts +0 -1
- package/src/tools/testing.ts +3 -1
- package/src/tools/tests/coList.test.ts +1 -1
- package/src/tools/tests/coMap.record.test-d.ts +105 -0
- package/src/tools/tests/coMap.record.test.ts +48 -2
- package/src/tools/tests/coMap.test-d.ts +50 -0
- package/src/tools/tests/coOptional.test.ts +3 -1
- package/tsconfig.json +1 -0
- package/tsup.config.ts +4 -9
- package/vitest.config.ts +14 -21
- package/dist/browser-media-images/index.d.ts +0 -9
- package/dist/browser-media-images/index.d.ts.map +0 -1
- package/dist/browser-media-images/index.js +0 -72
- package/dist/browser-media-images/index.js.map +0 -1
- package/dist/browser-media-images/index.test.browser.d.ts +0 -2
- package/dist/browser-media-images/index.test.browser.d.ts.map +0 -1
- package/dist/chunk-H3BIFFQG.js.map +0 -1
- package/dist/react/media.d.ts +0 -24
- package/dist/react/media.d.ts.map +0 -1
- package/dist/react-native-core/media.d.ts +0 -24
- package/dist/react-native-core/media.d.ts.map +0 -1
- package/dist/react-native-media-images/index.d.ts +0 -7
- package/dist/react-native-media-images/index.d.ts.map +0 -1
- package/dist/react-native-media-images/index.js +0 -177
- package/dist/react-native-media-images/index.js.map +0 -1
- package/dist/tools/tests/imageDef.test.d.ts +0 -2
- package/dist/tools/tests/imageDef.test.d.ts.map +0 -1
- package/src/browser-media-images/index.ts +0 -131
- package/src/react/media.tsx +0 -74
- package/src/react/scratch.tsx +0 -50
- package/src/react-native-core/media.tsx +0 -79
- package/src/react-native-media-images/index.ts +0 -238
- package/src/tools/tests/imageDef.test.ts +0 -278
@@ -0,0 +1,583 @@
|
|
1
|
+
// @vitest-environment happy-dom
|
2
|
+
import { FileStream, ImageDefinition } from "jazz-tools";
|
3
|
+
import { createJazzTestAccount } from "jazz-tools/testing";
|
4
|
+
import { describe, expect, it, vi } from "vitest";
|
5
|
+
import Image from "../../media/image.svelte";
|
6
|
+
import { render, screen, waitFor } from "../testUtils";
|
7
|
+
|
8
|
+
describe("Image", async () => {
|
9
|
+
const account = await createJazzTestAccount({
|
10
|
+
isCurrentActiveAccount: true,
|
11
|
+
});
|
12
|
+
|
13
|
+
const renderWithAccount = (props: any) => render(Image, props, { account });
|
14
|
+
|
15
|
+
describe("initial rendering", () => {
|
16
|
+
it("should render nothing if coValue is not found", async () => {
|
17
|
+
const { container } = renderWithAccount({
|
18
|
+
imageId: "co_zMTubMby3QiKDYnW9e2BEXW7Xaq",
|
19
|
+
alt: "test",
|
20
|
+
});
|
21
|
+
|
22
|
+
const img = container.querySelector("img");
|
23
|
+
expect(img).toBeDefined();
|
24
|
+
expect(img!.getAttribute("width")).toBe(null);
|
25
|
+
expect(img!.getAttribute("height")).toBe(null);
|
26
|
+
expect(img!.alt).toBe("test");
|
27
|
+
expect(img!.src).toBe("");
|
28
|
+
});
|
29
|
+
|
30
|
+
it("should render an empty image if the image is not loaded yet", async () => {
|
31
|
+
const original = FileStream.create({ owner: account._owner });
|
32
|
+
original.start({ mimeType: "image/jpeg" });
|
33
|
+
// Don't end original, so it has no chunks
|
34
|
+
|
35
|
+
const im = ImageDefinition.create(
|
36
|
+
{
|
37
|
+
original,
|
38
|
+
originalSize: [100, 100],
|
39
|
+
progressive: false,
|
40
|
+
},
|
41
|
+
{
|
42
|
+
owner: account,
|
43
|
+
},
|
44
|
+
);
|
45
|
+
|
46
|
+
const { container } = renderWithAccount({
|
47
|
+
imageId: im.id,
|
48
|
+
alt: "test",
|
49
|
+
});
|
50
|
+
|
51
|
+
const img = container.querySelector("img");
|
52
|
+
expect(img).toBeDefined();
|
53
|
+
expect(img!.getAttribute("width")).toBe(null);
|
54
|
+
expect(img!.getAttribute("height")).toBe(null);
|
55
|
+
expect(img!.alt).toBe("test");
|
56
|
+
expect(img!.src).toBe("");
|
57
|
+
});
|
58
|
+
|
59
|
+
it("should render the placeholder image if the image is not loaded yet", async () => {
|
60
|
+
const placeholderDataUrl =
|
61
|
+
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=";
|
62
|
+
|
63
|
+
const original = FileStream.create({ owner: account._owner });
|
64
|
+
original.start({ mimeType: "image/jpeg" });
|
65
|
+
// Don't end original, so it has no chunks
|
66
|
+
|
67
|
+
const im = ImageDefinition.create(
|
68
|
+
{
|
69
|
+
original,
|
70
|
+
originalSize: [100, 100],
|
71
|
+
progressive: false,
|
72
|
+
placeholderDataURL: placeholderDataUrl,
|
73
|
+
},
|
74
|
+
{
|
75
|
+
owner: account,
|
76
|
+
},
|
77
|
+
);
|
78
|
+
|
79
|
+
const { container } = renderWithAccount({
|
80
|
+
imageId: im.id,
|
81
|
+
alt: "test",
|
82
|
+
});
|
83
|
+
|
84
|
+
const img = container.querySelector("img");
|
85
|
+
expect(img).toBeDefined();
|
86
|
+
expect(img!.src).toBe(placeholderDataUrl);
|
87
|
+
});
|
88
|
+
|
89
|
+
it("should render the original image once loaded", async () => {
|
90
|
+
const createObjectURLSpy = vi
|
91
|
+
.spyOn(URL, "createObjectURL")
|
92
|
+
.mockImplementation((blob) => {
|
93
|
+
if (!(blob instanceof Blob)) {
|
94
|
+
throw new Error("Blob expected");
|
95
|
+
}
|
96
|
+
return `blob:test-${blob.size}`;
|
97
|
+
});
|
98
|
+
|
99
|
+
const im = ImageDefinition.create(
|
100
|
+
{
|
101
|
+
original: await createDummyFileStream(100, account),
|
102
|
+
originalSize: [100, 100],
|
103
|
+
progressive: false,
|
104
|
+
},
|
105
|
+
{
|
106
|
+
owner: account,
|
107
|
+
},
|
108
|
+
);
|
109
|
+
|
110
|
+
renderWithAccount({
|
111
|
+
imageId: im.id,
|
112
|
+
alt: "test-loading",
|
113
|
+
});
|
114
|
+
|
115
|
+
await waitFor(() => {
|
116
|
+
expect(
|
117
|
+
(screen.getByAltText("test-loading") as HTMLImageElement).src,
|
118
|
+
).toBe("blob:test-100");
|
119
|
+
});
|
120
|
+
|
121
|
+
expect(createObjectURLSpy).toHaveBeenCalledOnce();
|
122
|
+
});
|
123
|
+
});
|
124
|
+
|
125
|
+
describe("dimensions", () => {
|
126
|
+
it("should render the original image if the width and height are not set", async () => {
|
127
|
+
const im = ImageDefinition.create(
|
128
|
+
{
|
129
|
+
original: await createDummyFileStream(100, account),
|
130
|
+
originalSize: [100, 100],
|
131
|
+
progressive: false,
|
132
|
+
},
|
133
|
+
{
|
134
|
+
owner: account,
|
135
|
+
},
|
136
|
+
);
|
137
|
+
|
138
|
+
const { container } = renderWithAccount({
|
139
|
+
imageId: im.id,
|
140
|
+
alt: "test",
|
141
|
+
});
|
142
|
+
|
143
|
+
const img = container.querySelector("img");
|
144
|
+
expect(img).toBeDefined();
|
145
|
+
expect(img!.getAttribute("width")).toBe(null);
|
146
|
+
expect(img!.getAttribute("height")).toBe(null);
|
147
|
+
});
|
148
|
+
|
149
|
+
it("should render the original sizes", async () => {
|
150
|
+
const im = ImageDefinition.create(
|
151
|
+
{
|
152
|
+
original: await createDummyFileStream(100, account),
|
153
|
+
originalSize: [100, 100],
|
154
|
+
progressive: false,
|
155
|
+
},
|
156
|
+
{
|
157
|
+
owner: account,
|
158
|
+
},
|
159
|
+
);
|
160
|
+
|
161
|
+
const { container } = renderWithAccount({
|
162
|
+
imageId: im.id,
|
163
|
+
alt: "test",
|
164
|
+
width: "original",
|
165
|
+
height: "original",
|
166
|
+
});
|
167
|
+
|
168
|
+
const img = container.querySelector("img");
|
169
|
+
expect(img).toBeDefined();
|
170
|
+
expect(img!.getAttribute("width")).toBe("100");
|
171
|
+
expect(img!.getAttribute("height")).toBe("100");
|
172
|
+
});
|
173
|
+
|
174
|
+
it("should render the original size keeping the aspect ratio", async () => {
|
175
|
+
const im = ImageDefinition.create(
|
176
|
+
{
|
177
|
+
original: await createDummyFileStream(100, account),
|
178
|
+
originalSize: [100, 100],
|
179
|
+
progressive: false,
|
180
|
+
},
|
181
|
+
{
|
182
|
+
owner: account,
|
183
|
+
},
|
184
|
+
);
|
185
|
+
|
186
|
+
const { container } = renderWithAccount({
|
187
|
+
imageId: im.id,
|
188
|
+
alt: "test",
|
189
|
+
width: "original",
|
190
|
+
height: 300,
|
191
|
+
});
|
192
|
+
|
193
|
+
const img = container.querySelector("img");
|
194
|
+
expect(img).toBeDefined();
|
195
|
+
expect(img!.getAttribute("width")).toBe("300");
|
196
|
+
expect(img!.getAttribute("height")).toBe("300");
|
197
|
+
});
|
198
|
+
|
199
|
+
it("should render the width attribute if it is set", async () => {
|
200
|
+
const im = ImageDefinition.create(
|
201
|
+
{
|
202
|
+
original: await createDummyFileStream(100, account),
|
203
|
+
originalSize: [100, 100],
|
204
|
+
progressive: false,
|
205
|
+
},
|
206
|
+
{
|
207
|
+
owner: account,
|
208
|
+
},
|
209
|
+
);
|
210
|
+
|
211
|
+
const { container } = renderWithAccount({
|
212
|
+
imageId: im.id,
|
213
|
+
alt: "test",
|
214
|
+
width: 50,
|
215
|
+
});
|
216
|
+
|
217
|
+
const img = container.querySelector("img");
|
218
|
+
expect(img).toBeDefined();
|
219
|
+
expect(img!.getAttribute("width")).toBe("50");
|
220
|
+
expect(img!.getAttribute("height")).toBeNull();
|
221
|
+
});
|
222
|
+
|
223
|
+
it("should render the height attribute if it is set", async () => {
|
224
|
+
const im = ImageDefinition.create(
|
225
|
+
{
|
226
|
+
original: await createDummyFileStream(100, account),
|
227
|
+
originalSize: [100, 100],
|
228
|
+
progressive: false,
|
229
|
+
},
|
230
|
+
{
|
231
|
+
owner: account,
|
232
|
+
},
|
233
|
+
);
|
234
|
+
|
235
|
+
const { container } = renderWithAccount({
|
236
|
+
imageId: im.id,
|
237
|
+
alt: "test",
|
238
|
+
height: 50,
|
239
|
+
});
|
240
|
+
|
241
|
+
const img = container.querySelector("img");
|
242
|
+
expect(img).toBeDefined();
|
243
|
+
expect(img!.getAttribute("width")).toBeNull();
|
244
|
+
expect(img!.getAttribute("height")).toBe("50");
|
245
|
+
});
|
246
|
+
|
247
|
+
it("should render the class attribute if it is set", async () => {
|
248
|
+
const im = ImageDefinition.create(
|
249
|
+
{
|
250
|
+
original: await createDummyFileStream(100, account),
|
251
|
+
originalSize: [100, 100],
|
252
|
+
progressive: false,
|
253
|
+
},
|
254
|
+
{
|
255
|
+
owner: account,
|
256
|
+
},
|
257
|
+
);
|
258
|
+
|
259
|
+
const { container } = renderWithAccount({
|
260
|
+
imageId: im.id,
|
261
|
+
alt: "test",
|
262
|
+
class: "test-class",
|
263
|
+
});
|
264
|
+
|
265
|
+
const img = container.querySelector("img");
|
266
|
+
expect(img).toBeDefined();
|
267
|
+
expect(img!.classList.contains("test-class")).toBe(true);
|
268
|
+
});
|
269
|
+
});
|
270
|
+
|
271
|
+
describe("progressive loading", () => {
|
272
|
+
it("should render the resized image if progressive loading is enabled", async () => {
|
273
|
+
const createObjectURLSpy = vi
|
274
|
+
.spyOn(URL, "createObjectURL")
|
275
|
+
.mockImplementation((blob) => {
|
276
|
+
if (!(blob instanceof Blob)) {
|
277
|
+
throw new Error("Blob expected");
|
278
|
+
}
|
279
|
+
return `blob:test-${blob.size}`;
|
280
|
+
});
|
281
|
+
|
282
|
+
const original = await createDummyFileStream(500, account);
|
283
|
+
|
284
|
+
const im = ImageDefinition.create(
|
285
|
+
{
|
286
|
+
original,
|
287
|
+
originalSize: [500, 500],
|
288
|
+
progressive: true,
|
289
|
+
},
|
290
|
+
{
|
291
|
+
owner: account,
|
292
|
+
},
|
293
|
+
);
|
294
|
+
|
295
|
+
im["500x500"] = original;
|
296
|
+
im["256x256"] = await createDummyFileStream(256, account);
|
297
|
+
|
298
|
+
const { container } = renderWithAccount({
|
299
|
+
imageId: im.id,
|
300
|
+
alt: "test-progressive",
|
301
|
+
width: 300,
|
302
|
+
});
|
303
|
+
|
304
|
+
await waitFor(() => {
|
305
|
+
expect((container.querySelector("img") as HTMLImageElement).src).toBe(
|
306
|
+
"blob:test-500",
|
307
|
+
);
|
308
|
+
});
|
309
|
+
|
310
|
+
expect(createObjectURLSpy).toHaveBeenCalledOnce();
|
311
|
+
});
|
312
|
+
|
313
|
+
it("should show the highest resolution images as they are loaded", async () => {
|
314
|
+
const createObjectURLSpy = vi
|
315
|
+
.spyOn(URL, "createObjectURL")
|
316
|
+
.mockImplementation((blob) => {
|
317
|
+
if (!(blob instanceof Blob)) {
|
318
|
+
throw new Error("Blob expected");
|
319
|
+
}
|
320
|
+
return `blob:test-${blob.size}`;
|
321
|
+
});
|
322
|
+
|
323
|
+
const original = await createDummyFileStream(1920, account);
|
324
|
+
|
325
|
+
const im = ImageDefinition.create(
|
326
|
+
{
|
327
|
+
original,
|
328
|
+
originalSize: [1920, 1080],
|
329
|
+
progressive: true,
|
330
|
+
},
|
331
|
+
{
|
332
|
+
owner: account,
|
333
|
+
},
|
334
|
+
);
|
335
|
+
|
336
|
+
im["1920x1080"] = original;
|
337
|
+
im["256x256"] = await createDummyFileStream(256, account);
|
338
|
+
|
339
|
+
const { container } = renderWithAccount({
|
340
|
+
imageId: im.id,
|
341
|
+
alt: "test-progressive",
|
342
|
+
width: 1024,
|
343
|
+
});
|
344
|
+
|
345
|
+
await waitFor(() => {
|
346
|
+
expect((container.querySelector("img") as HTMLImageElement).src).toBe(
|
347
|
+
"blob:test-1920",
|
348
|
+
);
|
349
|
+
});
|
350
|
+
|
351
|
+
expect(createObjectURLSpy).toHaveBeenCalledTimes(1);
|
352
|
+
|
353
|
+
// Load higher resolution image
|
354
|
+
im["1024x1024"] = await createDummyFileStream(1024, account);
|
355
|
+
|
356
|
+
await waitFor(() => {
|
357
|
+
expect((container.querySelector("img") as HTMLImageElement).src).toBe(
|
358
|
+
"blob:test-1024",
|
359
|
+
);
|
360
|
+
});
|
361
|
+
|
362
|
+
expect(createObjectURLSpy).toHaveBeenCalledTimes(2);
|
363
|
+
});
|
364
|
+
|
365
|
+
it("should show the best loaded resolution if width is set", async () => {
|
366
|
+
const createObjectURLSpy = vi
|
367
|
+
.spyOn(URL, "createObjectURL")
|
368
|
+
.mockImplementation((blob) => {
|
369
|
+
if (!(blob instanceof Blob)) {
|
370
|
+
throw new Error("Blob expected");
|
371
|
+
}
|
372
|
+
return `blob:test-${blob.size}`;
|
373
|
+
});
|
374
|
+
|
375
|
+
const original = await FileStream.createFromBlob(createDummyBlob(1), {
|
376
|
+
owner: account,
|
377
|
+
});
|
378
|
+
|
379
|
+
const im = ImageDefinition.create(
|
380
|
+
{
|
381
|
+
original,
|
382
|
+
originalSize: [100, 100],
|
383
|
+
progressive: true,
|
384
|
+
},
|
385
|
+
{
|
386
|
+
owner: account,
|
387
|
+
},
|
388
|
+
);
|
389
|
+
|
390
|
+
im["100x100"] = original;
|
391
|
+
im["256x256"] = await createDummyFileStream(256, account);
|
392
|
+
im["1024x1024"] = await createDummyFileStream(1024, account);
|
393
|
+
|
394
|
+
const { container } = renderWithAccount({
|
395
|
+
imageId: im.id,
|
396
|
+
alt: "test-progressive",
|
397
|
+
width: 256,
|
398
|
+
});
|
399
|
+
|
400
|
+
await waitFor(() => {
|
401
|
+
expect((container.querySelector("img") as HTMLImageElement).src).toBe(
|
402
|
+
"blob:test-256",
|
403
|
+
);
|
404
|
+
});
|
405
|
+
|
406
|
+
expect(createObjectURLSpy).toHaveBeenCalledTimes(1);
|
407
|
+
});
|
408
|
+
|
409
|
+
it("should show the original image if asked resolution matches", async () => {
|
410
|
+
const createObjectURLSpy = vi
|
411
|
+
.spyOn(URL, "createObjectURL")
|
412
|
+
.mockImplementation((blob) => {
|
413
|
+
if (!(blob instanceof Blob)) {
|
414
|
+
throw new Error("Blob expected");
|
415
|
+
}
|
416
|
+
return `blob:test-${blob.size}`;
|
417
|
+
});
|
418
|
+
|
419
|
+
const original = await createDummyFileStream(100, account);
|
420
|
+
const im = ImageDefinition.create(
|
421
|
+
{
|
422
|
+
original,
|
423
|
+
originalSize: [100, 100],
|
424
|
+
progressive: true,
|
425
|
+
},
|
426
|
+
{
|
427
|
+
owner: account,
|
428
|
+
},
|
429
|
+
);
|
430
|
+
|
431
|
+
im["100x100"] = original;
|
432
|
+
im["256x256"] = await createDummyFileStream(256, account);
|
433
|
+
|
434
|
+
const { container } = renderWithAccount({
|
435
|
+
imageId: im.id,
|
436
|
+
alt: "test-progressive",
|
437
|
+
width: 100,
|
438
|
+
});
|
439
|
+
|
440
|
+
await waitFor(() => {
|
441
|
+
expect((container.querySelector("img") as HTMLImageElement).src).toBe(
|
442
|
+
"blob:test-100",
|
443
|
+
);
|
444
|
+
});
|
445
|
+
|
446
|
+
expect(createObjectURLSpy).toHaveBeenCalledTimes(1);
|
447
|
+
});
|
448
|
+
|
449
|
+
it("should update to a higher resolution image when width/height props are changed at runtime", async () => {
|
450
|
+
const createObjectURLSpy = vi
|
451
|
+
.spyOn(URL, "createObjectURL")
|
452
|
+
.mockImplementation((blob) => {
|
453
|
+
if (!(blob instanceof Blob)) {
|
454
|
+
throw new Error("Blob expected");
|
455
|
+
}
|
456
|
+
return `blob:test-${blob.size}`;
|
457
|
+
});
|
458
|
+
|
459
|
+
const original = await createDummyFileStream(256, account);
|
460
|
+
const im = ImageDefinition.create(
|
461
|
+
{
|
462
|
+
original,
|
463
|
+
originalSize: [256, 256],
|
464
|
+
progressive: true,
|
465
|
+
},
|
466
|
+
{
|
467
|
+
owner: account,
|
468
|
+
},
|
469
|
+
);
|
470
|
+
im["256x256"] = original;
|
471
|
+
im["1024x1024"] = await createDummyFileStream(1024, account);
|
472
|
+
|
473
|
+
const { container, rerender } = renderWithAccount({
|
474
|
+
imageId: im.id,
|
475
|
+
alt: "test-dynamic",
|
476
|
+
width: 256,
|
477
|
+
height: 256,
|
478
|
+
});
|
479
|
+
|
480
|
+
// Initially, should load 256x256
|
481
|
+
await waitFor(() => {
|
482
|
+
expect((container.querySelector("img") as HTMLImageElement).src).toBe(
|
483
|
+
"blob:test-256",
|
484
|
+
);
|
485
|
+
});
|
486
|
+
expect(createObjectURLSpy).toHaveBeenCalledTimes(1);
|
487
|
+
|
488
|
+
rerender({
|
489
|
+
imageId: im.id,
|
490
|
+
alt: "test-dynamic",
|
491
|
+
width: 1024,
|
492
|
+
height: 1024,
|
493
|
+
});
|
494
|
+
|
495
|
+
// After prop change, should load 1024x1024
|
496
|
+
await waitFor(() => {
|
497
|
+
expect((container.querySelector("img") as HTMLImageElement).src).toBe(
|
498
|
+
"blob:test-1024",
|
499
|
+
);
|
500
|
+
});
|
501
|
+
expect(createObjectURLSpy).toHaveBeenCalledTimes(2);
|
502
|
+
});
|
503
|
+
});
|
504
|
+
|
505
|
+
describe("lazy loading", () => {
|
506
|
+
it("should return an empty png if loading is lazy and placeholder is not set", async () => {
|
507
|
+
const im = ImageDefinition.create(
|
508
|
+
{
|
509
|
+
original: await createDummyFileStream(100, account),
|
510
|
+
originalSize: [100, 100],
|
511
|
+
progressive: false,
|
512
|
+
},
|
513
|
+
{
|
514
|
+
owner: account,
|
515
|
+
},
|
516
|
+
);
|
517
|
+
|
518
|
+
const { container } = renderWithAccount({
|
519
|
+
imageId: im.id,
|
520
|
+
alt: "test",
|
521
|
+
loading: "lazy",
|
522
|
+
});
|
523
|
+
|
524
|
+
const img = container.querySelector("img");
|
525
|
+
expect(img).toBeDefined();
|
526
|
+
expect(img!.src).toBe("blob:test-70");
|
527
|
+
});
|
528
|
+
|
529
|
+
it("should load the image when threshold is reached", async () => {
|
530
|
+
const createObjectURLSpy = vi
|
531
|
+
.spyOn(URL, "createObjectURL")
|
532
|
+
.mockImplementation((blob) => {
|
533
|
+
if (!(blob instanceof Blob)) {
|
534
|
+
throw new Error("Blob expected");
|
535
|
+
}
|
536
|
+
return `blob:test-${blob.size}`;
|
537
|
+
});
|
538
|
+
|
539
|
+
const im = ImageDefinition.create(
|
540
|
+
{
|
541
|
+
original: await createDummyFileStream(100, account),
|
542
|
+
originalSize: [100, 100],
|
543
|
+
progressive: false,
|
544
|
+
},
|
545
|
+
{
|
546
|
+
owner: account,
|
547
|
+
},
|
548
|
+
);
|
549
|
+
|
550
|
+
const { container } = renderWithAccount({
|
551
|
+
imageId: im.id,
|
552
|
+
alt: "test",
|
553
|
+
loading: "lazy",
|
554
|
+
});
|
555
|
+
|
556
|
+
const img = container.querySelector("img");
|
557
|
+
// simulate the load event when the browser's viewport reach the image
|
558
|
+
img!.dispatchEvent(new Event("load"));
|
559
|
+
|
560
|
+
await waitFor(() => {
|
561
|
+
expect((container.querySelector("img") as HTMLImageElement).src).toBe(
|
562
|
+
"blob:test-100",
|
563
|
+
);
|
564
|
+
});
|
565
|
+
|
566
|
+
expect(createObjectURLSpy).toHaveBeenCalledTimes(2);
|
567
|
+
});
|
568
|
+
});
|
569
|
+
});
|
570
|
+
|
571
|
+
function createDummyBlob(size: number): Blob {
|
572
|
+
const blob = new Blob([new Uint8Array(size)], { type: "image/png" });
|
573
|
+
return blob;
|
574
|
+
}
|
575
|
+
|
576
|
+
function createDummyFileStream(
|
577
|
+
size: number,
|
578
|
+
account: Awaited<ReturnType<typeof createJazzTestAccount>>,
|
579
|
+
) {
|
580
|
+
return FileStream.createFromBlob(createDummyBlob(size), {
|
581
|
+
owner: account,
|
582
|
+
});
|
583
|
+
}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import { render as renderSvelte } from "@testing-library/svelte";
|
2
|
+
import { Account, AnonymousJazzAgent } from "jazz-tools";
|
3
|
+
import { TestJazzContextManager } from "jazz-tools/testing";
|
4
|
+
import { Component, ComponentProps } from "svelte";
|
5
|
+
import { JAZZ_AUTH_CTX, JAZZ_CTX } from "../jazz.svelte";
|
6
|
+
|
7
|
+
type JazzExtendedOptions = {
|
8
|
+
account: Account | { guest: AnonymousJazzAgent };
|
9
|
+
};
|
10
|
+
|
11
|
+
const render = <T extends Component>(
|
12
|
+
component: T,
|
13
|
+
props: ComponentProps<T>,
|
14
|
+
jazzOptions: JazzExtendedOptions,
|
15
|
+
) => {
|
16
|
+
const ctx = TestJazzContextManager.fromAccountOrGuest(jazzOptions.account);
|
17
|
+
|
18
|
+
return renderSvelte(
|
19
|
+
// @ts-expect-error Svelte new Component type is not compatible with @testing-library/svelte
|
20
|
+
component,
|
21
|
+
{
|
22
|
+
props,
|
23
|
+
context: new Map<any, any>([
|
24
|
+
[JAZZ_CTX, { current: ctx.getCurrentValue() }],
|
25
|
+
[JAZZ_AUTH_CTX, ctx.getAuthSecretStorage()],
|
26
|
+
]),
|
27
|
+
},
|
28
|
+
);
|
29
|
+
};
|
30
|
+
|
31
|
+
export * from "@testing-library/svelte";
|
32
|
+
|
33
|
+
export { render };
|
@@ -9,7 +9,7 @@ import type {
|
|
9
9
|
RawCoStream,
|
10
10
|
SessionID,
|
11
11
|
} from "cojson";
|
12
|
-
import {
|
12
|
+
import { cojsonInternals } from "cojson";
|
13
13
|
import type {
|
14
14
|
AnonymousJazzAgent,
|
15
15
|
CoValue,
|
@@ -836,6 +836,38 @@ export class FileStream extends CoValueBase implements CoValue {
|
|
836
836
|
}
|
837
837
|
| Account
|
838
838
|
| Group,
|
839
|
+
): Promise<FileStream> {
|
840
|
+
const arrayBuffer = await blob.arrayBuffer();
|
841
|
+
return this.createFromArrayBuffer(
|
842
|
+
arrayBuffer,
|
843
|
+
blob.type,
|
844
|
+
blob instanceof File ? blob.name : undefined,
|
845
|
+
options,
|
846
|
+
);
|
847
|
+
}
|
848
|
+
|
849
|
+
/**
|
850
|
+
* Create a `FileStream` from a `Blob` or `File`
|
851
|
+
*
|
852
|
+
* @example
|
853
|
+
* ```ts
|
854
|
+
* import { coField, FileStream } from "jazz-tools";
|
855
|
+
*
|
856
|
+
* const fileStream = await FileStream.createFromBlob(file, {owner: group})
|
857
|
+
* ```
|
858
|
+
* @category Content
|
859
|
+
*/
|
860
|
+
static async createFromArrayBuffer(
|
861
|
+
arrayBuffer: ArrayBuffer,
|
862
|
+
mimeType: string,
|
863
|
+
fileName: string | undefined,
|
864
|
+
options?:
|
865
|
+
| {
|
866
|
+
owner?: Group | Account;
|
867
|
+
onProgress?: (progress: number) => void;
|
868
|
+
}
|
869
|
+
| Account
|
870
|
+
| Group,
|
839
871
|
): Promise<FileStream> {
|
840
872
|
const stream = this.create(options);
|
841
873
|
const onProgress =
|
@@ -843,13 +875,14 @@ export class FileStream extends CoValueBase implements CoValue {
|
|
843
875
|
|
844
876
|
const start = Date.now();
|
845
877
|
|
846
|
-
const data = new Uint8Array(
|
878
|
+
const data = new Uint8Array(arrayBuffer);
|
847
879
|
stream.start({
|
848
|
-
mimeType
|
849
|
-
totalSizeBytes:
|
850
|
-
fileName
|
880
|
+
mimeType,
|
881
|
+
totalSizeBytes: arrayBuffer.byteLength,
|
882
|
+
fileName,
|
851
883
|
});
|
852
|
-
const chunkSize =
|
884
|
+
const chunkSize =
|
885
|
+
cojsonInternals.TRANSACTION_CONFIG.MAX_RECOMMENDED_TX_SIZE;
|
853
886
|
|
854
887
|
let lastProgressUpdate = Date.now();
|
855
888
|
|
@@ -870,7 +903,7 @@ export class FileStream extends CoValueBase implements CoValue {
|
|
870
903
|
"Finished creating binary stream in",
|
871
904
|
(end - start) / 1000,
|
872
905
|
"s - Throughput in MB/s",
|
873
|
-
(1000 * (
|
906
|
+
(1000 * (arrayBuffer.byteLength / (end - start))) / (1024 * 1024),
|
874
907
|
);
|
875
908
|
onProgress?.(1);
|
876
909
|
|