excalidraw/excalidraw-app/tests/collab.test.tsx
Marcel Mraz 260706c42f First step towards delta based history
Introducing independent change detection for appState and elements

Generalizing object change, cleanup, refactoring, comments, solving typing issues

Shaping increment, change, delta hierarchy

Structural clone of elements

Introducing store and incremental API

Disabling buttons for canvas actions, smaller store and changes improvements

Update history entry based on latest changes, iterate through the stack for visible changes to limit empty commands

Solving concurrency issues, solving (partly) linear element issues,  introducing commitToStore breaking change

Fixing existing tests, updating snapshots

Trying to be smarter on the appstate change detection

Extending collab test, refactoring action / updateScene params, bugfixes

Resetting snapshots

Resetting snapshots

UI / API tests for history - WIP

Changing actions related to the observed appstate to at least update the store snapshot - WIP

Adding skipping of snapshot update flag for most no-breaking changes compatible solution

Ignoring uncomitted elements from local async actions, updating store directly in updateScene

Bound element issues - WIP
2023-12-11 11:59:24 +01:00

119 lines
3.6 KiB
TypeScript

import { vi } from "vitest";
import { render, updateSceneData, waitFor } from "../../src/tests/test-utils";
import ExcalidrawApp from "../../excalidraw-app";
import { API } from "../../src/tests/helpers/api";
import { createUndoAction } from "../../src/actions/actionHistory";
const { h } = window;
Object.defineProperty(window, "crypto", {
value: {
getRandomValues: (arr: number[]) =>
arr.forEach((v, i) => (arr[i] = Math.floor(Math.random() * 256))),
subtle: {
generateKey: () => {},
exportKey: () => ({ k: "sTdLvMC_M3V8_vGa3UVRDg" }),
},
},
});
vi.mock("../../excalidraw-app/data/index.ts", async (importActual) => {
const module = (await importActual()) as any;
return {
__esmodule: true,
...module,
getCollabServer: vi.fn(() => ({
url: /* doesn't really matter */ "http://localhost:3002",
})),
};
});
vi.mock("../../excalidraw-app/data/firebase.ts", () => {
const loadFromFirebase = async () => null;
const saveToFirebase = () => {};
const isSavedToFirebase = () => true;
const loadFilesFromFirebase = async () => ({
loadedFiles: [],
erroredFiles: [],
});
const saveFilesToFirebase = async () => ({
savedFiles: new Map(),
erroredFiles: new Map(),
});
return {
loadFromFirebase,
saveToFirebase,
isSavedToFirebase,
loadFilesFromFirebase,
saveFilesToFirebase,
};
});
vi.mock("socket.io-client", () => {
return {
default: () => {
return {
close: () => {},
on: () => {},
once: () => {},
off: () => {},
emit: () => {},
};
},
};
});
describe("collaboration", () => {
it("creating room should reset deleted elements while keeping store snapshot in sync", async () => {
await render(<ExcalidrawApp />);
// To update the scene with deleted elements before starting collab
updateSceneData({
elements: [
API.createElement({ type: "rectangle", id: "A" }),
API.createElement({
type: "rectangle",
id: "B",
isDeleted: true,
}),
],
commitToStore: true,
});
await waitFor(() => {
expect(API.getUndoStack().length).toBe(1);
expect(h.elements).toEqual([
expect.objectContaining({ id: "A" }),
expect.objectContaining({ id: "B", isDeleted: true }),
]);
expect(Array.from(h.store.snapshot.elements.values())).toEqual([
expect.objectContaining({ id: "A" }),
expect.objectContaining({ id: "B", isDeleted: true }),
]);
});
window.collab.startCollaboration(null);
await waitFor(() => {
expect(API.getUndoStack().length).toBe(1);
expect(h.elements).toEqual([expect.objectContaining({ id: "A" })]);
// We never delete from the local store as it is used for correct diff calculation
expect(Array.from(h.store.snapshot.elements.values())).toEqual([
expect.objectContaining({ id: "A" }),
expect.objectContaining({ id: "B", isDeleted: true }),
]);
});
const undoAction = createUndoAction(h.history);
// noop
h.app.actionManager.executeAction(undoAction);
// As it was introduced #2270, undo is a noop here, but we might want to re-enable it,
// since inability to undo your own deletions could be a bigger upsetting factor here
await waitFor(() => {
expect(h.history.isUndoStackEmpty).toBeTruthy();
expect(h.elements).toEqual([expect.objectContaining({ id: "A" })]);
expect(Array.from(h.store.snapshot.elements.values())).toEqual([
expect.objectContaining({ id: "A" }),
expect.objectContaining({ id: "B", isDeleted: true }),
]);
});
});
});