,
7 | |};
8 |
--------------------------------------------------------------------------------
/stories/static/media/bmo-min.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/atlassian/react-beautiful-dnd/b833d66c437b6a96c13591bcc9f7b3f4d2a6170e/stories/static/media/bmo-min.png
--------------------------------------------------------------------------------
/stories/static/media/bmo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/atlassian/react-beautiful-dnd/b833d66c437b6a96c13591bcc9f7b3f4d2a6170e/stories/static/media/bmo.png
--------------------------------------------------------------------------------
/stories/static/media/finn-min.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/atlassian/react-beautiful-dnd/b833d66c437b6a96c13591bcc9f7b3f4d2a6170e/stories/static/media/finn-min.png
--------------------------------------------------------------------------------
/stories/static/media/finn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/atlassian/react-beautiful-dnd/b833d66c437b6a96c13591bcc9f7b3f4d2a6170e/stories/static/media/finn.png
--------------------------------------------------------------------------------
/stories/static/media/jake-min.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/atlassian/react-beautiful-dnd/b833d66c437b6a96c13591bcc9f7b3f4d2a6170e/stories/static/media/jake-min.png
--------------------------------------------------------------------------------
/stories/static/media/jake.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/atlassian/react-beautiful-dnd/b833d66c437b6a96c13591bcc9f7b3f4d2a6170e/stories/static/media/jake.png
--------------------------------------------------------------------------------
/stories/static/media/princess-min.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/atlassian/react-beautiful-dnd/b833d66c437b6a96c13591bcc9f7b3f4d2a6170e/stories/static/media/princess-min.png
--------------------------------------------------------------------------------
/stories/static/media/princess.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/atlassian/react-beautiful-dnd/b833d66c437b6a96c13591bcc9f7b3f4d2a6170e/stories/static/media/princess.png
--------------------------------------------------------------------------------
/test-reports/lighthouse/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/atlassian/react-beautiful-dnd/b833d66c437b6a96c13591bcc9f7b3f4d2a6170e/test-reports/lighthouse/.gitkeep
--------------------------------------------------------------------------------
/test/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | rules: {
3 | // allowing console.warn / console.error
4 | // this is because we often mock console.warn and console.error and adding this rul
5 | // avoids needing to constantly be opting out of the rule
6 | 'no-console': ['error', { allow: ['warn', 'error'] }],
7 |
8 | // allowing useMemo and useCallback in tests
9 | 'no-restricted-imports': 'off',
10 |
11 | // Allowing Array.from
12 | 'no-restricted-syntax': 'off',
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/test/test-setup.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | // ensuring that each test has at least one assertion
4 | beforeEach(expect.hasAssertions);
5 |
6 | if (typeof document !== 'undefined') {
7 | // Simply importing this package will throw an error if document is not defined
8 | // eslint-disable-next-line global-require
9 | const { cleanup, fireEvent } = require('@testing-library/react');
10 |
11 | // unmount any components mounted with react-testing-library
12 | beforeAll(cleanup);
13 | afterEach(() => {
14 | cleanup();
15 | // lots of tests can leave a post-drop click blocker
16 | // this cleans it up before every test
17 | fireEvent.click(window);
18 |
19 | // Cleaning up any mocks
20 |
21 | if (window.getComputedStyle.mockRestore) {
22 | window.getComputedStyle.mockRestore();
23 | }
24 |
25 | if (Element.prototype.getBoundingClientRect.mockRestore) {
26 | Element.prototype.getBoundingClientRect.mockRestore();
27 | }
28 | });
29 | }
30 |
--------------------------------------------------------------------------------
/test/unit/dev-warning.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { warning } from '../../src/dev-warning';
3 |
4 | const warn = jest.spyOn(console, 'warn').mockImplementation(() => {});
5 |
6 | afterEach(() => {
7 | warn.mockClear();
8 | });
9 |
10 | it('should log a warning to the console', () => {
11 | warning('hey');
12 |
13 | expect(warn).toHaveBeenCalled();
14 | });
15 |
16 | it('should not log a warning if warnings are disabled', () => {
17 | window['__react-beautiful-dnd-disable-dev-warnings'] = true;
18 |
19 | warning('hey');
20 | warning('sup');
21 | warning('hi');
22 |
23 | expect(warn).not.toHaveBeenCalled();
24 |
25 | // re-enable
26 |
27 | window['__react-beautiful-dnd-disable-dev-warnings'] = false;
28 |
29 | warning('hey');
30 |
31 | expect(warn).toHaveBeenCalled();
32 | });
33 |
--------------------------------------------------------------------------------
/test/unit/health/src-file-name-convention.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import globby from 'globby';
3 | import { invariant } from '../../../src/invariant';
4 | import pkg from '../../../package.json';
5 |
6 | // Regex playground: https://regexr.com/40fin
7 | const convention: RegExp = /^[a-z0-9\-./]+$/;
8 | const isSnakeCase = (filePath: string): boolean => convention.test(filePath);
9 |
10 | const exceptions: string[] = [
11 | 'CHANGELOG.md',
12 | 'CODE_OF_CONDUCT.md',
13 | 'CONTRIBUTING.md',
14 | 'ISSUE_TEMPLATE.md',
15 | 'README.md',
16 | ];
17 |
18 | it('should have every prettier target following the file name convention', async () => {
19 | const targets: string[] = pkg.config.prettier_target.split(' ');
20 | const paths: string[] = await globby(targets);
21 |
22 | invariant(
23 | paths.length,
24 | 'Could not find files to test against file name convention',
25 | );
26 |
27 | paths.forEach((filePath: string) => {
28 | if (exceptions.includes(filePath)) {
29 | return;
30 | }
31 |
32 | const isMatching: boolean = isSnakeCase(filePath);
33 |
34 | invariant(
35 | isMatching,
36 | `${filePath} does not follow the file path convention (snake-case.js) ${convention.toString()}`,
37 | );
38 |
39 | expect(isMatching).toBe(true);
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/test/unit/integration/accessibility/axe-audit.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import { render, cleanup } from '@testing-library/react';
4 | import { axe, toHaveNoViolations } from 'jest-axe';
5 |
6 | import App from '../util/app';
7 |
8 | expect.extend(toHaveNoViolations);
9 |
10 | // Need to investigate this further as it doesn't seem to pick up the same errors
11 | // which are found by react-axe.
12 | it('should not fail an aXe audit', async () => {
13 | render();
14 |
15 | const results = await axe(document.body);
16 |
17 | // $FlowFixMe - flow doesn't know about hte custom validator
18 | expect(results).toHaveNoViolations();
19 |
20 | cleanup();
21 | });
22 |
--------------------------------------------------------------------------------
/test/unit/integration/body-removal-before-unmount.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import { render } from '@testing-library/react';
4 | import { isDragging } from './util/helpers';
5 | import App from './util/app';
6 | import { forEachSensor, simpleLift, type Control } from './util/controls';
7 | import getBodyElement from '../../../src/view/get-body-element';
8 |
9 | it('should have any errors when body is changed just before unmount', () => {
10 | jest.useFakeTimers();
11 | const { unmount } = render();
12 |
13 | expect(() => {
14 | getBodyElement().innerHTML = '';
15 | unmount();
16 | jest.runOnlyPendingTimers();
17 | }).not.toThrow();
18 |
19 | jest.useRealTimers();
20 | });
21 |
22 | forEachSensor((control: Control) => {
23 | it('should have any errors when body is changed just before unmount: mid drag', () => {
24 | const { unmount, getByText } = render();
25 | const handle: HTMLElement = getByText('item: 0');
26 |
27 | // mid drag
28 | simpleLift(control, handle);
29 | expect(isDragging(handle)).toEqual(true);
30 |
31 | expect(() => {
32 | getBodyElement().innerHTML = '';
33 | unmount();
34 | jest.runOnlyPendingTimers();
35 | }).not.toThrow();
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/test/unit/integration/drag-drop-context/check-doctype.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { JSDOM } from 'jsdom';
3 | import checkDoctype from '../../../../src/view/drag-drop-context/check-doctype';
4 |
5 | const warn = jest.spyOn(console, 'warn').mockImplementation(() => {});
6 |
7 | afterEach(() => {
8 | warn.mockClear();
9 | });
10 |
11 | it('should pass if using a html doctype', () => {
12 | const jsdom = new JSDOM(`Hello world
`);
13 |
14 | checkDoctype(jsdom.window.document);
15 |
16 | expect(warn).not.toHaveBeenCalled();
17 | });
18 |
19 | it('should fail if there is no doctype', () => {
20 | const jsdom = new JSDOM(`Hello world`);
21 |
22 | checkDoctype(jsdom.window.document);
23 |
24 | expect(warn).toHaveBeenCalled();
25 | });
26 |
27 | it('should fail if there is a non-html5 doctype', () => {
28 | // HTML 4.01 Strict
29 | const jsdom = new JSDOM(
30 | `Hello world`,
31 | );
32 |
33 | checkDoctype(jsdom.window.document);
34 |
35 | expect(warn).toHaveBeenCalled();
36 | });
37 |
--------------------------------------------------------------------------------
/test/unit/integration/drag-drop-context/unmount.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import { render } from '@testing-library/react';
4 | import DragDropContext from '../../../../src/view/drag-drop-context';
5 |
6 | it('should not throw when unmounting', () => {
7 | const { unmount } = render(
8 | {}}>{null},
9 | );
10 |
11 | expect(() => unmount()).not.toThrow();
12 | });
13 |
14 | it('should clean up any window event handlers', () => {
15 | jest.spyOn(window, 'addEventListener');
16 | jest.spyOn(window, 'removeEventListener');
17 |
18 | const { unmount } = render(
19 | {}}>{null},
20 | );
21 |
22 | unmount();
23 |
24 | expect(window.addEventListener.mock.calls).toHaveLength(
25 | window.removeEventListener.mock.calls.length,
26 | );
27 | // validation
28 | expect(window.addEventListener).toHaveBeenCalled();
29 | expect(window.removeEventListener).toHaveBeenCalled();
30 | });
31 |
--------------------------------------------------------------------------------
/test/unit/integration/drag-handle/keyboard-sensor/no-click-blocking.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import { render, createEvent, fireEvent } from '@testing-library/react';
4 | import App from '../../util/app';
5 | import { simpleLift, keyboard } from '../../util/controls';
6 |
7 | jest.useFakeTimers();
8 |
9 | it('should not prevent clicks after a drag', () => {
10 | // clearing any pending listeners that have leaked from other tests
11 | fireEvent.click(window);
12 |
13 | const onDragStart = jest.fn();
14 | const onDragEnd = jest.fn();
15 | const { getByText } = render(
16 | ,
17 | );
18 | const handle: HTMLElement = getByText('item: 0');
19 |
20 | simpleLift(keyboard, handle);
21 |
22 | // flush start timer
23 | jest.runOnlyPendingTimers();
24 | expect(onDragStart).toHaveBeenCalled();
25 | keyboard.drop(handle);
26 |
27 | const event: Event = createEvent.click(handle);
28 | fireEvent(handle, event);
29 |
30 | // click not blocked
31 | expect(event.defaultPrevented).toBe(false);
32 | expect(onDragEnd).toHaveBeenCalled();
33 | });
34 |
--------------------------------------------------------------------------------
/test/unit/integration/drag-handle/keyboard-sensor/prevent-keyboard-scroll.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import { createEvent, fireEvent, render } from '@testing-library/react';
4 | import * as keyCodes from '../../../../../src/view/key-codes';
5 | import App from '../../util/app';
6 | import { simpleLift, keyboard } from '../../util/controls';
7 | import { isDragging } from '../../util/helpers';
8 |
9 | it('should prevent using keyboard keys that modify scroll', () => {
10 | const keys: number[] = [
11 | keyCodes.pageUp,
12 | keyCodes.pageDown,
13 | keyCodes.home,
14 | keyCodes.end,
15 | ];
16 | const { getByText } = render();
17 | const handle: HTMLElement = getByText('item: 0');
18 |
19 | simpleLift(keyboard, handle);
20 |
21 | keys.forEach((keyCode: number) => {
22 | const event: Event = createEvent.keyDown(handle, { keyCode });
23 | fireEvent(handle, event);
24 |
25 | expect(event.defaultPrevented).toBe(true);
26 | expect(isDragging(handle)).toBe(true);
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/test/unit/integration/drag-handle/keyboard-sensor/prevent-standard-keys-while-dragging.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import { createEvent, fireEvent, render } from '@testing-library/react';
4 | import * as keyCodes from '../../../../../src/view/key-codes';
5 | import App from '../../util/app';
6 | import { isDragging } from '../../util/helpers';
7 | import { simpleLift, keyboard } from '../../util/controls';
8 |
9 | it('should prevent enter or tab being pressed during a drag', () => {
10 | const { getByText } = render();
11 | const handle: HTMLElement = getByText('item: 0');
12 |
13 | simpleLift(keyboard, handle);
14 | expect(isDragging(handle)).toBe(true);
15 |
16 | [keyCodes.enter, keyCodes.tab].forEach((keyCode: number) => {
17 | const event: Event = createEvent.keyDown(handle, { keyCode });
18 | fireEvent(handle, event);
19 | expect(event.defaultPrevented).toBe(true);
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/test/unit/integration/drag-handle/keyboard-sensor/starting-a-drag.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import { render, createEvent, fireEvent } from '@testing-library/react';
4 | import App from '../../util/app';
5 | import { isDragging } from '../../util/helpers';
6 | import * as keyCodes from '../../../../../src/view/key-codes';
7 |
8 | it('should prevent the default keyboard action when lifting', () => {
9 | const { getByText } = render();
10 | const handle: HTMLElement = getByText('item: 0');
11 |
12 | const event: Event = createEvent.keyDown(handle, { keyCode: keyCodes.space });
13 | fireEvent(handle, event);
14 |
15 | expect(isDragging(handle)).toBe(true);
16 | expect(event.defaultPrevented).toBe(true);
17 | });
18 |
--------------------------------------------------------------------------------
/test/unit/integration/drag-handle/mouse-sensor/prevent-standard-keys-while-dragging.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import { createEvent, fireEvent, render } from '@testing-library/react';
4 | import * as keyCodes from '../../../../../src/view/key-codes';
5 | import App from '../../util/app';
6 | import { isDragging } from '../../util/helpers';
7 | import { simpleLift, mouse } from '../../util/controls';
8 |
9 | it('should prevent enter or tab being pressed during a drag', () => {
10 | const { getByText } = render();
11 | const handle: HTMLElement = getByText('item: 0');
12 |
13 | simpleLift(mouse, handle);
14 | expect(isDragging(handle)).toBe(true);
15 |
16 | [keyCodes.enter, keyCodes.tab].forEach((keyCode: number) => {
17 | const event: Event = createEvent.keyDown(handle, { keyCode });
18 | fireEvent(handle, event);
19 | expect(event.defaultPrevented).toBe(true);
20 | expect(isDragging(handle)).toBe(true);
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/test/unit/integration/drag-handle/sensor-marshal/force-releasing-locks.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import { render } from '@testing-library/react';
4 | import { invariant } from '../../../../../src/invariant';
5 | import type {
6 | SensorAPI,
7 | Sensor,
8 | PreDragActions,
9 | } from '../../../../../src/types';
10 | import App from '../../util/app';
11 |
12 | it('should correctly state whether a lock is claimed', () => {
13 | let first: SensorAPI;
14 | let second: SensorAPI;
15 | const a: Sensor = (value: SensorAPI) => {
16 | first = value;
17 | };
18 | const b: Sensor = (value: SensorAPI) => {
19 | second = value;
20 | };
21 | const onForceStop = jest.fn();
22 |
23 | render(
24 |
25 |
26 | ,
27 | );
28 | invariant(first);
29 | invariant(second);
30 |
31 | const preDrag: ?PreDragActions = first.tryGetLock('0', onForceStop);
32 | expect(preDrag).toBeTruthy();
33 | expect(second.isLockClaimed()).toBe(true);
34 |
35 | second.tryReleaseLock();
36 | expect(onForceStop).toHaveBeenCalled();
37 | // lock is gone
38 | expect(second.isLockClaimed()).toBe(false);
39 | });
40 |
--------------------------------------------------------------------------------
/test/unit/integration/drag-handle/sensor-marshal/is-lock-claimed.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import { render } from '@testing-library/react';
4 | import { invariant } from '../../../../../src/invariant';
5 | import type {
6 | SensorAPI,
7 | Sensor,
8 | PreDragActions,
9 | } from '../../../../../src/types';
10 | import App from '../../util/app';
11 |
12 | it('should correctly state whether a lock is claimed', () => {
13 | let first: SensorAPI;
14 | let second: SensorAPI;
15 | const a: Sensor = (value: SensorAPI) => {
16 | first = value;
17 | };
18 | const b: Sensor = (value: SensorAPI) => {
19 | second = value;
20 | };
21 |
22 | render(
23 |
24 |
25 | ,
26 | );
27 | invariant(first && second);
28 |
29 | // both sensors know that the lock is not claimed
30 | expect(first.isLockClaimed()).toBe(false);
31 | expect(second.isLockClaimed()).toBe(false);
32 |
33 | const preDrag: ?PreDragActions = first.tryGetLock('0');
34 | expect(preDrag).toBeTruthy();
35 |
36 | // both sensors can know if the lock is claimed
37 | expect(first.isLockClaimed()).toBe(true);
38 | expect(second.isLockClaimed()).toBe(true);
39 | });
40 |
--------------------------------------------------------------------------------
/test/unit/integration/drag-handle/sensor-marshal/lock-context-isolation.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import { render } from '@testing-library/react';
4 | import { invariant } from '../../../../../src/invariant';
5 | import type { SensorAPI, Sensor } from '../../../../../src/types';
6 | import App from '../../util/app';
7 |
8 | it('should allow different locks in different DragDropContexts', () => {
9 | let first: SensorAPI;
10 | let second: SensorAPI;
11 |
12 | const a: Sensor = (value: SensorAPI) => {
13 | first = value;
14 | };
15 | const b: Sensor = (value: SensorAPI) => {
16 | second = value;
17 | };
18 |
19 | const { getAllByText } = render(
20 |
21 |
22 |
23 | ,
24 | );
25 |
26 | const items: HTMLElement[] = getAllByText('item: 0');
27 | expect(items).toHaveLength(2);
28 | const [inFirst, inSecond] = items;
29 | expect(inFirst).not.toBe(inSecond);
30 |
31 | // each sensor can get a different lock
32 | invariant(first, 'expected first to be set');
33 | invariant(second, 'expected second to be set');
34 | expect(first.tryGetLock('0')).toBeTruthy();
35 | expect(second.tryGetLock('0')).toBeTruthy();
36 | });
37 |
--------------------------------------------------------------------------------
/test/unit/integration/drag-handle/sensor-marshal/no-double-lift.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import { render } from '@testing-library/react';
4 | import { invariant } from '../../../../../src/invariant';
5 | import type {
6 | SensorAPI,
7 | PreDragActions,
8 | SnapDragActions,
9 | Sensor,
10 | } from '../../../../../src/types';
11 | import App from '../../util/app';
12 |
13 | it('should not allow double lifting', () => {
14 | let api: SensorAPI;
15 | const a: Sensor = (value: SensorAPI) => {
16 | api = value;
17 | };
18 | render();
19 | invariant(api, 'expected first to be set');
20 |
21 | const preDrag: ?PreDragActions = api.tryGetLock('0');
22 | invariant(preDrag);
23 | // it is currently active
24 | expect(preDrag.isActive()).toBe(true);
25 |
26 | const drag: SnapDragActions = preDrag.snapLift();
27 |
28 | expect(() => preDrag.fluidLift({ x: 0, y: 0 })).toThrow();
29 | // original lock is gone
30 | expect(drag.isActive()).toBe(false);
31 |
32 | // yolo
33 | expect(() => preDrag.snapLift()).toThrow();
34 | });
35 |
--------------------------------------------------------------------------------
/test/unit/integration/drag-handle/shared-behaviours/cannot-start-when-disabled.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import { render } from '@testing-library/react';
4 | import { isDragging } from '../../util/helpers';
5 | import App, { type Item } from '../../util/app';
6 | import { forEachSensor, type Control, simpleLift } from '../../util/controls';
7 |
8 | forEachSensor((control: Control) => {
9 | it('should not start a drag if disabled', () => {
10 | const items: Item[] = [{ id: '0', isEnabled: false }];
11 |
12 | const { getByText } = render();
13 | const handle: HTMLElement = getByText('item: 0');
14 |
15 | simpleLift(control, handle);
16 |
17 | expect(isDragging(handle)).toBe(false);
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/test/unit/integration/drag-handle/shared-behaviours/cannot-start-when-something-else-has-lock.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import { render } from '@testing-library/react';
4 | import { invariant } from '../../../../../src/invariant';
5 | import { isDragging } from '../../util/helpers';
6 | import App from '../../util/app';
7 | import type { SensorAPI } from '../../../../../src/types';
8 | import { forEachSensor, type Control, simpleLift } from '../../util/controls';
9 |
10 | forEachSensor((control: Control) => {
11 | it('should not start a drag if another sensor is capturing', () => {
12 | let api: SensorAPI;
13 | function greedy(value: SensorAPI) {
14 | api = value;
15 | }
16 | const { getByText } = render();
17 | const handle: HTMLElement = getByText('item: 0');
18 |
19 | invariant(api, 'Expected function to be set');
20 | api.tryGetLock('0');
21 |
22 | // won't be able to lift as the lock is already claimed
23 | simpleLift(control, handle);
24 |
25 | expect(isDragging(handle)).toBe(false);
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/test/unit/integration/drag-handle/shared-behaviours/cannot-start-when-unmounted.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import { render } from '@testing-library/react';
4 | import { isDragging } from '../../util/helpers';
5 | import App from '../../util/app';
6 | import { forEachSensor, type Control, simpleLift } from '../../util/controls';
7 |
8 | forEachSensor((control: Control) => {
9 | it('should not allow starting after the handle is unmounted', () => {
10 | const { getByText, unmount } = render();
11 | const handle: HTMLElement = getByText('item: 0');
12 |
13 | unmount();
14 |
15 | simpleLift(control, handle);
16 |
17 | expect(isDragging(handle)).toBe(false);
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/test/unit/integration/drag-handle/shared-behaviours/disable-default-sensors.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import { render } from '@testing-library/react';
4 | import { isDragging } from '../../util/helpers';
5 | import App from '../../util/app';
6 | import { forEachSensor, type Control, simpleLift } from '../../util/controls';
7 |
8 | forEachSensor((control: Control) => {
9 | it('should be able to start a drag if default sensors is disabled', () => {
10 | const { getByText } = render();
11 | const handle: HTMLElement = getByText('item: 0');
12 |
13 | simpleLift(control, handle);
14 | expect(isDragging(handle)).toBe(false);
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/test/unit/integration/drag-handle/shared-behaviours/nested-handles.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import { render } from '@testing-library/react';
4 | import { forEachSensor, type Control, simpleLift } from '../../util/controls';
5 | import { isDragging } from '../../util/helpers';
6 | import Board from '../../util/board';
7 |
8 | forEachSensor((control: Control) => {
9 | it('should not start a drag on a parent if a child drag handle has already received the event', () => {
10 | const { getByTestId } = render();
11 | const cardHandle: HTMLElement = getByTestId('inhome1');
12 | const columnHandle: HTMLElement = getByTestId('home');
13 |
14 | simpleLift(control, cardHandle);
15 |
16 | expect(isDragging(cardHandle)).toBe(true);
17 | expect(isDragging(columnHandle)).toBe(false);
18 | });
19 | it('should start a drag on a pare~nt the event is trigged on the parent', () => {
20 | const { getByTestId } = render();
21 | const cardHandle: HTMLElement = getByTestId('inhome1');
22 | const columnHandle: HTMLElement = getByTestId('home');
23 |
24 | simpleLift(control, columnHandle);
25 |
26 | expect(isDragging(columnHandle)).toBe(true);
27 | expect(isDragging(cardHandle)).toBe(false);
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/test/unit/integration/drag-handle/shared-behaviours/parent-rendering-should-not-kill-drag.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import { render } from '@testing-library/react';
4 | import { isDragging } from '../../util/helpers';
5 | import App from '../../util/app';
6 | import { forEachSensor, simpleLift, type Control } from '../../util/controls';
7 |
8 | forEachSensor((control: Control) => {
9 | it('should not abort a drag if a parent render occurs', () => {
10 | const { getByText, rerender } = render();
11 | const handle: HTMLElement = getByText('item: 0');
12 |
13 | simpleLift(control, handle);
14 | expect(isDragging(handle)).toBe(true);
15 |
16 | rerender();
17 |
18 | // handle element is unchanged
19 | expect(getByText('item: 0')).toBe(handle);
20 | // it is still dragging
21 | expect(isDragging(handle)).toBe(true);
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/test/unit/integration/drag-handle/touch-sensor/click-blocking.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import { fireEvent, render, createEvent } from '@testing-library/react';
4 | import { act } from 'react-dom/test-utils';
5 | import App from '../../util/app';
6 | import { touch, simpleLift } from '../../util/controls';
7 |
8 | jest.useFakeTimers();
9 |
10 | it('should block a click after a drag', () => {
11 | const { getByText } = render();
12 | const handle: HTMLElement = getByText('item: 0');
13 |
14 | simpleLift(touch, handle);
15 | act(() => touch.drop(handle));
16 |
17 | const click: Event = createEvent.click(handle);
18 | fireEvent(handle, click);
19 |
20 | expect(click.defaultPrevented).toBe(true);
21 | });
22 |
23 | it('should not block a click after an aborted pending drag', () => {
24 | const onDragStart = jest.fn();
25 | const { getByText } = render();
26 | const handle: HTMLElement = getByText('item: 0');
27 |
28 | // aborted before getting to a drag
29 | touch.preLift(handle);
30 | act(() => touch.cancel(handle));
31 |
32 | const click: Event = createEvent.click(handle);
33 | fireEvent(handle, click);
34 |
35 | expect(click.defaultPrevented).toBe(false);
36 | expect(onDragStart).not.toHaveBeenCalled();
37 | });
38 |
--------------------------------------------------------------------------------
/test/unit/integration/drag-handle/touch-sensor/context-menu-opt-out.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import { fireEvent, render } from '@testing-library/react';
4 | import App from '../../util/app';
5 | import { touch } from '../../util/controls';
6 | import { isDragging } from '../../util/helpers';
7 |
8 | jest.useFakeTimers();
9 |
10 | it('should opt of a context menu', () => {
11 | const { getByText } = render();
12 | const handle: HTMLElement = getByText('item: 0');
13 |
14 | touch.preLift(handle);
15 |
16 | // prevented during a pending drag
17 | const first: Event = new Event('contextmenu', {
18 | bubbles: true,
19 | cancelable: true,
20 | });
21 | fireEvent(handle, first);
22 | expect(first.defaultPrevented).toBe(true);
23 |
24 | touch.lift(handle);
25 |
26 | // prevented during a drag
27 | const second: Event = new Event('contextmenu', {
28 | bubbles: true,
29 | cancelable: true,
30 | });
31 | fireEvent(handle, second);
32 | expect(second.defaultPrevented).toBe(true);
33 |
34 | expect(isDragging(handle)).toBe(true);
35 | });
36 |
--------------------------------------------------------------------------------
/test/unit/integration/drag-handle/touch-sensor/unmounted-while-pending-timer-running.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import { render } from '@testing-library/react';
4 | import App from '../../util/app';
5 | import { isDragging } from '../../util/helpers';
6 | import { touch } from '../../util/controls';
7 | import { noop } from '../../../../../src/empty';
8 |
9 | jest.useFakeTimers();
10 |
11 | it('should cancel a pending drag when unmounted', () => {
12 | const warn = jest.spyOn(console, 'warn').mockImplementation(noop);
13 | const { getByText, unmount } = render();
14 | const handle: HTMLElement = getByText('item: 0');
15 |
16 | touch.preLift(handle);
17 |
18 | unmount();
19 |
20 | // finish lift timer
21 | jest.runOnlyPendingTimers();
22 |
23 | expect(warn).not.toHaveBeenCalled();
24 | expect(isDragging(handle)).toBe(false);
25 | warn.mockRestore();
26 | });
27 |
--------------------------------------------------------------------------------
/test/unit/state/auto-scroll/fluid-scroller/util/drag-to.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type { Position } from 'css-box-model';
3 | import type {
4 | Viewport,
5 | DragImpact,
6 | DraggingState,
7 | DroppableDimension,
8 | DimensionMap,
9 | } from '../../../../../../src/types';
10 | import patchDimensionMap from '../../../../../../src/state/patch-dimension-map';
11 |
12 | type DragToArgs = {|
13 | selection: Position,
14 | viewport: Viewport,
15 | state: Object,
16 | impact?: DragImpact,
17 | droppable?: DroppableDimension,
18 | |};
19 |
20 | export default ({
21 | selection,
22 | viewport,
23 | // seeding that we are over the home droppable
24 | impact,
25 | state,
26 | droppable,
27 | }: DragToArgs): DraggingState => {
28 | const base: DraggingState = state.dragging(
29 | state.preset.inHome1.descriptor.id,
30 | selection,
31 | viewport,
32 | );
33 |
34 | const dimensions: DimensionMap = (() => {
35 | if (!droppable) {
36 | return base.dimensions;
37 | }
38 | return patchDimensionMap(base.dimensions, droppable);
39 | })();
40 |
41 | return {
42 | ...base,
43 | // add impact if needed
44 | impact: impact || base.impact,
45 | // add droppable if needed
46 | dimensions,
47 | };
48 | };
49 |
--------------------------------------------------------------------------------
/test/unit/state/auto-scroll/fluid-scroller/util/for-each.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { vertical, horizontal } from '../../../../../../src/state/axis';
3 | import type { Axis } from '../../../../../../src/types';
4 | import { getPreset } from '../../../../../util/dimension';
5 | import getSimpleStatePreset from '../../../../../util/get-simple-state-preset';
6 |
7 | export type BlockFnArgs = {|
8 | axis: Axis,
9 | preset: Object,
10 | state: Object,
11 | |};
12 |
13 | type BlockFn = (args: BlockFnArgs) => void;
14 |
15 | export default (block: BlockFn) => {
16 | [vertical, horizontal].forEach((axis: Axis) => {
17 | describe(`on the ${axis.direction} axis`, () => {
18 | beforeEach(() => {
19 | requestAnimationFrame.reset();
20 | jest.useFakeTimers();
21 | });
22 | afterEach(() => {
23 | jest.useRealTimers();
24 | });
25 |
26 | afterAll(() => {
27 | requestAnimationFrame.reset();
28 | });
29 |
30 | const preset = getPreset(axis);
31 | const state = getSimpleStatePreset(axis);
32 |
33 | block({ axis, preset, state });
34 | });
35 | });
36 | };
37 |
--------------------------------------------------------------------------------
/test/unit/state/auto-scroll/fluid-scroller/util/get-args-mock.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type { Position } from 'css-box-model';
3 | import type { DroppableId } from '../../../../../../src/types';
4 |
5 | // Similiar to PublicArgs
6 | type Result = {|
7 | scrollWindow: JestMockFn<[Position], void>,
8 | scrollDroppable: JestMockFn<[DroppableId, Position], void>,
9 | |};
10 |
11 | export default (): Result => {
12 | const scrollWindow: JestMockFn<[Position], void> = jest.fn();
13 | const scrollDroppable: JestMockFn<[DroppableId, Position], void> = jest.fn();
14 |
15 | return {
16 | scrollWindow,
17 | scrollDroppable,
18 | };
19 | };
20 |
--------------------------------------------------------------------------------
/test/unit/state/auto-scroll/fluid-scroller/util/viewport.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { getRect } from 'css-box-model';
3 | import type { Viewport } from '../../../../../../src/types';
4 | import { origin } from '../../../../../../src/state/position';
5 | import { createViewport } from '../../../../../util/viewport';
6 |
7 | export const windowScrollSize = {
8 | scrollHeight: 2000,
9 | scrollWidth: 1600,
10 | };
11 | export const scrollableViewport: Viewport = createViewport({
12 | frame: getRect({
13 | top: 0,
14 | left: 0,
15 | right: 800,
16 | bottom: 1000,
17 | }),
18 | scrollHeight: windowScrollSize.scrollHeight,
19 | scrollWidth: windowScrollSize.scrollWidth,
20 | scroll: origin,
21 | });
22 |
23 | export const unscrollableViewport: Viewport = createViewport({
24 | frame: getRect({
25 | top: 0,
26 | left: 0,
27 | right: 800,
28 | bottom: 1000,
29 | }),
30 | scrollHeight: 1000,
31 | scrollWidth: 800,
32 | scroll: origin,
33 | });
34 |
--------------------------------------------------------------------------------
/test/unit/state/droppable/clip.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { getRect, type Spacing } from 'css-box-model';
3 | import clip from '../../../../src/state/droppable/util/clip';
4 | import { offsetByPosition } from '../../../../src/state/spacing';
5 |
6 | it('should select clip a subject in a frame', () => {
7 | const subject: Spacing = {
8 | top: 0,
9 | left: 0,
10 | right: 100,
11 | bottom: 100,
12 | };
13 | const frame: Spacing = {
14 | top: 20,
15 | left: 20,
16 | right: 50,
17 | bottom: 50,
18 | };
19 |
20 | expect(clip(frame, subject)).toEqual(getRect(frame));
21 | });
22 |
23 | it('should return null when the subject it outside the frame on any side', () => {
24 | const frame: Spacing = {
25 | top: 0,
26 | left: 0,
27 | right: 100,
28 | bottom: 100,
29 | };
30 | const outside: Spacing[] = [
31 | // top
32 | offsetByPosition(frame, { x: 0, y: -200 }),
33 | // right
34 | offsetByPosition(frame, { x: 200, y: 0 }),
35 | // bottom
36 | offsetByPosition(frame, { x: 0, y: 200 }),
37 | // left
38 | offsetByPosition(frame, { x: -200, y: 0 }),
39 | ];
40 |
41 | outside.forEach((subject: Spacing) => {
42 | expect(clip(frame, subject)).toEqual(null);
43 | });
44 | });
45 |
--------------------------------------------------------------------------------
/test/unit/state/droppable/is-home-of.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { getPreset } from '../../../util/dimension';
3 | import isHomeOf from '../../../../src/state/droppable/is-home-of';
4 |
5 | const preset = getPreset();
6 |
7 | it('should return true if destination is home of draggable', () => {
8 | expect(isHomeOf(preset.inHome1, preset.home)).toBe(true);
9 | expect(isHomeOf(preset.inHome2, preset.home)).toBe(true);
10 | expect(isHomeOf(preset.inForeign1, preset.foreign)).toBe(true);
11 | });
12 |
13 | it('should return false if destination is not home of draggable', () => {
14 | expect(isHomeOf(preset.inForeign1, preset.home)).toBe(false);
15 | expect(isHomeOf(preset.inHome1, preset.foreign)).toBe(false);
16 | });
17 |
--------------------------------------------------------------------------------
/test/unit/state/droppable/what-is-dragged-over.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import noImpact from '../../../../src/state/no-impact';
3 | import whatIsDraggedOver from '../../../../src/state/droppable/what-is-dragged-over';
4 | import type {
5 | DraggableLocation,
6 | DragImpact,
7 | CombineImpact,
8 | } from '../../../../src/types';
9 |
10 | it('should return the droppableId of a reorder impact', () => {
11 | const destination: DraggableLocation = {
12 | droppableId: 'droppable',
13 | index: 0,
14 | };
15 | const impact: DragImpact = {
16 | ...noImpact,
17 | at: {
18 | type: 'REORDER',
19 | destination,
20 | },
21 | };
22 | expect(whatIsDraggedOver(impact)).toEqual(destination.droppableId);
23 | });
24 |
25 | it('should return the droppableId from a merge impact', () => {
26 | const at: CombineImpact = {
27 | type: 'COMBINE',
28 | combine: {
29 | draggableId: 'draggable',
30 | droppableId: 'droppable',
31 | },
32 | };
33 | const impact: DragImpact = {
34 | ...noImpact,
35 | at,
36 | };
37 | expect(whatIsDraggedOver(impact)).toEqual(at.combine.droppableId);
38 | });
39 |
40 | it('should return null when there is no destination or merge impact', () => {
41 | expect(whatIsDraggedOver(noImpact)).toEqual(null);
42 | });
43 |
--------------------------------------------------------------------------------
/test/unit/state/get-drag-impact/util/get-combine-threshold.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type { Position } from 'css-box-model';
3 | import type { Axis, DraggableDimension } from '../../../../../src/types';
4 | import { patch } from '../../../../../src/state/position';
5 | import { combineThresholdDivisor } from '../../../../../src/state/get-drag-impact/get-combine-impact';
6 |
7 | export function getThreshold(
8 | axis: Axis,
9 | draggable: DraggableDimension,
10 | ): Position {
11 | return patch(
12 | axis.line,
13 | draggable.page.borderBox[axis.size] / combineThresholdDivisor,
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/test/unit/state/get-droppable-over/is-disabled.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type { DroppableDimensionMap, DroppableId } from '../../../../src/types';
3 | import getDroppableOver from '../../../../src/state/get-droppable-over';
4 | import { disableDroppable, getPreset } from '../../../util/dimension';
5 |
6 | const preset = getPreset();
7 |
8 | it('should not consider lists that are disabled', () => {
9 | const withDisabled: DroppableDimensionMap = {
10 | ...preset.droppables,
11 | [preset.home.descriptor.id]: disableDroppable(preset.home),
12 | };
13 |
14 | const whileEnabled: ?DroppableId = getDroppableOver({
15 | pageBorderBox: preset.inHome1.page.borderBox,
16 | draggable: preset.inHome1,
17 | droppables: preset.droppables,
18 | });
19 | const whileDisabled: ?DroppableId = getDroppableOver({
20 | pageBorderBox: preset.inHome1.page.borderBox,
21 | draggable: preset.inHome1,
22 | droppables: withDisabled,
23 | });
24 |
25 | expect(whileEnabled).toBe(preset.home.descriptor.id);
26 | expect(whileDisabled).toBe(null);
27 | });
28 |
--------------------------------------------------------------------------------
/test/unit/state/get-droppable-over/is-over-nothing.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type { DroppableId } from '../../../../src/types';
3 | import getDroppableOver from '../../../../src/state/get-droppable-over';
4 | import { getPreset } from '../../../util/dimension';
5 | import { offsetRectByPosition } from '../../../../src/state/rect';
6 |
7 | const preset = getPreset();
8 |
9 | it('should return null when over nothing', () => {
10 | const result: ?DroppableId = getDroppableOver({
11 | pageBorderBox: offsetRectByPosition(preset.inHome1.page.borderBox, {
12 | x: 10000,
13 | y: 10000,
14 | }),
15 | draggable: preset.inHome1,
16 | droppables: preset.droppables,
17 | });
18 |
19 | expect(result).toBe(null);
20 | });
21 |
--------------------------------------------------------------------------------
/test/unit/state/is-within.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import isWithin from '../../../src/state/is-within';
3 |
4 | describe('is within', () => {
5 | const lowerBound: number = 5;
6 | const upperBound: number = 10;
7 | const execute = isWithin(5, 10);
8 |
9 | it('should return true when the value is between the bounds', () => {
10 | expect(execute(lowerBound + 1)).toBe(true);
11 | });
12 |
13 | it('should return true when the value is equal to the lower bound', () => {
14 | expect(execute(lowerBound)).toBe(true);
15 | });
16 |
17 | it('should return true when the value is equal to the upper bound', () => {
18 | expect(execute(upperBound)).toBe(true);
19 | });
20 |
21 | it('should return false when the value is less then the lower bound', () => {
22 | expect(execute(lowerBound - 1)).toBe(false);
23 | });
24 |
25 | it('should return false when the value is greater than the upper bound', () => {
26 | expect(execute(upperBound + 1)).toBe(false);
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/test/unit/state/middleware/responders/util/get-announce-stub.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type { Announce } from '../../../../../../src/types';
3 |
4 | export default (): Announce => jest.fn();
5 |
--------------------------------------------------------------------------------
/test/unit/state/middleware/responders/util/get-completed-with-result.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type {
3 | DropResult,
4 | State,
5 | CompletedDrag,
6 | } from '../../../../../../src/types';
7 | import { invariant } from '../../../../../../src/invariant';
8 |
9 | export default (result: DropResult, state: State): CompletedDrag => {
10 | invariant(
11 | state.phase === 'DRAGGING',
12 | `This is just a simple helper.
13 | Unsupported phase: ${state.phase}`,
14 | );
15 |
16 | return {
17 | afterCritical: state.afterCritical,
18 | critical: state.critical,
19 | result,
20 | impact: state.impact,
21 | };
22 | };
23 |
--------------------------------------------------------------------------------
/test/unit/state/middleware/responders/util/get-responders-stub.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type { Responders } from '../../../../../../src/types';
3 |
4 | export default (): Responders => ({
5 | onBeforeDragStart: jest.fn(),
6 | onDragStart: jest.fn(),
7 | onDragUpdate: jest.fn(),
8 | onDragEnd: jest.fn(),
9 | });
10 |
--------------------------------------------------------------------------------
/test/unit/state/middleware/util/create-store.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { createStore, applyMiddleware } from 'redux';
3 | import reducer from '../../../../../src/state/reducer';
4 | import type { Store, Middleware } from '../../../../../src/state/store-types';
5 |
6 | export default (...middleware: Middleware[]): Store =>
7 | createStore(reducer, applyMiddleware(...middleware));
8 |
--------------------------------------------------------------------------------
/test/unit/state/middleware/util/pass-through-middleware.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type { Action, Middleware } from '../../../../../src/state/store-types';
3 |
4 | const passThrough = (mock: Function): Middleware => {
5 | const result: Middleware = () => (next: Function) => (
6 | action: Action,
7 | ): any => {
8 | mock(action);
9 | next(action);
10 | };
11 |
12 | return result;
13 | };
14 |
15 | export default passThrough;
16 |
--------------------------------------------------------------------------------
/test/unit/state/post-reducer/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/atlassian/react-beautiful-dnd/b833d66c437b6a96c13591bcc9f7b3f4d2a6170e/test/unit/state/post-reducer/.gitkeep
--------------------------------------------------------------------------------
/test/unit/state/publish-while-dragging/nothing-changed.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type {
3 | DropPendingState,
4 | DraggingState,
5 | CollectingState,
6 | } from '../../../../src/types';
7 | import { invariant } from '../../../../src/invariant';
8 | import publish from '../../../../src/state/publish-while-dragging-in-virtual';
9 | import getStatePreset from '../../../util/get-simple-state-preset';
10 | import { empty, withVirtuals } from './util';
11 |
12 | const state = getStatePreset();
13 |
14 | it('should do not modify the dimensions when nothing has changed', () => {
15 | const original: CollectingState = withVirtuals(state.collecting());
16 |
17 | const result: DraggingState | DropPendingState = publish({
18 | state: original,
19 | published: empty,
20 | });
21 |
22 | invariant(result.phase === 'DRAGGING');
23 |
24 | // only minor modifications on original
25 | const expected: DraggingState = {
26 | phase: 'DRAGGING',
27 | ...original,
28 | // appeasing flow
29 | // eslint-disable-next-line
30 | phase: 'DRAGGING',
31 | // we force no animation of the moving item
32 | forceShouldAnimate: false,
33 | };
34 | expect(result).toEqual(expected);
35 | });
36 |
--------------------------------------------------------------------------------
/test/unit/state/publish-while-dragging/phase-change.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import { invariant } from '../../../../src/invariant';
4 | import getStatePreset from '../../../util/get-simple-state-preset';
5 | import type { DropPendingState, DraggingState } from '../../../../src/types';
6 | import publish from '../../../../src/state/publish-while-dragging-in-virtual';
7 | import { empty } from './util';
8 |
9 | const state = getStatePreset();
10 |
11 | it('should move to the DRAGGING phase if was in the COLLECTING phase', () => {
12 | const result: DraggingState | DropPendingState = publish({
13 | state: state.collecting(),
14 | published: empty,
15 | });
16 |
17 | expect(result.phase).toBe('DRAGGING');
18 | });
19 |
20 | it('should move into a non-waiting DROP_PENDING phase if was in a DROP_PENDING phase', () => {
21 | const result: DraggingState | DropPendingState = publish({
22 | state: state.dropPending(),
23 | published: empty,
24 | });
25 |
26 | expect(result.phase).toBe('DROP_PENDING');
27 | invariant(result.phase === 'DROP_PENDING');
28 | expect(result.reason).toBe(state.dropPending().reason);
29 | });
30 |
--------------------------------------------------------------------------------
/test/unit/view/connected-draggable/util/get-dragging-map-props.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type {
3 | MappedProps,
4 | MapProps,
5 | DraggingMapProps,
6 | } from '../../../../../src/view/draggable/draggable-types';
7 | import { invariant } from '../../../../../src/invariant';
8 |
9 | export default (mapProps: MapProps): DraggingMapProps => {
10 | const mapped: MappedProps = mapProps.mapped;
11 | invariant(mapped.type === 'DRAGGING');
12 | return mapped;
13 | };
14 |
--------------------------------------------------------------------------------
/test/unit/view/connected-draggable/util/get-own-props.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type { OwnProps } from '../../../../../src/view/draggable/draggable-types';
3 | import type { DraggableDimension } from '../../../../../src/types';
4 |
5 | export default (dimension: DraggableDimension): OwnProps => ({
6 | // Public own props
7 | draggableId: dimension.descriptor.id,
8 | index: dimension.descriptor.index,
9 | children: () => null,
10 |
11 | // Private own props
12 | isClone: false,
13 | isEnabled: true,
14 | canDragInteractiveElements: false,
15 | shouldRespectForcePress: true,
16 | });
17 |
--------------------------------------------------------------------------------
/test/unit/view/connected-draggable/util/get-secondary-map-props.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type {
3 | MapProps,
4 | MappedProps,
5 | SecondaryMapProps,
6 | } from '../../../../../src/view/draggable/draggable-types';
7 | import { invariant } from '../../../../../src/invariant';
8 |
9 | export default (mapProps: MapProps): SecondaryMapProps => {
10 | const mapped: MappedProps = mapProps.mapped;
11 | invariant(mapped.type === 'SECONDARY');
12 | return mapped;
13 | };
14 |
--------------------------------------------------------------------------------
/test/unit/view/connected-draggable/util/get-snapshot.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type {
3 | StateSnapshot,
4 | DropAnimation,
5 | } from '../../../../../src/view/draggable/draggable-types';
6 | import type {
7 | MovementMode,
8 | DroppableId,
9 | DraggableId,
10 | } from '../../../../../src/types';
11 |
12 | type GetDraggingSnapshotArgs = {|
13 | mode: MovementMode,
14 | draggingOver: ?DroppableId,
15 | combineWith: ?DraggableId,
16 | dropping: ?DropAnimation,
17 | isClone?: ?boolean,
18 | |};
19 |
20 | export const getDraggingSnapshot = ({
21 | mode,
22 | draggingOver,
23 | combineWith,
24 | dropping,
25 | isClone,
26 | }: GetDraggingSnapshotArgs): StateSnapshot => ({
27 | isDragging: true,
28 | isDropAnimating: Boolean(dropping),
29 | dropAnimation: dropping,
30 | mode,
31 | draggingOver,
32 | combineWith,
33 | combineTargetFor: null,
34 | isClone: Boolean(isClone),
35 | });
36 |
37 | type GetSecondarySnapshotArgs = {|
38 | combineTargetFor: ?DraggableId,
39 | |};
40 |
41 | export const getSecondarySnapshot = ({
42 | combineTargetFor,
43 | }: GetSecondarySnapshotArgs): StateSnapshot => ({
44 | isDragging: false,
45 | isClone: false,
46 | isDropAnimating: false,
47 | dropAnimation: null,
48 | mode: null,
49 | draggingOver: null,
50 | combineTargetFor,
51 | combineWith: null,
52 | });
53 |
--------------------------------------------------------------------------------
/test/unit/view/connected-droppable/util/get-own-props.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type { DroppableDimension } from '../../../../../src/types';
3 | import type { OwnProps } from '../../../../../src/view/droppable/droppable-types';
4 | import getBodyElement from '../../../../../src/view/get-body-element';
5 |
6 | export default (dimension: DroppableDimension): OwnProps => ({
7 | droppableId: dimension.descriptor.id,
8 | type: dimension.descriptor.type,
9 | isDropDisabled: false,
10 | isCombineEnabled: true,
11 | direction: dimension.axis.direction,
12 | ignoreContainerClipping: false,
13 | children: () => null,
14 | getContainerForClone: getBodyElement,
15 | mode: 'standard',
16 | renderClone: null,
17 | });
18 |
--------------------------------------------------------------------------------
/test/unit/view/connected-droppable/util/resting-props.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type { MapProps } from '../../../../../src/view/droppable/droppable-types';
3 |
4 | const restingProps: MapProps = {
5 | placeholder: null,
6 | shouldAnimatePlaceholder: false,
7 | snapshot: {
8 | isDraggingOver: false,
9 | draggingOverWith: null,
10 | draggingFromThisWith: null,
11 | isUsingPlaceholder: false,
12 | },
13 | useClone: null,
14 | };
15 |
16 | export default restingProps;
17 |
--------------------------------------------------------------------------------
/test/unit/view/connected-droppable/util/with-combine-impact.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type { DragImpact, Combine } from '../../../../../src/types';
3 |
4 | export default (impact: DragImpact, combine: Combine): DragImpact => ({
5 | ...impact,
6 | at: {
7 | type: 'COMBINE',
8 | combine,
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/test/unit/view/dimension-marshal/util.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { getPreset } from '../../../util/dimension';
3 | import type {
4 | DimensionMap,
5 | LiftRequest,
6 | Critical,
7 | } from '../../../../src/types';
8 |
9 | const preset = getPreset();
10 |
11 | export const defaultRequest: LiftRequest = {
12 | draggableId: preset.inHome1.descriptor.id,
13 | scrollOptions: {
14 | shouldPublishImmediately: false,
15 | },
16 | };
17 |
18 | export const critical: Critical = {
19 | draggable: preset.inHome1.descriptor,
20 | droppable: preset.home.descriptor,
21 | };
22 |
23 | export const justCritical: DimensionMap = {
24 | draggables: {
25 | [preset.inHome1.descriptor.id]: preset.inHome1,
26 | },
27 | droppables: {
28 | [preset.home.descriptor.id]: preset.home,
29 | },
30 | };
31 |
--------------------------------------------------------------------------------
/test/unit/view/drag-drop-context/content-security-protection-nonce.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import { mount, type ReactWrapper } from 'enzyme';
4 | import DragDropContext from '../../../../src/view/drag-drop-context';
5 | import { resetServerContext } from '../../../../src';
6 | import * as attributes from '../../../../src/view/data-attributes';
7 |
8 | it('should insert nonce into style tag', () => {
9 | const nonce = 'ThisShouldBeACryptographicallySecurePseudorandomNumber';
10 |
11 | resetServerContext();
12 | const wrapper1: ReactWrapper<*> = mount(
13 | {}}>
14 | {null}
15 | ,
16 | );
17 | const styleTag = document.querySelector(`[${attributes.prefix}-always="0"]`);
18 | const nonceAttribute = styleTag ? styleTag.getAttribute('nonce') : '';
19 | expect(nonceAttribute).toEqual(nonce);
20 |
21 | wrapper1.unmount();
22 | });
23 |
--------------------------------------------------------------------------------
/test/unit/view/droppable/util/get-stubber.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import type {
4 | Provided,
5 | StateSnapshot,
6 | } from '../../../../../src/view/droppable/droppable-types';
7 |
8 | export default (mock?: Function = () => {}) =>
9 | class Stubber extends React.Component<{
10 | provided: Provided,
11 | snapshot: StateSnapshot,
12 | }> {
13 | render() {
14 | const { provided, snapshot } = this.props;
15 | mock({
16 | provided,
17 | snapshot,
18 | });
19 | return (
20 |
21 | Hey there
22 | {provided.placeholder}
23 |
24 | );
25 | }
26 | };
27 |
--------------------------------------------------------------------------------
/test/unit/view/is-type-of-element/util/get-svg.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | // $FlowFixMe - flow does not know what a SVGElement is
3 | export default (doc: HTMLElement): SVGElement =>
4 | doc.createElementNS('http://www.w3.org/2000/svg', 'svg');
5 |
--------------------------------------------------------------------------------
/test/unit/view/placeholder/util/data.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type { Placeholder } from '../../../../../src/types';
3 | import { getPreset } from '../../../../util/dimension';
4 |
5 | export const preset = getPreset();
6 | export const placeholder: Placeholder = preset.inHome1.placeholder;
7 |
--------------------------------------------------------------------------------
/test/unit/view/placeholder/util/expect.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type { PlaceholderStyle } from '../../../../../src/view/placeholder/placeholder-types';
3 | import { placeholder } from './data';
4 |
5 | export const expectIsEmpty = (style: PlaceholderStyle) => {
6 | expect(style.width).toBe(0);
7 | expect(style.height).toBe(0);
8 | expect(style.marginTop).toBe(0);
9 | expect(style.marginRight).toBe(0);
10 | expect(style.marginBottom).toBe(0);
11 | expect(style.marginLeft).toBe(0);
12 | };
13 |
14 | export const expectIsFull = (style: PlaceholderStyle) => {
15 | expect(style.width).toBe(placeholder.client.borderBox.width);
16 | expect(style.height).toBe(placeholder.client.borderBox.height);
17 | expect(style.marginTop).toBe(placeholder.client.margin.top);
18 | expect(style.marginRight).toBe(placeholder.client.margin.right);
19 | expect(style.marginBottom).toBe(placeholder.client.margin.bottom);
20 | expect(style.marginLeft).toBe(placeholder.client.margin.left);
21 | };
22 |
--------------------------------------------------------------------------------
/test/unit/view/placeholder/util/get-placeholder-style.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type { ReactWrapper } from 'enzyme';
3 | import type { PlaceholderStyle } from '../../../../../src/view/placeholder/placeholder-types';
4 | import { placeholder } from './data';
5 |
6 | export default (wrapper: ReactWrapper<*>): PlaceholderStyle =>
7 | wrapper.find(placeholder.tagName).props().style;
8 |
--------------------------------------------------------------------------------
/test/unit/view/placeholder/util/placeholder-with-class.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import { WithoutMemo } from '../../../../../src/view/placeholder/placeholder';
4 | import type { Props } from '../../../../../src/view/placeholder/placeholder';
5 |
6 | // enzyme does not work well with memo, so exporting the non-memo version
7 | // Using PureComponent to match behaviour of React.memo
8 | export default class PlaceholderWithClass extends React.PureComponent {
9 | render() {
10 | return ;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/test/unit/view/style-marshal/get-styles.spec.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import stylelint from 'stylelint';
3 | import getStyles, {
4 | type Styles,
5 | } from '../../../../src/view/use-style-marshal/get-styles';
6 |
7 | const styles: Styles = getStyles('hey');
8 |
9 | Object.keys(styles).forEach((key: string) => {
10 | it(`should generate valid ${key} styles`, () =>
11 | stylelint
12 | .lint({
13 | code: styles[key],
14 | config: {
15 | // just using the recommended config as it only checks for errors and not formatting
16 | extends: ['stylelint-config-recommended'],
17 | // basic semi colin rules
18 | rules: {
19 | 'no-extra-semicolons': true,
20 | 'declaration-block-semicolon-space-after': 'always-single-line',
21 | },
22 | },
23 | })
24 | .then((result) => {
25 | expect(result.errored).toBe(false);
26 | // asserting that some CSS was actually generated!
27 | // eslint-disable-next-line no-underscore-dangle
28 | expect(result.results[0]._postcssResult.css.length).toBeGreaterThan(1);
29 | }));
30 | });
31 |
--------------------------------------------------------------------------------
/test/util/after-point.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type { Position } from 'css-box-model';
3 | import type { Axis } from '../../src/types';
4 | import { add, patch } from '../../src/state/position';
5 |
6 | export default function afterPoint(axis: Axis, point: Position): Position {
7 | return add(point, patch(axis.line, 1));
8 | }
9 |
10 | export function afterCrossAxisPoint(axis: Axis, point: Position): Position {
11 | return add(point, patch(axis.crossAxisLine, 1));
12 | }
13 |
--------------------------------------------------------------------------------
/test/util/before-point.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type { Position } from 'css-box-model';
3 | import type { Axis } from '../../src/types';
4 | import { subtract, patch } from '../../src/state/position';
5 |
6 | export default function beforePoint(axis: Axis, point: Position): Position {
7 | return subtract(point, patch(axis.line, 1));
8 | }
9 |
10 | export function beforeCrossAxisPoint(axis: Axis, point: Position): Position {
11 | return subtract(point, patch(axis.crossAxisLine, 1));
12 | }
13 |
--------------------------------------------------------------------------------
/test/util/cause-runtime-error.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | export function getRuntimeError(): Event {
3 | return new window.ErrorEvent('error', {
4 | error: new Error('non-rbd'),
5 | cancelable: true,
6 | });
7 | }
8 |
9 | export default function causeRuntimeError() {
10 | window.dispatchEvent(getRuntimeError());
11 | }
12 |
--------------------------------------------------------------------------------
/test/util/clone-impact.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type { DragImpact } from '../../src/types';
3 |
4 | export default (target: DragImpact): DragImpact =>
5 | JSON.parse(JSON.stringify(target));
6 |
--------------------------------------------------------------------------------
/test/util/console.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { noop } from '../../src/empty';
3 |
4 | function withConsole(type: string, fn: () => void, message?: string) {
5 | const mock = jest.spyOn(console, type).mockImplementation(noop);
6 |
7 | fn();
8 |
9 | expect(mock).toHaveBeenCalled();
10 |
11 | if (message) {
12 | expect(mock).toHaveBeenCalledWith(expect.stringContaining(message));
13 | }
14 |
15 | mock.mockReset();
16 | }
17 |
18 | export const withError = withConsole.bind(null, 'error');
19 | export const withWarn = withConsole.bind(null, 'warn');
20 |
21 | function withoutConsole(type: string, fn: () => void) {
22 | const mock = jest.spyOn(console, type).mockImplementation(noop);
23 |
24 | fn();
25 |
26 | expect(mock).not.toHaveBeenCalled();
27 | mock.mockReset();
28 | }
29 |
30 | export const withoutError = withoutConsole.bind(null, 'error');
31 | export const withoutWarn = withoutConsole.bind(null, 'warn');
32 |
--------------------------------------------------------------------------------
/test/util/create-ref.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | export default function createRef() {
3 | let ref: ?HTMLElement = null;
4 |
5 | const setRef = (supplied: ?HTMLElement) => {
6 | ref = supplied;
7 | };
8 |
9 | const getRef = (): ?HTMLElement => ref;
10 |
11 | return { ref, setRef, getRef };
12 | }
13 |
--------------------------------------------------------------------------------
/test/util/force-update.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type { ReactWrapper } from 'enzyme';
3 |
4 | // wrapper.update() no longer forces a render
5 | // instead using wrapper.setProps({});
6 | // https://github.com/airbnb/enzyme/issues/1245
7 |
8 | export default (wrapper: ReactWrapper<*>) => wrapper.setProps({});
9 |
--------------------------------------------------------------------------------
/test/util/no-after-critical.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type { LiftEffect } from '../../src/types';
3 | import { origin } from '../../src/state/position';
4 |
5 | const noOnLift: LiftEffect = {
6 | effected: {},
7 | inVirtualList: false,
8 | displacedBy: {
9 | point: origin,
10 | value: 0,
11 | },
12 | };
13 |
14 | export default noOnLift;
15 |
--------------------------------------------------------------------------------
/test/util/pass-through-props.jsx:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { type Node } from 'react';
3 |
4 | type Props = {|
5 | ...T,
6 | children: (value: T) => Node,
7 | |};
8 |
9 | export default function PassThroughProps(props: Props<*>) {
10 | const { children, ...rest } = props;
11 | return children(rest);
12 | }
13 |
--------------------------------------------------------------------------------
/test/util/reorder.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | // A little helper function to reorder a list
4 | export default (list: any[], startIndex: number, endIndex: number): any[] => {
5 | const result = Array.from(list);
6 | const [removed] = result.splice(startIndex, 1);
7 | result.splice(endIndex, 0, removed);
8 |
9 | return result;
10 | };
11 |
--------------------------------------------------------------------------------
/test/util/set-window-scroll-size.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | type Args = {|
4 | scrollHeight: number,
5 | scrollWidth: number,
6 | |};
7 |
8 | const setWindowScrollSize = ({ scrollHeight, scrollWidth }: Args): void => {
9 | const el: ?HTMLElement = document.documentElement;
10 |
11 | if (!el) {
12 | throw new Error('Unable to find document element');
13 | }
14 |
15 | el.scrollHeight = scrollHeight;
16 | el.scrollWidth = scrollWidth;
17 | };
18 |
19 | const original: Args = (() => {
20 | const el: ?HTMLElement = document.documentElement;
21 |
22 | if (!el) {
23 | throw new Error('Unable to find document element');
24 | }
25 |
26 | return {
27 | scrollWidth: el.scrollWidth,
28 | scrollHeight: el.scrollHeight,
29 | };
30 | })();
31 |
32 | export const resetWindowScrollSize = () => setWindowScrollSize(original);
33 |
34 | export default setWindowScrollSize;
35 |
--------------------------------------------------------------------------------
/test/util/set-window-scroll.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { type Position } from 'css-box-model';
3 |
4 | type Options = {|
5 | shouldPublish: boolean,
6 | |};
7 |
8 | const defaultOptions: Options = {
9 | shouldPublish: true,
10 | };
11 |
12 | const setWindowScroll = (
13 | point: Position,
14 | options?: Options = defaultOptions,
15 | ) => {
16 | window.pageXOffset = point.x;
17 | window.pageYOffset = point.y;
18 |
19 | if (options.shouldPublish) {
20 | window.dispatchEvent(new Event('scroll'));
21 | }
22 | };
23 |
24 | const original: Position = {
25 | x: window.pageXOffset,
26 | y: window.pageYOffset,
27 | };
28 |
29 | export const resetWindowScroll = () => setWindowScroll(original);
30 |
31 | export default setWindowScroll;
32 |
--------------------------------------------------------------------------------
/test/util/spacing.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type { Spacing } from 'css-box-model';
3 |
4 | export const expandBySpacing = (
5 | spacing1: Spacing,
6 | spacing2: Spacing,
7 | ): Spacing => ({
8 | // pulling back to increase size
9 | top: spacing1.top - spacing2.top,
10 | left: spacing1.left - spacing2.left,
11 | // pushing forward to increase size
12 | bottom: spacing1.bottom + spacing2.bottom,
13 | right: spacing1.right + spacing2.right,
14 | });
15 |
16 | export const shrinkBySpacing = (
17 | spacing1: Spacing,
18 | spacing2: Spacing,
19 | ): Spacing => ({
20 | // pushing forward to descrease size
21 | top: spacing1.top + spacing2.top,
22 | left: spacing1.left + spacing2.left,
23 | // pulling backwards to descrease size
24 | bottom: spacing1.bottom - spacing2.bottom,
25 | right: spacing1.right - spacing2.right,
26 | });
27 |
--------------------------------------------------------------------------------
/test/util/try-clean-prototype-stubs.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | export default () => {
3 | // clean up any stubs
4 | if (Element.prototype.getBoundingClientRect.mockRestore) {
5 | Element.prototype.getBoundingClientRect.mockRestore();
6 | }
7 | if (window.getComputedStyle.mockRestore) {
8 | window.getComputedStyle.mockRestore();
9 | }
10 | };
11 |
--------------------------------------------------------------------------------