2020-10-15 21:31:21 +02:00
|
|
|
import { render, waitFor } from "./test-utils";
|
2020-12-05 20:00:53 +05:30
|
|
|
import ExcalidrawApp from "../excalidraw-app";
|
2020-10-15 21:31:21 +02:00
|
|
|
import { API } from "./helpers/api";
|
|
|
|
import {
|
|
|
|
encodePngMetadata,
|
|
|
|
encodeSvgMetadata,
|
|
|
|
decodeSvgMetadata,
|
|
|
|
} from "../data/image";
|
|
|
|
import { serializeAsJSON } from "../data/json";
|
2022-09-18 05:02:13 +08:00
|
|
|
import { exportToSvg } from "../scene/export";
|
|
|
|
import { FileId } from "../element/types";
|
|
|
|
import { getDataURL } from "../data/blob";
|
|
|
|
import { getDefaultAppState } from "../appState";
|
2020-10-15 21:31:21 +02:00
|
|
|
|
|
|
|
const { h } = window;
|
|
|
|
|
|
|
|
const testElements = [
|
|
|
|
{
|
|
|
|
...API.createElement({
|
|
|
|
type: "text",
|
|
|
|
id: "A",
|
|
|
|
text: "😀",
|
|
|
|
}),
|
|
|
|
// can't get jsdom text measurement to work so this is a temp hack
|
2020-11-05 19:06:18 +02:00
|
|
|
// to ensure the element isn't stripped as invisible
|
2020-10-15 21:31:21 +02:00
|
|
|
width: 16,
|
|
|
|
height: 16,
|
|
|
|
},
|
|
|
|
];
|
|
|
|
|
|
|
|
// tiny polyfill for TextDecoder.decode on which we depend
|
|
|
|
Object.defineProperty(window, "TextDecoder", {
|
|
|
|
value: class TextDecoder {
|
|
|
|
decode(ab: ArrayBuffer) {
|
|
|
|
return new Uint8Array(ab).reduce(
|
|
|
|
(acc, c) => acc + String.fromCharCode(c),
|
|
|
|
"",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2020-10-30 21:01:41 +01:00
|
|
|
describe("export", () => {
|
2020-12-05 20:00:53 +05:30
|
|
|
beforeEach(async () => {
|
|
|
|
await render(<ExcalidrawApp />);
|
2020-10-15 21:31:21 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
it("export embedded png and reimport", async () => {
|
2020-10-30 21:01:41 +01:00
|
|
|
const pngBlob = await API.loadFile("./fixtures/smiley.png");
|
2020-10-15 21:31:21 +02:00
|
|
|
const pngBlobEmbedded = await encodePngMetadata({
|
|
|
|
blob: pngBlob,
|
2021-10-21 22:05:48 +02:00
|
|
|
metadata: serializeAsJSON(testElements, h.state, {}, "local"),
|
2020-10-15 21:31:21 +02:00
|
|
|
});
|
2020-10-30 21:01:41 +01:00
|
|
|
API.drop(pngBlobEmbedded);
|
2020-10-15 21:31:21 +02:00
|
|
|
|
|
|
|
await waitFor(() => {
|
|
|
|
expect(h.elements).toEqual([
|
|
|
|
expect.objectContaining({ type: "text", text: "😀" }),
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it("test encoding/decoding scene for SVG export", async () => {
|
|
|
|
const encoded = await encodeSvgMetadata({
|
2021-10-21 22:05:48 +02:00
|
|
|
text: serializeAsJSON(testElements, h.state, {}, "local"),
|
2020-10-15 21:31:21 +02:00
|
|
|
});
|
|
|
|
const decoded = JSON.parse(await decodeSvgMetadata({ svg: encoded }));
|
|
|
|
expect(decoded.elements).toEqual([
|
|
|
|
expect.objectContaining({ type: "text", text: "😀" }),
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("import embedded png (legacy v1)", async () => {
|
2020-10-30 21:01:41 +01:00
|
|
|
API.drop(await API.loadFile("./fixtures/test_embedded_v1.png"));
|
2020-10-15 21:31:21 +02:00
|
|
|
await waitFor(() => {
|
|
|
|
expect(h.elements).toEqual([
|
|
|
|
expect.objectContaining({ type: "text", text: "test" }),
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it("import embedded png (v2)", async () => {
|
2020-10-30 21:01:41 +01:00
|
|
|
API.drop(await API.loadFile("./fixtures/smiley_embedded_v2.png"));
|
2020-10-15 21:31:21 +02:00
|
|
|
await waitFor(() => {
|
|
|
|
expect(h.elements).toEqual([
|
|
|
|
expect.objectContaining({ type: "text", text: "😀" }),
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it("import embedded svg (legacy v1)", async () => {
|
2020-10-30 21:01:41 +01:00
|
|
|
API.drop(await API.loadFile("./fixtures/test_embedded_v1.svg"));
|
2020-10-15 21:31:21 +02:00
|
|
|
await waitFor(() => {
|
|
|
|
expect(h.elements).toEqual([
|
|
|
|
expect.objectContaining({ type: "text", text: "test" }),
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it("import embedded svg (v2)", async () => {
|
2020-10-30 21:01:41 +01:00
|
|
|
API.drop(await API.loadFile("./fixtures/smiley_embedded_v2.svg"));
|
2020-10-15 21:31:21 +02:00
|
|
|
await waitFor(() => {
|
|
|
|
expect(h.elements).toEqual([
|
|
|
|
expect.objectContaining({ type: "text", text: "😀" }),
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
});
|
2022-09-18 05:02:13 +08:00
|
|
|
|
|
|
|
it("exporting svg containing transformed images", async () => {
|
|
|
|
const normalizeAngle = (angle: number) => (angle / 180) * Math.PI;
|
|
|
|
|
|
|
|
const elements = [
|
|
|
|
API.createElement({
|
|
|
|
type: "image",
|
|
|
|
fileId: "file_A",
|
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
scale: [1, 1],
|
|
|
|
width: 100,
|
|
|
|
height: 100,
|
|
|
|
angle: normalizeAngle(315),
|
|
|
|
}),
|
|
|
|
API.createElement({
|
|
|
|
type: "image",
|
|
|
|
fileId: "file_A",
|
|
|
|
x: 100,
|
|
|
|
y: 0,
|
|
|
|
scale: [-1, 1],
|
|
|
|
width: 50,
|
|
|
|
height: 50,
|
|
|
|
angle: normalizeAngle(45),
|
|
|
|
}),
|
|
|
|
API.createElement({
|
|
|
|
type: "image",
|
|
|
|
fileId: "file_A",
|
|
|
|
x: 0,
|
|
|
|
y: 100,
|
|
|
|
scale: [1, -1],
|
|
|
|
width: 100,
|
|
|
|
height: 100,
|
|
|
|
angle: normalizeAngle(45),
|
|
|
|
}),
|
|
|
|
API.createElement({
|
|
|
|
type: "image",
|
|
|
|
fileId: "file_A",
|
|
|
|
x: 100,
|
|
|
|
y: 100,
|
|
|
|
scale: [-1, -1],
|
|
|
|
width: 50,
|
|
|
|
height: 50,
|
|
|
|
angle: normalizeAngle(315),
|
|
|
|
}),
|
|
|
|
];
|
|
|
|
const appState = { ...getDefaultAppState(), exportBackground: false };
|
|
|
|
const files = {
|
|
|
|
file_A: {
|
|
|
|
id: "file_A" as FileId,
|
|
|
|
dataURL: await getDataURL(await API.loadFile("./fixtures/deer.png")),
|
|
|
|
mimeType: "image/png",
|
|
|
|
created: Date.now(),
|
2022-11-06 19:41:14 +01:00
|
|
|
lastRetrieved: Date.now(),
|
2022-09-18 05:02:13 +08:00
|
|
|
},
|
|
|
|
} as const;
|
|
|
|
|
|
|
|
const svg = await exportToSvg(elements, appState, files);
|
|
|
|
|
|
|
|
const svgText = svg.outerHTML;
|
|
|
|
|
|
|
|
// expect 1 <image> element (deduped)
|
|
|
|
expect(svgText.match(/<image/g)?.length).toBe(1);
|
|
|
|
// expect 4 <use> elements (one for each excalidraw image element)
|
|
|
|
expect(svgText.match(/<use/g)?.length).toBe(4);
|
|
|
|
|
|
|
|
// in case of regressions, save the SVG to a file and visually compare to:
|
|
|
|
// src/tests/fixtures/svg-image-exporting-reference.svg
|
|
|
|
expect(svgText).toMatchSnapshot(`svg export output`);
|
|
|
|
});
|
2020-10-15 21:31:21 +02:00
|
|
|
});
|