
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
119 lines
3.6 KiB
TypeScript
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 }),
|
|
]);
|
|
});
|
|
});
|
|
});
|