excalidraw/src/tests/test-utils.ts
Aakansha Doshi 8420aecb34
feat: new Menu Component API (#6034)
* feat: new Menu Component API

* allow valid children types

* introduce menu group to group items

* Add lang footer

* use display name

* displayName

* define types inside

* fix default menu

* add json export to menu

* fix

* simplify expression

* put open menu into own compo to optimize perf

So that we don't rerun `useOutsideClickHook` (and rebind event listeners
all the time)

* naming tweaks

* rename MenuComponents->MenuDefaultItems and export default items from Menu.Items

* import Menu.scss in Menu.tsx

* move menu scss to excal app

* Don't filter children inside menu group

* move E+ out of socials

* support style prop for MenuItem and MenuGroup

* Support header in menu group and add Excalidraw links header for default items in social section

* rename header to title

* fix padding for lang

* render menu in mobile

* review fixes

* tweaks

* Export collaborators and show in mobile menu

* revert .env

* lint :p

* again lint

* show correct actions in view mode for mobile

* Whitelist Collaborators Comp

* mobile styling

* padding

* don't show nerds when menu open in mobile

* lint :(

* hide shortcuts

* refactor userlist to support mobile and keep a wrapper comp for excal app

* use only UserList

* render only on mobile for default items

* remove unused hooks

* Show collab button in menu when onCollabButtonClick present and hide export when UIOptions.canvasActions.export is false

* fix tests

* lint

* inject userlist inside menu on mobile

* revert userlist

* move menu socials to default menu

* fix collab

* use meny in library

* Make Menu generic and create hamburgemenu for public excal menu and use menu in library as well

* use appState.openMenu for mobile

* fix tests

* styling fixes and support style and class name in menu content

* fix test

* rename MenuDefaultItems->DefaultItems

* move footer css to its own comp

* rename HamburgerMenu -> MainMenu

* rename menu -> dropdownMenu and update classes, onClick->onToggle

* close main menu when dialog closes

* by bye filtering

* update docs

* fix lint

* update example, docs for useDevice and footer in mobile, rename menu ->DropDownMenu everywhere

* spec

* remove isMenuOpenAtom and set openMenu as canvas for main menu, render decreases in specs :)

* [temp] remove cyclic depenedency to fix build

* hack- update appstate to sync lang change

* Add more specs

* wip: rewrite MainMenu footer

* fix margin

* fix snaps

* not needed as lang list no more imported

* simplify custom footer rendering

* Add DropdownMenuItemLink and DropdownMenuItemCustom and update API, docs

* fix `MainMenu.ItemCustom`

* naming

* use onSelect and base class for custom items

* fix lint

* fix snap

* use custom item for lang

* update docs

* fix

* properly use `MainMenu.ItemCustom` for `LanguageList`

* add margin top to custom items

* flex

Co-authored-by: dwelle <luzar.david@gmail.com>
2023-01-05 22:04:23 +05:30

193 lines
4.6 KiB
TypeScript

import "pepjs";
import {
render,
queries,
RenderResult,
RenderOptions,
waitFor,
fireEvent,
} from "@testing-library/react";
import * as toolQueries from "./queries/toolQueries";
import { ImportedDataState } from "../data/types";
import { STORAGE_KEYS } from "../excalidraw-app/app_constants";
import { SceneData } from "../types";
import { getSelectedElements } from "../scene/selection";
import { ExcalidrawElement } from "../element/types";
const customQueries = {
...queries,
...toolQueries,
};
type TestRenderFn = (
ui: React.ReactElement,
options?: Omit<
RenderOptions & { localStorageData?: ImportedDataState },
"queries"
>,
) => Promise<RenderResult<typeof customQueries>>;
const renderApp: TestRenderFn = async (ui, options) => {
if (options?.localStorageData) {
initLocalStorage(options.localStorageData);
delete options.localStorageData;
}
const renderResult = render(ui, {
queries: customQueries,
...options,
});
GlobalTestState.renderResult = renderResult;
Object.defineProperty(GlobalTestState, "canvas", {
// must be a getter because at the time of ExcalidrawApp render the
// child App component isn't likely mounted yet (and thus canvas not
// present in DOM)
get() {
return renderResult.container.querySelector("canvas")!;
},
});
await waitFor(() => {
const canvas = renderResult.container.querySelector("canvas");
if (!canvas) {
throw new Error("not initialized yet");
}
});
return renderResult;
};
// re-export everything
export * from "@testing-library/react";
// override render method
export { renderApp as render };
/**
* For state-sharing across test helpers.
* NOTE: there shouldn't be concurrency issues as each test is running in its
* own process and thus gets its own instance of this module when running
* tests in parallel.
*/
export class GlobalTestState {
/**
* automatically updated on each call to render()
*/
static renderResult: RenderResult<typeof customQueries> = null!;
/**
* retrieves canvas for currently rendered app instance
*/
static get canvas(): HTMLCanvasElement {
return null!;
}
}
const initLocalStorage = (data: ImportedDataState) => {
if (data.elements) {
localStorage.setItem(
STORAGE_KEYS.LOCAL_STORAGE_ELEMENTS,
JSON.stringify(data.elements),
);
}
if (data.appState) {
localStorage.setItem(
STORAGE_KEYS.LOCAL_STORAGE_APP_STATE,
JSON.stringify(data.appState),
);
}
};
export const updateSceneData = (data: SceneData) => {
(window.collab as any).excalidrawAPI.updateScene(data);
};
const originalGetBoundingClientRect =
global.window.HTMLDivElement.prototype.getBoundingClientRect;
export const mockBoundingClientRect = (
{
top = 0,
left = 0,
bottom = 0,
right = 0,
width = 1920,
height = 1080,
x = 0,
y = 0,
toJSON = () => {},
} = {
top: 10,
left: 20,
bottom: 10,
right: 10,
width: 200,
x: 10,
y: 20,
height: 100,
},
) => {
// override getBoundingClientRect as by default it will always return all values as 0 even if customized in html
global.window.HTMLDivElement.prototype.getBoundingClientRect = () => ({
top,
left,
bottom,
right,
width,
height,
x,
y,
toJSON,
});
};
export const withExcalidrawDimensions = async (
dimensions: { width: number; height: number },
cb: () => void,
) => {
mockBoundingClientRect(dimensions);
// @ts-ignore
window.h.app.refreshDeviceState(h.app.excalidrawContainerRef.current!);
window.h.app.refresh();
await cb();
restoreOriginalGetBoundingClientRect();
// @ts-ignore
window.h.app.refreshDeviceState(h.app.excalidrawContainerRef.current!);
window.h.app.refresh();
};
export const restoreOriginalGetBoundingClientRect = () => {
global.window.HTMLDivElement.prototype.getBoundingClientRect =
originalGetBoundingClientRect;
};
export const assertSelectedElements = (
...elements: (
| (ExcalidrawElement["id"] | ExcalidrawElement)[]
| ExcalidrawElement["id"]
| ExcalidrawElement
)[]
) => {
const { h } = window;
const selectedElementIds = getSelectedElements(
h.app.getSceneElements(),
h.state,
).map((el) => el.id);
const ids = elements
.flat()
.map((item) => (typeof item === "string" ? item : item.id));
expect(selectedElementIds.length).toBe(ids.length);
expect(selectedElementIds).toEqual(expect.arrayContaining(ids));
};
export const toggleMenu = (container: HTMLElement) => {
// open menu
fireEvent.click(container.querySelector(".dropdown-menu-button")!);
};