@khanacademy/perseus 19.1.4 → 19.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/components/graphie.d.ts +9 -1
- package/dist/es/index.js +255 -122
- package/dist/es/index.js.map +1 -1
- package/dist/index.js +261 -126
- package/dist/index.js.map +1 -1
- package/dist/interactive2/interactive-util.d.ts +1 -1
- package/dist/interactive2/movable-point-options.d.ts +5 -4
- package/dist/interactive2/movable.d.ts +90 -13
- package/dist/interactive2/types.d.ts +6 -0
- package/dist/util/graphie.d.ts +5 -0
- package/dist/widgets/expression.d.ts +4 -4
- package/package.json +3 -3
- package/src/components/graphie.tsx +9 -1
- package/src/interactive2/__tests__/movable-mock.ts +42 -30
- package/src/interactive2/movable-point-options.ts +31 -19
- package/src/interactive2/movable.test.ts +249 -0
- package/src/interactive2/movable.ts +289 -131
- package/src/interactive2/types.ts +15 -0
- package/src/interactive2.ts +1 -1
- package/src/util/graphie.ts +10 -0
- package/src/util/interactive.ts +4 -2
- package/tsconfig-build.tsbuildinfo +1 -1
- package/src/interactive2/__tests__/movable.test.ts +0 -157
|
@@ -14,7 +14,7 @@ declare const InteractiveUtil: {
|
|
|
14
14
|
/**
|
|
15
15
|
* Turn a function or an array of functions into an array of functions
|
|
16
16
|
*/
|
|
17
|
-
readonly arrayify: (funcOrArray:
|
|
17
|
+
readonly arrayify: (funcOrArray: ((...args: ReadonlyArray<unknown>) => unknown) | readonly unknown[]) => ReadonlyArray<unknown>;
|
|
18
18
|
/**
|
|
19
19
|
* Convert all function-or-array arguments to arrays of functions
|
|
20
20
|
*/
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { Coord } from "./types";
|
|
1
|
+
import type { Constraint, Coord } from "./types";
|
|
2
|
+
import type { Interval } from "../util/interval";
|
|
2
3
|
declare const _default: {
|
|
3
4
|
add: {
|
|
4
5
|
readonly constrain: () => void;
|
|
@@ -17,9 +18,9 @@ declare const _default: {
|
|
|
17
18
|
standard: null;
|
|
18
19
|
};
|
|
19
20
|
constraints: {
|
|
20
|
-
readonly fixed: () =>
|
|
21
|
-
readonly snap: (snap
|
|
22
|
-
readonly bound: (range
|
|
21
|
+
readonly fixed: () => Constraint;
|
|
22
|
+
readonly snap: (snap?: number | null | undefined) => Constraint;
|
|
23
|
+
readonly bound: (range?: [Interval, Interval] | undefined, snap?: number | Coord | undefined, optionalPaddingPx?: number | undefined) => Constraint;
|
|
23
24
|
};
|
|
24
25
|
onMove: {
|
|
25
26
|
standard: null;
|
|
@@ -1,21 +1,98 @@
|
|
|
1
|
-
import type { Coord } from "./types";
|
|
1
|
+
import type { Constraint, ConstraintCallbacks, Coord } from "./types";
|
|
2
2
|
import type { Graphie } from "../util/graphie";
|
|
3
3
|
export interface State {
|
|
4
4
|
added?: boolean;
|
|
5
5
|
isHovering?: boolean;
|
|
6
6
|
isMouseOver?: boolean;
|
|
7
7
|
isDragging?: boolean;
|
|
8
|
-
mouseTarget?:
|
|
9
|
-
|
|
8
|
+
mouseTarget?: {
|
|
9
|
+
toFront(): void;
|
|
10
|
+
toBack(): void;
|
|
11
|
+
remove(): void;
|
|
12
|
+
getMouseTarget(): Element;
|
|
13
|
+
} | null;
|
|
14
|
+
cursor?: null;
|
|
10
15
|
id: string;
|
|
11
|
-
add
|
|
12
|
-
modify
|
|
13
|
-
draw
|
|
14
|
-
remove
|
|
15
|
-
onMoveStart
|
|
16
|
-
onMove
|
|
17
|
-
onMoveEnd
|
|
18
|
-
onClick
|
|
16
|
+
add?: (() => void)[];
|
|
17
|
+
modify?: ((state: State, prevState?: State) => void)[];
|
|
18
|
+
draw?: ((state: State, prevState?: State) => void)[];
|
|
19
|
+
remove?: (() => void)[];
|
|
20
|
+
onMoveStart?: ((position: Coord, positionAgain: Coord) => void)[];
|
|
21
|
+
onMove?: ((end: Coord, start: Coord) => void)[];
|
|
22
|
+
onMoveEnd?: ((end: Coord, start: Coord) => void)[];
|
|
23
|
+
onClick?: ((position: Coord, start: Coord) => void)[];
|
|
24
|
+
constraints?: Constraint[];
|
|
25
|
+
}
|
|
26
|
+
export declare class Movable<Options extends Record<string, any>> {
|
|
27
|
+
graphie: Graphie;
|
|
28
|
+
state: State;
|
|
29
|
+
prevState: State | undefined;
|
|
30
|
+
_listenerMap: Record<string, number>;
|
|
31
|
+
constructor(graphie: Graphie, options: Options);
|
|
32
|
+
modify(options: Options): void;
|
|
33
|
+
_createDefaultState(): {
|
|
34
|
+
cursor: null;
|
|
35
|
+
id: string;
|
|
36
|
+
add: never[];
|
|
37
|
+
modify: never[];
|
|
38
|
+
draw: never[];
|
|
39
|
+
remove: never[];
|
|
40
|
+
onMoveStart: never[];
|
|
41
|
+
onMove: never[];
|
|
42
|
+
onMoveEnd: never[];
|
|
43
|
+
onClick: never[];
|
|
44
|
+
};
|
|
45
|
+
update(options: Options): void;
|
|
46
|
+
cloneState(): State;
|
|
47
|
+
/**
|
|
48
|
+
* Fire an onSomething type event to all functions in listeners
|
|
49
|
+
*/
|
|
50
|
+
_fireEvent<F extends (...args: any[]) => any>(listeners: F[] | undefined, ...args: Parameters<F>): void;
|
|
51
|
+
/**
|
|
52
|
+
* Call all draw functions, and update our prevState for the next
|
|
53
|
+
* draw function
|
|
54
|
+
*/
|
|
55
|
+
draw(): void;
|
|
56
|
+
/**
|
|
57
|
+
* Simulates a mouse grab event on the movable object.
|
|
58
|
+
*/
|
|
59
|
+
grab(coord: Coord): void;
|
|
60
|
+
_applyConstraints(current: Coord, previous: Coord, extraOptions?: ConstraintCallbacks): any;
|
|
61
|
+
toBack(): void;
|
|
62
|
+
toFront(): void;
|
|
63
|
+
/**
|
|
64
|
+
* Add a listener to any event: startMove, constraints, onMove, onMoveEnd,
|
|
65
|
+
* etc. If a listener is already bound to the given eventName and id, then
|
|
66
|
+
* it is overwritten by func.
|
|
67
|
+
*
|
|
68
|
+
* eventName: the string name of the event to listen to. one of:
|
|
69
|
+
* "onMoveStart", "onMove", "onMoveEnd", "draw", "remove"
|
|
70
|
+
*
|
|
71
|
+
* id: a string id that can be used to remove this event at a later time
|
|
72
|
+
* note: adding multiple listeners with the same id is undefined behavior
|
|
73
|
+
*
|
|
74
|
+
* func: the function to call when the event happens, which is called
|
|
75
|
+
* with the event's standard parameters [usually (coord, prevCoord) or
|
|
76
|
+
* (state, prevState)]
|
|
77
|
+
*/
|
|
78
|
+
listen(eventName: string, id: string, func: () => unknown): void;
|
|
79
|
+
/**
|
|
80
|
+
* Remove a previously added listener, by the id specified in the
|
|
81
|
+
* corresponding listen() call
|
|
82
|
+
*
|
|
83
|
+
* If the given id has not been registered already, this is a no-op
|
|
84
|
+
*/
|
|
85
|
+
unlisten(eventName: string, id: string): void;
|
|
86
|
+
remove(): void;
|
|
87
|
+
cursor(): null | undefined;
|
|
88
|
+
added(): boolean | undefined;
|
|
89
|
+
isHovering(): boolean | undefined;
|
|
90
|
+
isMouseOver(): boolean | undefined;
|
|
91
|
+
isDragging(): boolean | undefined;
|
|
92
|
+
mouseTarget(): {
|
|
93
|
+
toFront(): void;
|
|
94
|
+
toBack(): void;
|
|
95
|
+
remove(): void;
|
|
96
|
+
getMouseTarget(): Element;
|
|
97
|
+
} | null | undefined;
|
|
19
98
|
}
|
|
20
|
-
declare const Movable: (graphie: Graphie, options: any) => void;
|
|
21
|
-
export default Movable;
|
|
@@ -1,2 +1,8 @@
|
|
|
1
|
+
import type { Movable } from "./movable";
|
|
1
2
|
export type Coord = [number, number];
|
|
2
3
|
export type Line = [Coord, Coord];
|
|
4
|
+
export type Constraint = (this: Movable<any>, current: Coord | boolean, previous: Coord, options: ConstraintCallbacks) => Coord | boolean | undefined;
|
|
5
|
+
export type ConstraintCallbacks = {
|
|
6
|
+
onSkipRemaining(): void;
|
|
7
|
+
onOutOfBounds?: () => void;
|
|
8
|
+
};
|
package/dist/util/graphie.d.ts
CHANGED
|
@@ -40,6 +40,7 @@ export declare class Graphie {
|
|
|
40
40
|
dimensions?: Coord;
|
|
41
41
|
xpixels?: number;
|
|
42
42
|
ypixels?: number;
|
|
43
|
+
isDragging?: boolean;
|
|
43
44
|
constructor(el: Element);
|
|
44
45
|
init(options: {
|
|
45
46
|
range: [Interval, Interval];
|
|
@@ -102,6 +103,10 @@ export declare class Graphie {
|
|
|
102
103
|
unscalePoint: (point: Coord) => any;
|
|
103
104
|
unscaleVector: (point: Coord) => any;
|
|
104
105
|
private processAttributes;
|
|
106
|
+
getMouseCoord(event: Readonly<{
|
|
107
|
+
pageX?: number;
|
|
108
|
+
pageY?: number;
|
|
109
|
+
}>): Coord;
|
|
105
110
|
}
|
|
106
111
|
type RangeSpecifier = [Interval, Interval] | Coord | number;
|
|
107
112
|
export declare function normalizeRange(range: RangeSpecifier): [Interval, Interval];
|
|
@@ -212,10 +212,10 @@ declare const _default: Readonly<{
|
|
|
212
212
|
onFocus: (blurPath: import("../types").FocusPath) => void;
|
|
213
213
|
times: boolean;
|
|
214
214
|
value: string;
|
|
215
|
-
}, "
|
|
216
|
-
value?: string | undefined;
|
|
215
|
+
}, "onChange" | "trackInteraction" | "static" | "analytics" | "problemNum" | "questionCompleted" | "keypadElement" | "alignment" | "buttonsVisible" | "containerSizeClass" | "widgetId" | "findWidgets" | "keypadConfiguration" | "reviewModeRubric" | "isLastUsedWidget"> & {
|
|
217
216
|
onFocus?: ((blurPath: import("../types").FocusPath) => void) | undefined;
|
|
218
217
|
onBlur?: ((blurPath: import("../types").FocusPath) => void) | undefined;
|
|
218
|
+
value?: string | undefined;
|
|
219
219
|
linterContext?: import("@khanacademy/perseus-linter").LinterContextProps | undefined;
|
|
220
220
|
apiOptions?: Readonly<Readonly<{
|
|
221
221
|
isArticle?: boolean | undefined;
|
|
@@ -401,10 +401,10 @@ declare const _default: Readonly<{
|
|
|
401
401
|
onFocus: (blurPath: import("../types").FocusPath) => void;
|
|
402
402
|
times: boolean;
|
|
403
403
|
value: string;
|
|
404
|
-
}, "
|
|
405
|
-
value?: string | undefined;
|
|
404
|
+
}, "onChange" | "trackInteraction" | "static" | "analytics" | "problemNum" | "questionCompleted" | "keypadElement" | "alignment" | "buttonsVisible" | "containerSizeClass" | "widgetId" | "findWidgets" | "keypadConfiguration" | "reviewModeRubric" | "isLastUsedWidget"> & {
|
|
406
405
|
onFocus?: ((blurPath: import("../types").FocusPath) => void) | undefined;
|
|
407
406
|
onBlur?: ((blurPath: import("../types").FocusPath) => void) | undefined;
|
|
407
|
+
value?: string | undefined;
|
|
408
408
|
linterContext?: import("@khanacademy/perseus-linter").LinterContextProps | undefined;
|
|
409
409
|
apiOptions?: Readonly<Readonly<{
|
|
410
410
|
isArticle?: boolean | undefined;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Core Perseus API (includes renderers and widgets)",
|
|
4
4
|
"author": "Khan Academy",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "19.1.
|
|
6
|
+
"version": "19.1.5",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public"
|
|
9
9
|
},
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@khanacademy/kas": "^0.3.7",
|
|
26
|
-
"@khanacademy/kmath": "^0.1.
|
|
27
|
-
"@khanacademy/math-input": "^17.0.
|
|
26
|
+
"@khanacademy/kmath": "^0.1.11",
|
|
27
|
+
"@khanacademy/math-input": "^17.0.4",
|
|
28
28
|
"@khanacademy/perseus-core": "1.4.1",
|
|
29
29
|
"@khanacademy/perseus-linter": "^0.3.10",
|
|
30
30
|
"@khanacademy/pure-markdown": "^0.2.13",
|
|
@@ -62,6 +62,9 @@ type DefaultProps = {
|
|
|
62
62
|
|
|
63
63
|
interface Movable {
|
|
64
64
|
remove(): void;
|
|
65
|
+
props: unknown;
|
|
66
|
+
modify(graphie: GraphieDrawingContext): "reordered" | undefined;
|
|
67
|
+
toFront(): void;
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
class Graphie extends React.Component<Props> {
|
|
@@ -231,7 +234,12 @@ class Graphie extends React.Component<Props> {
|
|
|
231
234
|
// @ts-expect-error - TS2322 - Type '(children: readonly any[], options: any) => ReactElement<any, string | JSXElementConstructor<any>> | readonly ReactElement<any, string | JSXElementConstructor<any>>[]' is not assignable to type '(children: readonly any[], arg2: any) => ReactElement<any, string | JSXElementConstructor<any>>'.
|
|
232
235
|
_renderMovables: (
|
|
233
236
|
children: ReadonlyArray<any>,
|
|
234
|
-
arg2:
|
|
237
|
+
arg2: {
|
|
238
|
+
nextKey: number;
|
|
239
|
+
graphie: GraphieDrawingContext;
|
|
240
|
+
oldMovables: Record<string, Movable>;
|
|
241
|
+
newMovables: Record<string, Movable>;
|
|
242
|
+
},
|
|
235
243
|
) => React.ReactElement = (children, options) => {
|
|
236
244
|
// Each leaf of `children` is a movable descriptor created by a call to
|
|
237
245
|
// some `GraphieMovable`, such as `MovablePoint`.
|
|
@@ -5,39 +5,51 @@
|
|
|
5
5
|
import _ from "underscore";
|
|
6
6
|
|
|
7
7
|
import {Graphie} from "../../util/graphie";
|
|
8
|
-
import Movable from "../movable";
|
|
8
|
+
import {Movable} from "../movable";
|
|
9
|
+
|
|
10
|
+
import type {Coord} from "@khanacademy/perseus";
|
|
9
11
|
|
|
10
12
|
const createMock = function (): any {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
};
|
|
23
|
-
},
|
|
24
|
-
);
|
|
25
|
-
movable.move = function (...args) {
|
|
26
|
-
const startPoint = _.first(args);
|
|
13
|
+
return new MovableMock(new Graphie(document.createElement("div")), {});
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
class MovableMock extends Movable<Record<string, never>> {
|
|
17
|
+
modify(options: any) {
|
|
18
|
+
// TODO(benchristel): what is mouseTarget? It seems to be unused.
|
|
19
|
+
super.modify(_.omit(options, "mouseTarget"));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
move(...args) {
|
|
23
|
+
const [startPoint, ...rest] = args;
|
|
27
24
|
// TODO(jack): Move these into onMoveStart, onMove, and onMoveEnd
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
this.state.isMouseOver = true;
|
|
26
|
+
this.state.isHovering = true;
|
|
27
|
+
this.onMoveStart(startPoint, startPoint);
|
|
28
|
+
rest.forEach((point, i) => {
|
|
29
|
+
this.state.isDragging = true;
|
|
30
|
+
this.onMove(point, args[i]);
|
|
34
31
|
});
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
32
|
+
this.onMoveEnd(_.last(args), startPoint);
|
|
33
|
+
this.state.isDragging = false;
|
|
34
|
+
this.state.isMouseOver = false;
|
|
35
|
+
this.state.isHovering = false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
onMoveStart(coord: Coord, prevCoord: Coord) {
|
|
39
|
+
this._fireEvent(this.state.onMoveStart, coord, prevCoord);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
onMove(coord: Coord, prevCoord: Coord) {
|
|
43
|
+
this._fireEvent(this.state.onMove, coord, prevCoord);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
onMoveEnd(coord: Coord, prevCoord: Coord) {
|
|
47
|
+
this._fireEvent(this.state.onMoveEnd, coord, prevCoord);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
onClick(coord: Coord, prevCoord: Coord) {
|
|
51
|
+
this._fireEvent(this.state.onClick, coord, prevCoord);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
42
54
|
|
|
43
55
|
export default createMock;
|
|
@@ -7,7 +7,8 @@ import _ from "underscore";
|
|
|
7
7
|
|
|
8
8
|
import WrappedEllipse from "./wrapped-ellipse";
|
|
9
9
|
|
|
10
|
-
import type {Coord} from "./types";
|
|
10
|
+
import type {Constraint, ConstraintCallbacks, Coord} from "./types";
|
|
11
|
+
import type {Interval} from "../util/interval";
|
|
11
12
|
|
|
12
13
|
const add = {
|
|
13
14
|
constrain: function () {
|
|
@@ -119,51 +120,62 @@ const remove = {
|
|
|
119
120
|
remove.standard = remove.basic;
|
|
120
121
|
|
|
121
122
|
const constraints = {
|
|
122
|
-
fixed: function ():
|
|
123
|
+
fixed: function (): Constraint {
|
|
123
124
|
return function () {
|
|
124
125
|
return false;
|
|
125
126
|
};
|
|
126
127
|
},
|
|
127
128
|
|
|
128
|
-
snap: function (snap
|
|
129
|
-
return function (coord: Coord)
|
|
129
|
+
snap: function (snap?: number | null): Constraint {
|
|
130
|
+
return function (coord: Coord) {
|
|
130
131
|
if (snap === null) {
|
|
131
|
-
// NOTE(kevinb): this should probably return
|
|
132
|
+
// TODO(benchristel), NOTE(kevinb): this should probably return
|
|
133
|
+
// the original point
|
|
132
134
|
return true;
|
|
133
135
|
}
|
|
134
|
-
// @ts-expect-error -
|
|
135
|
-
|
|
136
|
-
return kpoint.roundTo(coord, snap);
|
|
136
|
+
// @ts-expect-error - TS2339: Property 'snap' does not exist on type 'Graphie'.
|
|
137
|
+
return kpoint.roundTo(coord, snap || this.graphie.snap);
|
|
137
138
|
};
|
|
138
139
|
},
|
|
139
140
|
|
|
140
141
|
bound: function (
|
|
141
|
-
range
|
|
142
|
-
snap
|
|
143
|
-
|
|
144
|
-
):
|
|
145
|
-
|
|
146
|
-
if (
|
|
147
|
-
|
|
142
|
+
range?: [Interval, Interval],
|
|
143
|
+
snap?: Coord | number,
|
|
144
|
+
optionalPaddingPx?: number,
|
|
145
|
+
): Constraint {
|
|
146
|
+
const paddingPx = (() => {
|
|
147
|
+
if (optionalPaddingPx === undefined) {
|
|
148
|
+
if (range === undefined) {
|
|
149
|
+
return 10;
|
|
150
|
+
} else {
|
|
151
|
+
return 0;
|
|
152
|
+
}
|
|
148
153
|
} else {
|
|
149
|
-
|
|
154
|
+
return optionalPaddingPx;
|
|
150
155
|
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
|
|
156
|
+
})();
|
|
157
|
+
|
|
158
|
+
return function (
|
|
159
|
+
coord: Coord,
|
|
160
|
+
prev: Coord,
|
|
161
|
+
options: ConstraintCallbacks,
|
|
162
|
+
) {
|
|
154
163
|
const graphie = this.graphie;
|
|
155
164
|
range = range || graphie.range;
|
|
156
165
|
|
|
157
166
|
if (snap === undefined) {
|
|
167
|
+
// @ts-expect-error - Property 'snap' does not exist on type 'Graphie'.
|
|
158
168
|
snap = graphie.snap;
|
|
159
169
|
}
|
|
160
170
|
|
|
161
171
|
let lower = graphie.unscalePoint([
|
|
162
172
|
paddingPx,
|
|
173
|
+
// @ts-expect-error - 'graphie.ypixels' is possibly 'undefined'.
|
|
163
174
|
graphie.ypixels - paddingPx,
|
|
164
175
|
]);
|
|
165
176
|
|
|
166
177
|
let upper = graphie.unscalePoint([
|
|
178
|
+
// @ts-expect-error - 'graphie.ypixels' is possibly 'undefined'.
|
|
167
179
|
graphie.xpixels - paddingPx,
|
|
168
180
|
paddingPx,
|
|
169
181
|
]);
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import {fireEvent} from "@testing-library/react";
|
|
2
|
+
|
|
3
|
+
import {Graphie} from "../util/graphie";
|
|
4
|
+
|
|
5
|
+
import {Movable} from "./movable";
|
|
6
|
+
|
|
7
|
+
// Yay for side-effect imports!
|
|
8
|
+
// eslint-disable-next-line import/no-unassigned-import
|
|
9
|
+
import "../util/interactive";
|
|
10
|
+
|
|
11
|
+
describe("Movable", () => {
|
|
12
|
+
const properties = [
|
|
13
|
+
"graphie",
|
|
14
|
+
"state",
|
|
15
|
+
"prevState",
|
|
16
|
+
"cursor",
|
|
17
|
+
"added",
|
|
18
|
+
"isHovering",
|
|
19
|
+
"isMouseOver",
|
|
20
|
+
"isDragging",
|
|
21
|
+
"mouseTarget",
|
|
22
|
+
"_fireEvent",
|
|
23
|
+
"_applyConstraints",
|
|
24
|
+
"draw",
|
|
25
|
+
"listen",
|
|
26
|
+
"unlisten",
|
|
27
|
+
"cloneState",
|
|
28
|
+
"_createDefaultState",
|
|
29
|
+
"modify",
|
|
30
|
+
"grab",
|
|
31
|
+
"update",
|
|
32
|
+
"remove",
|
|
33
|
+
"toBack",
|
|
34
|
+
"toFront",
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
it.each(properties)("has a %s property", (property) => {
|
|
38
|
+
const dummyGraphie: any = {};
|
|
39
|
+
const movable = new Movable(dummyGraphie, {});
|
|
40
|
+
expect(movable[property]).toBeDefined();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const getters = [
|
|
44
|
+
"cursor",
|
|
45
|
+
"added",
|
|
46
|
+
"isHovering",
|
|
47
|
+
"isMouseOver",
|
|
48
|
+
"isDragging",
|
|
49
|
+
"mouseTarget",
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
it.each(getters)(
|
|
53
|
+
"returns the corresponding state property from %s()",
|
|
54
|
+
(getter) => {
|
|
55
|
+
const dummyGraphie: any = {};
|
|
56
|
+
const movable = new Movable(dummyGraphie, {});
|
|
57
|
+
expect(movable[getter]()).toBe(movable.state[getter]);
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
it("sets default state when constructed", () => {
|
|
62
|
+
const dummyGraphie: any = {};
|
|
63
|
+
const movable = new Movable(dummyGraphie, {});
|
|
64
|
+
expect(movable.state).toEqual({
|
|
65
|
+
id: expect.any(String),
|
|
66
|
+
add: [],
|
|
67
|
+
added: true,
|
|
68
|
+
cursor: null,
|
|
69
|
+
draw: [],
|
|
70
|
+
isDragging: false,
|
|
71
|
+
isHovering: false,
|
|
72
|
+
isMouseOver: false,
|
|
73
|
+
modify: [],
|
|
74
|
+
mouseTarget: null,
|
|
75
|
+
onClick: [],
|
|
76
|
+
onMove: [],
|
|
77
|
+
onMoveEnd: [],
|
|
78
|
+
onMoveStart: [],
|
|
79
|
+
remove: [],
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("calls the modify listener when constructed", () => {
|
|
84
|
+
const dummyGraphie: any = {};
|
|
85
|
+
const modifySpy = jest.fn().mockName("modify");
|
|
86
|
+
|
|
87
|
+
const _ = new Movable(dummyGraphie, {modify: modifySpy});
|
|
88
|
+
|
|
89
|
+
const baseExpectedState = {
|
|
90
|
+
add: [],
|
|
91
|
+
added: false,
|
|
92
|
+
cursor: null,
|
|
93
|
+
draw: [],
|
|
94
|
+
id: expect.any(String),
|
|
95
|
+
isDragging: false,
|
|
96
|
+
isHovering: false,
|
|
97
|
+
isMouseOver: false,
|
|
98
|
+
modify: [modifySpy],
|
|
99
|
+
mouseTarget: null,
|
|
100
|
+
onClick: [],
|
|
101
|
+
onMove: [],
|
|
102
|
+
onMoveEnd: [],
|
|
103
|
+
onMoveStart: [],
|
|
104
|
+
remove: [],
|
|
105
|
+
};
|
|
106
|
+
expect(modifySpy.mock.calls).toEqual([
|
|
107
|
+
[baseExpectedState, {}],
|
|
108
|
+
[
|
|
109
|
+
{...baseExpectedState, added: true},
|
|
110
|
+
{...baseExpectedState, added: true},
|
|
111
|
+
],
|
|
112
|
+
]);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("cloneState()", () => {
|
|
116
|
+
const movable = new Movable(
|
|
117
|
+
new Graphie(document.createElement("div")),
|
|
118
|
+
{},
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
expect(movable.cloneState()).toEqual(
|
|
122
|
+
expect.objectContaining({
|
|
123
|
+
id: expect.any(String),
|
|
124
|
+
add: [],
|
|
125
|
+
added: true,
|
|
126
|
+
cursor: null,
|
|
127
|
+
draw: [],
|
|
128
|
+
isDragging: false,
|
|
129
|
+
isHovering: false,
|
|
130
|
+
isMouseOver: false,
|
|
131
|
+
modify: [],
|
|
132
|
+
mouseTarget: null,
|
|
133
|
+
onClick: [],
|
|
134
|
+
onMove: [],
|
|
135
|
+
onMoveEnd: [],
|
|
136
|
+
onMoveStart: [],
|
|
137
|
+
remove: [],
|
|
138
|
+
}),
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("cloneState() makes a copy", () => {
|
|
143
|
+
const movable = new Movable(
|
|
144
|
+
new Graphie(document.createElement("div")),
|
|
145
|
+
{},
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
expect(movable.cloneState()).not.toBe(movable.state);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("modify() retains original ID", () => {
|
|
152
|
+
const movable = new Movable(
|
|
153
|
+
new Graphie(document.createElement("div")),
|
|
154
|
+
{},
|
|
155
|
+
);
|
|
156
|
+
const id = movable.state.id;
|
|
157
|
+
|
|
158
|
+
movable.modify({added: true});
|
|
159
|
+
|
|
160
|
+
expect(movable.state.id).toBe(id);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("grab() fires onMoveStart", () => {
|
|
164
|
+
const graphie = new Graphie(document.createElement("div"));
|
|
165
|
+
graphie.init({
|
|
166
|
+
range: [
|
|
167
|
+
[-10, 10],
|
|
168
|
+
[-10, 10],
|
|
169
|
+
],
|
|
170
|
+
});
|
|
171
|
+
const onMoveStart = jest.fn();
|
|
172
|
+
const movable = new Movable(graphie, {onMoveStart});
|
|
173
|
+
|
|
174
|
+
movable.grab([0, 0]);
|
|
175
|
+
|
|
176
|
+
expect(onMoveStart).toHaveBeenCalledWith([0, 0], [0, 0]);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("grab() fires onMove when mouse moves", () => {
|
|
180
|
+
const graphie = new Graphie(document.createElement("div"));
|
|
181
|
+
graphie.init({
|
|
182
|
+
range: [
|
|
183
|
+
[-10, 10],
|
|
184
|
+
[-10, 10],
|
|
185
|
+
],
|
|
186
|
+
scale: [10, 10],
|
|
187
|
+
});
|
|
188
|
+
const onMove = jest.fn();
|
|
189
|
+
const movable = new Movable(graphie, {onMove});
|
|
190
|
+
|
|
191
|
+
movable.grab([0, 0]);
|
|
192
|
+
// eslint-disable-next-line testing-library/prefer-user-event
|
|
193
|
+
fireEvent.mouseMove(document, {clientX: 120, clientY: 80});
|
|
194
|
+
|
|
195
|
+
expect(onMove).toHaveBeenCalledWith([2, 2], [0, 0]);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("grab() fires onMoveEnd when mouse up", () => {
|
|
199
|
+
const dummyGraphie: any = {};
|
|
200
|
+
const onMoveEnd = jest.fn();
|
|
201
|
+
const movable = new Movable(dummyGraphie, {onMoveEnd});
|
|
202
|
+
|
|
203
|
+
movable.grab([0, 0]);
|
|
204
|
+
// eslint-disable-next-line testing-library/prefer-user-event
|
|
205
|
+
fireEvent.mouseUp(document);
|
|
206
|
+
|
|
207
|
+
expect(onMoveEnd).toHaveBeenCalledWith([0, 0], [0, 0]);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("grab() fires onClick if still hovering when mouse up", () => {
|
|
211
|
+
const dummyGraphie: any = {};
|
|
212
|
+
const onClick = jest.fn();
|
|
213
|
+
const movable = new Movable(dummyGraphie, {onClick});
|
|
214
|
+
|
|
215
|
+
movable.grab([0, 0]);
|
|
216
|
+
// eslint-disable-next-line testing-library/prefer-user-event
|
|
217
|
+
fireEvent.mouseUp(document);
|
|
218
|
+
|
|
219
|
+
expect(onClick).toHaveBeenCalledWith([0, 0], [0, 0]);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("draw() fires a draw event", () => {
|
|
223
|
+
const dummyGraphie: any = {};
|
|
224
|
+
const drawSpy = jest.fn();
|
|
225
|
+
const movable = new Movable(dummyGraphie, {draw: drawSpy});
|
|
226
|
+
|
|
227
|
+
movable.draw();
|
|
228
|
+
|
|
229
|
+
const expectedState = {
|
|
230
|
+
add: [],
|
|
231
|
+
added: true,
|
|
232
|
+
cursor: null,
|
|
233
|
+
draw: [drawSpy],
|
|
234
|
+
id: expect.any(String),
|
|
235
|
+
isDragging: false,
|
|
236
|
+
isHovering: false,
|
|
237
|
+
isMouseOver: false,
|
|
238
|
+
modify: [],
|
|
239
|
+
mouseTarget: null,
|
|
240
|
+
onClick: [],
|
|
241
|
+
onMove: [],
|
|
242
|
+
onMoveEnd: [],
|
|
243
|
+
onMoveStart: [],
|
|
244
|
+
remove: [],
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
expect(drawSpy).toHaveBeenCalledWith(expectedState, expectedState);
|
|
248
|
+
});
|
|
249
|
+
});
|