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
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/svelte/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACvE,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,OAAO,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC"}
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/svelte/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACvE,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,OAAO,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AACzE,cAAc,kBAAkB,CAAC"}
|
@@ -0,0 +1,131 @@
|
|
1
|
+
<script lang="ts">
|
2
|
+
import { ImageDefinition } from "jazz-tools";
|
3
|
+
import { highestResAvailable } from "jazz-tools/media";
|
4
|
+
import { onDestroy } from "svelte";
|
5
|
+
import type { HTMLImgAttributes } from "svelte/elements";
|
6
|
+
import { CoState } from "../jazz.class.svelte";
|
7
|
+
|
8
|
+
interface ImageProps extends Omit<HTMLImgAttributes, "width" | "height"> {
|
9
|
+
imageId: string;
|
10
|
+
width?: number | "original";
|
11
|
+
height?: number | "original";
|
12
|
+
}
|
13
|
+
|
14
|
+
const { imageId, width, height, ...rest }: ImageProps = $props();
|
15
|
+
|
16
|
+
const imageState = new CoState(ImageDefinition, imageId);
|
17
|
+
let lastBestImage: [string, string] | null = null;
|
18
|
+
|
19
|
+
/**
|
20
|
+
* For lazy loading, we use the browser's strategy for images with loading="lazy".
|
21
|
+
* We use an empty image, and when the browser triggers the load event, we load the best available image.
|
22
|
+
* On page loading, if the image url is already in browser's cache, the load event is triggered immediately.
|
23
|
+
* This is why we need to use a different blob url for every image.
|
24
|
+
*/
|
25
|
+
let waitingLazyLoading = $state(rest.loading === "lazy");
|
26
|
+
const lazyPlaceholder = $derived.by(() =>
|
27
|
+
waitingLazyLoading ? URL.createObjectURL(emptyPixelBlob) : undefined,
|
28
|
+
);
|
29
|
+
|
30
|
+
const dimensions = $derived.by<{
|
31
|
+
width: number | undefined;
|
32
|
+
height: number | undefined;
|
33
|
+
}>(() => {
|
34
|
+
const originalWidth = imageState.current?.originalSize?.[0];
|
35
|
+
const originalHeight = imageState.current?.originalSize?.[1];
|
36
|
+
|
37
|
+
// Both width and height are "original"
|
38
|
+
if (width === "original" && height === "original") {
|
39
|
+
return { width: originalWidth, height: originalHeight };
|
40
|
+
}
|
41
|
+
|
42
|
+
// Width is "original", height is a number
|
43
|
+
if (width === "original" && typeof height === "number") {
|
44
|
+
if (originalWidth && originalHeight) {
|
45
|
+
return {
|
46
|
+
width: Math.round((height * originalWidth) / originalHeight),
|
47
|
+
height,
|
48
|
+
};
|
49
|
+
}
|
50
|
+
return { width: undefined, height };
|
51
|
+
}
|
52
|
+
|
53
|
+
// Height is "original", width is a number
|
54
|
+
if (height === "original" && typeof width === "number") {
|
55
|
+
if (originalWidth && originalHeight) {
|
56
|
+
return {
|
57
|
+
width,
|
58
|
+
height: Math.round((width * originalHeight) / originalWidth),
|
59
|
+
};
|
60
|
+
}
|
61
|
+
return { width, height: undefined };
|
62
|
+
}
|
63
|
+
|
64
|
+
// In all other cases, use the property value:
|
65
|
+
return {
|
66
|
+
width: width === "original" ? originalWidth : width,
|
67
|
+
height: height === "original" ? originalHeight : height,
|
68
|
+
};
|
69
|
+
});
|
70
|
+
|
71
|
+
const src = $derived.by(() => {
|
72
|
+
if (waitingLazyLoading) {
|
73
|
+
return lazyPlaceholder;
|
74
|
+
}
|
75
|
+
|
76
|
+
const image = imageState.current;
|
77
|
+
if (!image) return undefined;
|
78
|
+
|
79
|
+
const bestImage = highestResAvailable(
|
80
|
+
image,
|
81
|
+
dimensions.width || dimensions.height || 9999,
|
82
|
+
dimensions.height || dimensions.width || 9999,
|
83
|
+
);
|
84
|
+
|
85
|
+
if (!bestImage) return image.placeholderDataURL;
|
86
|
+
if (lastBestImage?.[0] === bestImage.image.id) return lastBestImage?.[1];
|
87
|
+
|
88
|
+
const blob = bestImage.image.toBlob();
|
89
|
+
|
90
|
+
if (blob) {
|
91
|
+
const url = URL.createObjectURL(blob);
|
92
|
+
revokeObjectURL(lastBestImage?.[1]);
|
93
|
+
lastBestImage = [bestImage.image.id, url];
|
94
|
+
return url;
|
95
|
+
}
|
96
|
+
|
97
|
+
return image.placeholderDataURL;
|
98
|
+
});
|
99
|
+
|
100
|
+
// Cleanup object URL on component destroy
|
101
|
+
onDestroy(() => {
|
102
|
+
revokeObjectURL(lastBestImage?.[1]);
|
103
|
+
});
|
104
|
+
|
105
|
+
function revokeObjectURL(url: string | undefined) {
|
106
|
+
if (url && url.startsWith("blob:")) {
|
107
|
+
URL.revokeObjectURL(url);
|
108
|
+
}
|
109
|
+
}
|
110
|
+
|
111
|
+
const emptyPixelBlob = new Blob(
|
112
|
+
[
|
113
|
+
Uint8Array.from(
|
114
|
+
atob(
|
115
|
+
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==",
|
116
|
+
),
|
117
|
+
(c) => c.charCodeAt(0),
|
118
|
+
),
|
119
|
+
],
|
120
|
+
{ type: "image/png" },
|
121
|
+
);
|
122
|
+
</script>
|
123
|
+
|
124
|
+
<img
|
125
|
+
{src}
|
126
|
+
width={dimensions.width}
|
127
|
+
height={dimensions.height}
|
128
|
+
alt={rest.alt}
|
129
|
+
onload={() => {waitingLazyLoading = false}}
|
130
|
+
{...rest}
|
131
|
+
/>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import type { HTMLImgAttributes } from "svelte/elements";
|
2
|
+
interface ImageProps extends Omit<HTMLImgAttributes, "width" | "height"> {
|
3
|
+
imageId: string;
|
4
|
+
width?: number | "original";
|
5
|
+
height?: number | "original";
|
6
|
+
}
|
7
|
+
declare const Image: import("svelte").Component<ImageProps, {}, "">;
|
8
|
+
type Image = ReturnType<typeof Image>;
|
9
|
+
export default Image;
|
10
|
+
//# sourceMappingURL=image.svelte.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"image.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/media/image.svelte.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAIzD,UAAU,UAAW,SAAQ,IAAI,CAAC,iBAAiB,EAAE,OAAO,GAAG,QAAQ,CAAC;IACtE,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;CAC9B;AA2HD,QAAA,MAAM,KAAK,gDAAsC,CAAC;AAClD,KAAK,KAAK,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AACtC,eAAe,KAAK,CAAC"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/svelte/media/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC"}
|
@@ -0,0 +1 @@
|
|
1
|
+
export { default as Image } from "./image.svelte";
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"image.svelte.test.d.ts","sourceRoot":"","sources":["../../../../../src/svelte/tests/media/image.svelte.test.ts"],"names":[],"mappings":""}
|
@@ -0,0 +1,430 @@
|
|
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
|
+
describe("Image", async () => {
|
8
|
+
const account = await createJazzTestAccount({
|
9
|
+
isCurrentActiveAccount: true,
|
10
|
+
});
|
11
|
+
const renderWithAccount = (props) => render(Image, props, { account });
|
12
|
+
describe("initial rendering", () => {
|
13
|
+
it("should render nothing if coValue is not found", async () => {
|
14
|
+
const { container } = renderWithAccount({
|
15
|
+
imageId: "co_zMTubMby3QiKDYnW9e2BEXW7Xaq",
|
16
|
+
alt: "test",
|
17
|
+
});
|
18
|
+
const img = container.querySelector("img");
|
19
|
+
expect(img).toBeDefined();
|
20
|
+
expect(img.getAttribute("width")).toBe(null);
|
21
|
+
expect(img.getAttribute("height")).toBe(null);
|
22
|
+
expect(img.alt).toBe("test");
|
23
|
+
expect(img.src).toBe("");
|
24
|
+
});
|
25
|
+
it("should render an empty image if the image is not loaded yet", async () => {
|
26
|
+
const original = FileStream.create({ owner: account._owner });
|
27
|
+
original.start({ mimeType: "image/jpeg" });
|
28
|
+
// Don't end original, so it has no chunks
|
29
|
+
const im = ImageDefinition.create({
|
30
|
+
original,
|
31
|
+
originalSize: [100, 100],
|
32
|
+
progressive: false,
|
33
|
+
}, {
|
34
|
+
owner: account,
|
35
|
+
});
|
36
|
+
const { container } = renderWithAccount({
|
37
|
+
imageId: im.id,
|
38
|
+
alt: "test",
|
39
|
+
});
|
40
|
+
const img = container.querySelector("img");
|
41
|
+
expect(img).toBeDefined();
|
42
|
+
expect(img.getAttribute("width")).toBe(null);
|
43
|
+
expect(img.getAttribute("height")).toBe(null);
|
44
|
+
expect(img.alt).toBe("test");
|
45
|
+
expect(img.src).toBe("");
|
46
|
+
});
|
47
|
+
it("should render the placeholder image if the image is not loaded yet", async () => {
|
48
|
+
const placeholderDataUrl = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=";
|
49
|
+
const original = FileStream.create({ owner: account._owner });
|
50
|
+
original.start({ mimeType: "image/jpeg" });
|
51
|
+
// Don't end original, so it has no chunks
|
52
|
+
const im = ImageDefinition.create({
|
53
|
+
original,
|
54
|
+
originalSize: [100, 100],
|
55
|
+
progressive: false,
|
56
|
+
placeholderDataURL: placeholderDataUrl,
|
57
|
+
}, {
|
58
|
+
owner: account,
|
59
|
+
});
|
60
|
+
const { container } = renderWithAccount({
|
61
|
+
imageId: im.id,
|
62
|
+
alt: "test",
|
63
|
+
});
|
64
|
+
const img = container.querySelector("img");
|
65
|
+
expect(img).toBeDefined();
|
66
|
+
expect(img.src).toBe(placeholderDataUrl);
|
67
|
+
});
|
68
|
+
it("should render the original image once loaded", async () => {
|
69
|
+
const createObjectURLSpy = vi
|
70
|
+
.spyOn(URL, "createObjectURL")
|
71
|
+
.mockImplementation((blob) => {
|
72
|
+
if (!(blob instanceof Blob)) {
|
73
|
+
throw new Error("Blob expected");
|
74
|
+
}
|
75
|
+
return `blob:test-${blob.size}`;
|
76
|
+
});
|
77
|
+
const im = ImageDefinition.create({
|
78
|
+
original: await createDummyFileStream(100, account),
|
79
|
+
originalSize: [100, 100],
|
80
|
+
progressive: false,
|
81
|
+
}, {
|
82
|
+
owner: account,
|
83
|
+
});
|
84
|
+
renderWithAccount({
|
85
|
+
imageId: im.id,
|
86
|
+
alt: "test-loading",
|
87
|
+
});
|
88
|
+
await waitFor(() => {
|
89
|
+
expect(screen.getByAltText("test-loading").src).toBe("blob:test-100");
|
90
|
+
});
|
91
|
+
expect(createObjectURLSpy).toHaveBeenCalledOnce();
|
92
|
+
});
|
93
|
+
});
|
94
|
+
describe("dimensions", () => {
|
95
|
+
it("should render the original image if the width and height are not set", async () => {
|
96
|
+
const im = ImageDefinition.create({
|
97
|
+
original: await createDummyFileStream(100, account),
|
98
|
+
originalSize: [100, 100],
|
99
|
+
progressive: false,
|
100
|
+
}, {
|
101
|
+
owner: account,
|
102
|
+
});
|
103
|
+
const { container } = renderWithAccount({
|
104
|
+
imageId: im.id,
|
105
|
+
alt: "test",
|
106
|
+
});
|
107
|
+
const img = container.querySelector("img");
|
108
|
+
expect(img).toBeDefined();
|
109
|
+
expect(img.getAttribute("width")).toBe(null);
|
110
|
+
expect(img.getAttribute("height")).toBe(null);
|
111
|
+
});
|
112
|
+
it("should render the original sizes", async () => {
|
113
|
+
const im = ImageDefinition.create({
|
114
|
+
original: await createDummyFileStream(100, account),
|
115
|
+
originalSize: [100, 100],
|
116
|
+
progressive: false,
|
117
|
+
}, {
|
118
|
+
owner: account,
|
119
|
+
});
|
120
|
+
const { container } = renderWithAccount({
|
121
|
+
imageId: im.id,
|
122
|
+
alt: "test",
|
123
|
+
width: "original",
|
124
|
+
height: "original",
|
125
|
+
});
|
126
|
+
const img = container.querySelector("img");
|
127
|
+
expect(img).toBeDefined();
|
128
|
+
expect(img.getAttribute("width")).toBe("100");
|
129
|
+
expect(img.getAttribute("height")).toBe("100");
|
130
|
+
});
|
131
|
+
it("should render the original size keeping the aspect ratio", async () => {
|
132
|
+
const im = ImageDefinition.create({
|
133
|
+
original: await createDummyFileStream(100, account),
|
134
|
+
originalSize: [100, 100],
|
135
|
+
progressive: false,
|
136
|
+
}, {
|
137
|
+
owner: account,
|
138
|
+
});
|
139
|
+
const { container } = renderWithAccount({
|
140
|
+
imageId: im.id,
|
141
|
+
alt: "test",
|
142
|
+
width: "original",
|
143
|
+
height: 300,
|
144
|
+
});
|
145
|
+
const img = container.querySelector("img");
|
146
|
+
expect(img).toBeDefined();
|
147
|
+
expect(img.getAttribute("width")).toBe("300");
|
148
|
+
expect(img.getAttribute("height")).toBe("300");
|
149
|
+
});
|
150
|
+
it("should render the width attribute if it is set", async () => {
|
151
|
+
const im = ImageDefinition.create({
|
152
|
+
original: await createDummyFileStream(100, account),
|
153
|
+
originalSize: [100, 100],
|
154
|
+
progressive: false,
|
155
|
+
}, {
|
156
|
+
owner: account,
|
157
|
+
});
|
158
|
+
const { container } = renderWithAccount({
|
159
|
+
imageId: im.id,
|
160
|
+
alt: "test",
|
161
|
+
width: 50,
|
162
|
+
});
|
163
|
+
const img = container.querySelector("img");
|
164
|
+
expect(img).toBeDefined();
|
165
|
+
expect(img.getAttribute("width")).toBe("50");
|
166
|
+
expect(img.getAttribute("height")).toBeNull();
|
167
|
+
});
|
168
|
+
it("should render the height attribute if it is set", async () => {
|
169
|
+
const im = ImageDefinition.create({
|
170
|
+
original: await createDummyFileStream(100, account),
|
171
|
+
originalSize: [100, 100],
|
172
|
+
progressive: false,
|
173
|
+
}, {
|
174
|
+
owner: account,
|
175
|
+
});
|
176
|
+
const { container } = renderWithAccount({
|
177
|
+
imageId: im.id,
|
178
|
+
alt: "test",
|
179
|
+
height: 50,
|
180
|
+
});
|
181
|
+
const img = container.querySelector("img");
|
182
|
+
expect(img).toBeDefined();
|
183
|
+
expect(img.getAttribute("width")).toBeNull();
|
184
|
+
expect(img.getAttribute("height")).toBe("50");
|
185
|
+
});
|
186
|
+
it("should render the class attribute if it is set", async () => {
|
187
|
+
const im = ImageDefinition.create({
|
188
|
+
original: await createDummyFileStream(100, account),
|
189
|
+
originalSize: [100, 100],
|
190
|
+
progressive: false,
|
191
|
+
}, {
|
192
|
+
owner: account,
|
193
|
+
});
|
194
|
+
const { container } = renderWithAccount({
|
195
|
+
imageId: im.id,
|
196
|
+
alt: "test",
|
197
|
+
class: "test-class",
|
198
|
+
});
|
199
|
+
const img = container.querySelector("img");
|
200
|
+
expect(img).toBeDefined();
|
201
|
+
expect(img.classList.contains("test-class")).toBe(true);
|
202
|
+
});
|
203
|
+
});
|
204
|
+
describe("progressive loading", () => {
|
205
|
+
it("should render the resized image if progressive loading is enabled", async () => {
|
206
|
+
const createObjectURLSpy = vi
|
207
|
+
.spyOn(URL, "createObjectURL")
|
208
|
+
.mockImplementation((blob) => {
|
209
|
+
if (!(blob instanceof Blob)) {
|
210
|
+
throw new Error("Blob expected");
|
211
|
+
}
|
212
|
+
return `blob:test-${blob.size}`;
|
213
|
+
});
|
214
|
+
const original = await createDummyFileStream(500, account);
|
215
|
+
const im = ImageDefinition.create({
|
216
|
+
original,
|
217
|
+
originalSize: [500, 500],
|
218
|
+
progressive: true,
|
219
|
+
}, {
|
220
|
+
owner: account,
|
221
|
+
});
|
222
|
+
im["500x500"] = original;
|
223
|
+
im["256x256"] = await createDummyFileStream(256, account);
|
224
|
+
const { container } = renderWithAccount({
|
225
|
+
imageId: im.id,
|
226
|
+
alt: "test-progressive",
|
227
|
+
width: 300,
|
228
|
+
});
|
229
|
+
await waitFor(() => {
|
230
|
+
expect(container.querySelector("img").src).toBe("blob:test-500");
|
231
|
+
});
|
232
|
+
expect(createObjectURLSpy).toHaveBeenCalledOnce();
|
233
|
+
});
|
234
|
+
it("should show the highest resolution images as they are loaded", async () => {
|
235
|
+
const createObjectURLSpy = vi
|
236
|
+
.spyOn(URL, "createObjectURL")
|
237
|
+
.mockImplementation((blob) => {
|
238
|
+
if (!(blob instanceof Blob)) {
|
239
|
+
throw new Error("Blob expected");
|
240
|
+
}
|
241
|
+
return `blob:test-${blob.size}`;
|
242
|
+
});
|
243
|
+
const original = await createDummyFileStream(1920, account);
|
244
|
+
const im = ImageDefinition.create({
|
245
|
+
original,
|
246
|
+
originalSize: [1920, 1080],
|
247
|
+
progressive: true,
|
248
|
+
}, {
|
249
|
+
owner: account,
|
250
|
+
});
|
251
|
+
im["1920x1080"] = original;
|
252
|
+
im["256x256"] = await createDummyFileStream(256, account);
|
253
|
+
const { container } = renderWithAccount({
|
254
|
+
imageId: im.id,
|
255
|
+
alt: "test-progressive",
|
256
|
+
width: 1024,
|
257
|
+
});
|
258
|
+
await waitFor(() => {
|
259
|
+
expect(container.querySelector("img").src).toBe("blob:test-1920");
|
260
|
+
});
|
261
|
+
expect(createObjectURLSpy).toHaveBeenCalledTimes(1);
|
262
|
+
// Load higher resolution image
|
263
|
+
im["1024x1024"] = await createDummyFileStream(1024, account);
|
264
|
+
await waitFor(() => {
|
265
|
+
expect(container.querySelector("img").src).toBe("blob:test-1024");
|
266
|
+
});
|
267
|
+
expect(createObjectURLSpy).toHaveBeenCalledTimes(2);
|
268
|
+
});
|
269
|
+
it("should show the best loaded resolution if width is set", async () => {
|
270
|
+
const createObjectURLSpy = vi
|
271
|
+
.spyOn(URL, "createObjectURL")
|
272
|
+
.mockImplementation((blob) => {
|
273
|
+
if (!(blob instanceof Blob)) {
|
274
|
+
throw new Error("Blob expected");
|
275
|
+
}
|
276
|
+
return `blob:test-${blob.size}`;
|
277
|
+
});
|
278
|
+
const original = await FileStream.createFromBlob(createDummyBlob(1), {
|
279
|
+
owner: account,
|
280
|
+
});
|
281
|
+
const im = ImageDefinition.create({
|
282
|
+
original,
|
283
|
+
originalSize: [100, 100],
|
284
|
+
progressive: true,
|
285
|
+
}, {
|
286
|
+
owner: account,
|
287
|
+
});
|
288
|
+
im["100x100"] = original;
|
289
|
+
im["256x256"] = await createDummyFileStream(256, account);
|
290
|
+
im["1024x1024"] = await createDummyFileStream(1024, account);
|
291
|
+
const { container } = renderWithAccount({
|
292
|
+
imageId: im.id,
|
293
|
+
alt: "test-progressive",
|
294
|
+
width: 256,
|
295
|
+
});
|
296
|
+
await waitFor(() => {
|
297
|
+
expect(container.querySelector("img").src).toBe("blob:test-256");
|
298
|
+
});
|
299
|
+
expect(createObjectURLSpy).toHaveBeenCalledTimes(1);
|
300
|
+
});
|
301
|
+
it("should show the original image if asked resolution matches", async () => {
|
302
|
+
const createObjectURLSpy = vi
|
303
|
+
.spyOn(URL, "createObjectURL")
|
304
|
+
.mockImplementation((blob) => {
|
305
|
+
if (!(blob instanceof Blob)) {
|
306
|
+
throw new Error("Blob expected");
|
307
|
+
}
|
308
|
+
return `blob:test-${blob.size}`;
|
309
|
+
});
|
310
|
+
const original = await createDummyFileStream(100, account);
|
311
|
+
const im = ImageDefinition.create({
|
312
|
+
original,
|
313
|
+
originalSize: [100, 100],
|
314
|
+
progressive: true,
|
315
|
+
}, {
|
316
|
+
owner: account,
|
317
|
+
});
|
318
|
+
im["100x100"] = original;
|
319
|
+
im["256x256"] = await createDummyFileStream(256, account);
|
320
|
+
const { container } = renderWithAccount({
|
321
|
+
imageId: im.id,
|
322
|
+
alt: "test-progressive",
|
323
|
+
width: 100,
|
324
|
+
});
|
325
|
+
await waitFor(() => {
|
326
|
+
expect(container.querySelector("img").src).toBe("blob:test-100");
|
327
|
+
});
|
328
|
+
expect(createObjectURLSpy).toHaveBeenCalledTimes(1);
|
329
|
+
});
|
330
|
+
it("should update to a higher resolution image when width/height props are changed at runtime", async () => {
|
331
|
+
const createObjectURLSpy = vi
|
332
|
+
.spyOn(URL, "createObjectURL")
|
333
|
+
.mockImplementation((blob) => {
|
334
|
+
if (!(blob instanceof Blob)) {
|
335
|
+
throw new Error("Blob expected");
|
336
|
+
}
|
337
|
+
return `blob:test-${blob.size}`;
|
338
|
+
});
|
339
|
+
const original = await createDummyFileStream(256, account);
|
340
|
+
const im = ImageDefinition.create({
|
341
|
+
original,
|
342
|
+
originalSize: [256, 256],
|
343
|
+
progressive: true,
|
344
|
+
}, {
|
345
|
+
owner: account,
|
346
|
+
});
|
347
|
+
im["256x256"] = original;
|
348
|
+
im["1024x1024"] = await createDummyFileStream(1024, account);
|
349
|
+
const { container, rerender } = renderWithAccount({
|
350
|
+
imageId: im.id,
|
351
|
+
alt: "test-dynamic",
|
352
|
+
width: 256,
|
353
|
+
height: 256,
|
354
|
+
});
|
355
|
+
// Initially, should load 256x256
|
356
|
+
await waitFor(() => {
|
357
|
+
expect(container.querySelector("img").src).toBe("blob:test-256");
|
358
|
+
});
|
359
|
+
expect(createObjectURLSpy).toHaveBeenCalledTimes(1);
|
360
|
+
rerender({
|
361
|
+
imageId: im.id,
|
362
|
+
alt: "test-dynamic",
|
363
|
+
width: 1024,
|
364
|
+
height: 1024,
|
365
|
+
});
|
366
|
+
// After prop change, should load 1024x1024
|
367
|
+
await waitFor(() => {
|
368
|
+
expect(container.querySelector("img").src).toBe("blob:test-1024");
|
369
|
+
});
|
370
|
+
expect(createObjectURLSpy).toHaveBeenCalledTimes(2);
|
371
|
+
});
|
372
|
+
});
|
373
|
+
describe("lazy loading", () => {
|
374
|
+
it("should return an empty png if loading is lazy and placeholder is not set", async () => {
|
375
|
+
const im = ImageDefinition.create({
|
376
|
+
original: await createDummyFileStream(100, account),
|
377
|
+
originalSize: [100, 100],
|
378
|
+
progressive: false,
|
379
|
+
}, {
|
380
|
+
owner: account,
|
381
|
+
});
|
382
|
+
const { container } = renderWithAccount({
|
383
|
+
imageId: im.id,
|
384
|
+
alt: "test",
|
385
|
+
loading: "lazy",
|
386
|
+
});
|
387
|
+
const img = container.querySelector("img");
|
388
|
+
expect(img).toBeDefined();
|
389
|
+
expect(img.src).toBe("blob:test-70");
|
390
|
+
});
|
391
|
+
it("should load the image when threshold is reached", async () => {
|
392
|
+
const createObjectURLSpy = vi
|
393
|
+
.spyOn(URL, "createObjectURL")
|
394
|
+
.mockImplementation((blob) => {
|
395
|
+
if (!(blob instanceof Blob)) {
|
396
|
+
throw new Error("Blob expected");
|
397
|
+
}
|
398
|
+
return `blob:test-${blob.size}`;
|
399
|
+
});
|
400
|
+
const im = ImageDefinition.create({
|
401
|
+
original: await createDummyFileStream(100, account),
|
402
|
+
originalSize: [100, 100],
|
403
|
+
progressive: false,
|
404
|
+
}, {
|
405
|
+
owner: account,
|
406
|
+
});
|
407
|
+
const { container } = renderWithAccount({
|
408
|
+
imageId: im.id,
|
409
|
+
alt: "test",
|
410
|
+
loading: "lazy",
|
411
|
+
});
|
412
|
+
const img = container.querySelector("img");
|
413
|
+
// simulate the load event when the browser's viewport reach the image
|
414
|
+
img.dispatchEvent(new Event("load"));
|
415
|
+
await waitFor(() => {
|
416
|
+
expect(container.querySelector("img").src).toBe("blob:test-100");
|
417
|
+
});
|
418
|
+
expect(createObjectURLSpy).toHaveBeenCalledTimes(2);
|
419
|
+
});
|
420
|
+
});
|
421
|
+
});
|
422
|
+
function createDummyBlob(size) {
|
423
|
+
const blob = new Blob([new Uint8Array(size)], { type: "image/png" });
|
424
|
+
return blob;
|
425
|
+
}
|
426
|
+
function createDummyFileStream(size, account) {
|
427
|
+
return FileStream.createFromBlob(createDummyBlob(size), {
|
428
|
+
owner: account,
|
429
|
+
});
|
430
|
+
}
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { Account, AnonymousJazzAgent } from "jazz-tools";
|
2
|
+
import { Component, ComponentProps } from "svelte";
|
3
|
+
type JazzExtendedOptions = {
|
4
|
+
account: Account | {
|
5
|
+
guest: AnonymousJazzAgent;
|
6
|
+
};
|
7
|
+
};
|
8
|
+
declare const render: <T extends Component>(component: T, props: ComponentProps<T>, jazzOptions: JazzExtendedOptions) => import("@testing-library/svelte").RenderResult<T, typeof import("@testing-library/dom/types/queries")>;
|
9
|
+
export * from "@testing-library/svelte";
|
10
|
+
export { render };
|
11
|
+
//# sourceMappingURL=testUtils.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"testUtils.d.ts","sourceRoot":"","sources":["../../../../src/svelte/tests/testUtils.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEzD,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAGnD,KAAK,mBAAmB,GAAG;IACzB,OAAO,EAAE,OAAO,GAAG;QAAE,KAAK,EAAE,kBAAkB,CAAA;KAAE,CAAC;CAClD,CAAC;AAEF,QAAA,MAAM,MAAM,GAAI,CAAC,SAAS,SAAS,aACtB,CAAC,SACL,cAAc,CAAC,CAAC,CAAC,eACX,mBAAmB,2GAejC,CAAC;AAEF,cAAc,yBAAyB,CAAC;AAExC,OAAO,EAAE,MAAM,EAAE,CAAC"}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import { render as renderSvelte } from "@testing-library/svelte";
|
2
|
+
import { TestJazzContextManager } from "jazz-tools/testing";
|
3
|
+
import { JAZZ_AUTH_CTX, JAZZ_CTX } from "../jazz.svelte";
|
4
|
+
const render = (component, props, jazzOptions) => {
|
5
|
+
const ctx = TestJazzContextManager.fromAccountOrGuest(jazzOptions.account);
|
6
|
+
return renderSvelte(
|
7
|
+
// @ts-expect-error Svelte new Component type is not compatible with @testing-library/svelte
|
8
|
+
component, {
|
9
|
+
props,
|
10
|
+
context: new Map([
|
11
|
+
[JAZZ_CTX, { current: ctx.getCurrentValue() }],
|
12
|
+
[JAZZ_AUTH_CTX, ctx.getAuthSecretStorage()],
|
13
|
+
]),
|
14
|
+
});
|
15
|
+
};
|
16
|
+
export * from "@testing-library/svelte";
|
17
|
+
export { render };
|