@eclipse-glsp/protocol 2.7.0-next.2 → 2.7.0-next.21
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/lib/action-protocol/base-protocol.d.ts +7 -1
- package/lib/action-protocol/base-protocol.d.ts.map +1 -1
- package/lib/action-protocol/base-protocol.js +1 -1
- package/lib/action-protocol/base-protocol.js.map +1 -1
- package/lib/action-protocol/contexts.d.ts +44 -1
- package/lib/action-protocol/contexts.d.ts.map +1 -1
- package/lib/action-protocol/contexts.js +35 -2
- package/lib/action-protocol/contexts.js.map +1 -1
- package/lib/action-protocol/types.d.ts +9 -1
- package/lib/action-protocol/types.d.ts.map +1 -1
- package/lib/action-protocol/types.js.map +1 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -1
- package/lib/index.js.map +1 -1
- package/lib/sprotty-geometry-point.d.ts +29 -1
- package/lib/sprotty-geometry-point.d.ts.map +1 -1
- package/lib/sprotty-geometry-point.js +4 -1
- package/lib/sprotty-geometry-point.js.map +1 -1
- package/lib/utils/array-util.d.ts +2 -1
- package/lib/utils/array-util.d.ts.map +1 -1
- package/lib/utils/array-util.js +19 -1
- package/lib/utils/array-util.js.map +1 -1
- package/lib/utils/function-util.d.ts +34 -0
- package/lib/utils/function-util.d.ts.map +1 -0
- package/lib/utils/function-util.js +61 -0
- package/lib/utils/function-util.js.map +1 -0
- package/lib/utils/math-util.d.ts +6 -1
- package/lib/utils/math-util.d.ts.map +1 -1
- package/lib/utils/math-util.js +7 -1
- package/lib/utils/math-util.js.map +1 -1
- package/package.json +3 -3
- package/src/action-protocol/base-protocol.ts +7 -1
- package/src/action-protocol/contexts.ts +65 -1
- package/src/action-protocol/types.ts +11 -1
- package/src/index.ts +2 -1
- package/src/sprotty-geometry-point.spec.ts +48 -1
- package/src/sprotty-geometry-point.ts +38 -2
- package/src/utils/array-util.spec.ts +205 -0
- package/src/utils/array-util.ts +18 -1
- package/src/utils/function-util.spec.ts +109 -0
- package/src/utils/function-util.ts +81 -0
- package/src/utils/math-util.ts +7 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"math-util.js","sourceRoot":"","sources":["../../src/utils/math-util.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;kFAckF
|
|
1
|
+
{"version":3,"file":"math-util.js","sourceRoot":"","sources":["../../src/utils/math-util.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;kFAckF;;;AAQlF,8BAEC;AARD;;;GAGG;AACU,QAAA,oBAAoB,GAAG,IAAI,CAAC;AAEzC,SAAgB,SAAS,CAAC,GAAW,EAAE,KAAa,EAAE,UAAkB,MAAM,CAAC,OAAO;IAClF,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,KAAK,CAAC,IAAI,OAAO,CAAC;AAC5C,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eclipse-glsp/protocol",
|
|
3
|
-
"version": "2.7.0-next.
|
|
3
|
+
"version": "2.7.0-next.21+760b637",
|
|
4
4
|
"description": "The protocol definition for client-server communication in GLSP",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eclipse",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"sprotty-protocol": "1.4.0",
|
|
49
|
-
"uuid": "~
|
|
49
|
+
"uuid": "~14.0.0",
|
|
50
50
|
"vscode-jsonrpc": "8.2.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
"publishConfig": {
|
|
59
59
|
"access": "public"
|
|
60
60
|
},
|
|
61
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "760b637de00febf2dd2cd0b4a97cc90b7cff1bca"
|
|
62
62
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/********************************************************************************
|
|
2
|
-
* Copyright (c) 2021-
|
|
2
|
+
* Copyright (c) 2021-2026 STMicroelectronics and others.
|
|
3
3
|
*
|
|
4
4
|
* This program and the accompanying materials are made available under the
|
|
5
5
|
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
@@ -82,6 +82,12 @@ export interface RequestAction<Res extends ResponseAction> extends Action, sprot
|
|
|
82
82
|
* Unique id for this request. In order to match a response to this request, the response needs to have the same id.
|
|
83
83
|
*/
|
|
84
84
|
requestId: string;
|
|
85
|
+
/**
|
|
86
|
+
* Optional timeout in milliseconds. When set, `requestUntil()` uses this value instead of its
|
|
87
|
+
* default timeout. This allows the sender to control how long the receiver waits for a response.
|
|
88
|
+
* Precedence: explicit `timeoutMs` parameter > `action.timeout` > default (2000ms).
|
|
89
|
+
*/
|
|
90
|
+
timeout?: number;
|
|
85
91
|
/**
|
|
86
92
|
* Used to ensure correct typing. Clients must not use this property
|
|
87
93
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/********************************************************************************
|
|
2
|
-
* Copyright (c) 2021-
|
|
2
|
+
* Copyright (c) 2021-2026 STMicroelectronics and others.
|
|
3
3
|
*
|
|
4
4
|
* This program and the accompanying materials are made available under the
|
|
5
5
|
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
@@ -85,3 +85,67 @@ export namespace SetContextActions {
|
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Sent from the server to the client to request the current {@link EditorContext}. This is the server-initiated
|
|
91
|
+
* counterpart to the `editorContext` parameter that is included in many client-to-server requests.
|
|
92
|
+
*
|
|
93
|
+
* All fields in the response represent a snapshot of the client state at the time the response is generated.
|
|
94
|
+
* There is no guarantee that the state has not changed by the time the server processes the response.
|
|
95
|
+
*
|
|
96
|
+
* If you only need a subset of the editor context, consider using a more specific action instead:
|
|
97
|
+
* - For selected elements only, use `GetSelectionAction`.
|
|
98
|
+
* - For viewport and canvas bounds only, use `GetViewportAction`.
|
|
99
|
+
*/
|
|
100
|
+
export interface GetEditorContextAction extends RequestAction<EditorContextResult> {
|
|
101
|
+
kind: typeof GetEditorContextAction.KIND;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export namespace GetEditorContextAction {
|
|
105
|
+
export const KIND = 'getEditorContext';
|
|
106
|
+
|
|
107
|
+
export function is(object: unknown): object is GetEditorContextAction {
|
|
108
|
+
return RequestAction.hasKind(object, KIND);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function create(options: { requestId?: string; timeout?: number } = {}): GetEditorContextAction {
|
|
112
|
+
return {
|
|
113
|
+
kind: KIND,
|
|
114
|
+
requestId: '',
|
|
115
|
+
...options
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Response to a {@link GetEditorContextAction} containing a snapshot of the client-side editor state.
|
|
122
|
+
*
|
|
123
|
+
* All fields in the {@link EditorContext} reflect the state at the time of response generation.
|
|
124
|
+
* The server should not assume that these values are still current when processing the response,
|
|
125
|
+
* as the client state may have changed in the meantime.
|
|
126
|
+
*/
|
|
127
|
+
export interface EditorContextResult extends ResponseAction {
|
|
128
|
+
kind: typeof EditorContextResult.KIND;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The editor context snapshot.
|
|
132
|
+
*/
|
|
133
|
+
readonly editorContext: EditorContext;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export namespace EditorContextResult {
|
|
137
|
+
export const KIND = 'editorContextResult';
|
|
138
|
+
|
|
139
|
+
export function is(object: unknown): object is EditorContextResult {
|
|
140
|
+
return Action.hasKind(object, KIND) && hasObjectProp(object, 'editorContext');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function create(editorContext: EditorContext, options: { responseId?: string } = {}): EditorContextResult {
|
|
144
|
+
return {
|
|
145
|
+
kind: KIND,
|
|
146
|
+
responseId: '',
|
|
147
|
+
editorContext,
|
|
148
|
+
...options
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
15
15
|
********************************************************************************/
|
|
16
16
|
import * as sprotty from 'sprotty-protocol';
|
|
17
|
-
import { Dimension, Point } from 'sprotty-protocol';
|
|
17
|
+
import { Bounds, Dimension, Point, Viewport } from 'sprotty-protocol';
|
|
18
18
|
import { GModelElementSchema } from '../model/model-schema';
|
|
19
19
|
import { AnyObject, hasArrayProp, hasStringProp } from '../utils/type-util';
|
|
20
20
|
import { Action } from './base-protocol';
|
|
@@ -121,6 +121,16 @@ export interface EditorContext {
|
|
|
121
121
|
*/
|
|
122
122
|
readonly lastMousePosition?: Point;
|
|
123
123
|
|
|
124
|
+
/**
|
|
125
|
+
* The current viewport (scroll position and zoom level).
|
|
126
|
+
*/
|
|
127
|
+
readonly viewport?: Viewport;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* The bounds of the canvas element in the browser.
|
|
131
|
+
*/
|
|
132
|
+
readonly canvasBounds?: Bounds;
|
|
133
|
+
|
|
124
134
|
/**
|
|
125
135
|
* Custom arguments.
|
|
126
136
|
*/
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/********************************************************************************
|
|
2
|
-
* Copyright (c) 2024-
|
|
2
|
+
* Copyright (c) 2024-2026 EclipseSource and others.
|
|
3
3
|
*
|
|
4
4
|
* This program and the accompanying materials are made available under the
|
|
5
5
|
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
@@ -54,6 +54,7 @@ export * from './sprotty-geometry-point';
|
|
|
54
54
|
export * from './utils/array-util';
|
|
55
55
|
export * from './utils/disposable';
|
|
56
56
|
export * from './utils/event';
|
|
57
|
+
export * from './utils/function-util';
|
|
57
58
|
export * from './utils/geometry-movement';
|
|
58
59
|
export * from './utils/geometry-util';
|
|
59
60
|
export * from './utils/geometry-vector';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/********************************************************************************
|
|
2
|
-
* Copyright (c) 2024 EclipseSource and others.
|
|
2
|
+
* Copyright (c) 2024-2026 EclipseSource and others.
|
|
3
3
|
*
|
|
4
4
|
* This program and the accompanying materials are made available under the
|
|
5
5
|
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
@@ -127,4 +127,51 @@ describe('Point', () => {
|
|
|
127
127
|
expect(Point.equals({ x: 1, y: 2 }, { x: 1.0001, y: 2.0001 }, 0.001)).to.be.true;
|
|
128
128
|
});
|
|
129
129
|
});
|
|
130
|
+
|
|
131
|
+
describe('isVerticalAligned', () => {
|
|
132
|
+
it('returns true when both points share the same x coordinate', () => {
|
|
133
|
+
expect(Point.isVerticalAligned({ x: 10, y: 0 }, { x: 10, y: 100 })).to.be.true;
|
|
134
|
+
});
|
|
135
|
+
it('returns true within the default tolerance', () => {
|
|
136
|
+
expect(Point.isVerticalAligned({ x: 10, y: 0 }, { x: 10.0001, y: 100 })).to.be.true;
|
|
137
|
+
});
|
|
138
|
+
it('returns false when the x coordinates differ beyond the default tolerance', () => {
|
|
139
|
+
expect(Point.isVerticalAligned({ x: 10, y: 0 }, { x: 10.5, y: 100 })).to.be.false;
|
|
140
|
+
});
|
|
141
|
+
it('honors an explicit epsilon', () => {
|
|
142
|
+
expect(Point.isVerticalAligned({ x: 10, y: 0 }, { x: 14, y: 100 }, 5)).to.be.true;
|
|
143
|
+
expect(Point.isVerticalAligned({ x: 10, y: 0 }, { x: 14, y: 100 }, 3)).to.be.false;
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('isHorizontalAligned', () => {
|
|
148
|
+
it('returns true when both points share the same y coordinate', () => {
|
|
149
|
+
expect(Point.isHorizontalAligned({ x: 0, y: 50 }, { x: 200, y: 50 })).to.be.true;
|
|
150
|
+
});
|
|
151
|
+
it('returns true within the default tolerance', () => {
|
|
152
|
+
expect(Point.isHorizontalAligned({ x: 0, y: 50 }, { x: 200, y: 50.0001 })).to.be.true;
|
|
153
|
+
});
|
|
154
|
+
it('returns false when the y coordinates differ beyond the default tolerance', () => {
|
|
155
|
+
expect(Point.isHorizontalAligned({ x: 0, y: 50 }, { x: 200, y: 50.5 })).to.be.false;
|
|
156
|
+
});
|
|
157
|
+
it('honors an explicit epsilon', () => {
|
|
158
|
+
expect(Point.isHorizontalAligned({ x: 0, y: 50 }, { x: 200, y: 53 }, 5)).to.be.true;
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('isAxisAligned', () => {
|
|
163
|
+
it('returns true for vertically aligned points', () => {
|
|
164
|
+
expect(Point.isAxisAligned({ x: 10, y: 0 }, { x: 10, y: 100 })).to.be.true;
|
|
165
|
+
});
|
|
166
|
+
it('returns true for horizontally aligned points', () => {
|
|
167
|
+
expect(Point.isAxisAligned({ x: 0, y: 50 }, { x: 200, y: 50 })).to.be.true;
|
|
168
|
+
});
|
|
169
|
+
it('returns false for points that are neither vertically nor horizontally aligned', () => {
|
|
170
|
+
expect(Point.isAxisAligned({ x: 0, y: 0 }, { x: 50, y: 50 })).to.be.false;
|
|
171
|
+
});
|
|
172
|
+
it('honors an explicit epsilon', () => {
|
|
173
|
+
expect(Point.isAxisAligned({ x: 10, y: 0 }, { x: 14, y: 100 }, 5)).to.be.true;
|
|
174
|
+
expect(Point.isAxisAligned({ x: 10, y: 0 }, { x: 14, y: 100 }, 3)).to.be.false;
|
|
175
|
+
});
|
|
176
|
+
});
|
|
130
177
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/********************************************************************************
|
|
2
|
-
* Copyright (c) 2024 EclipseSource and others.
|
|
2
|
+
* Copyright (c) 2024-2026 EclipseSource and others.
|
|
3
3
|
*
|
|
4
4
|
* This program and the accompanying materials are made available under the
|
|
5
5
|
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import { Point } from 'sprotty-protocol/lib/utils/geometry';
|
|
19
19
|
import { Movement } from './utils/geometry-movement';
|
|
20
20
|
import { Vector } from './utils/geometry-vector';
|
|
21
|
-
import { equalUpTo } from './utils/math-util';
|
|
21
|
+
import { ALMOST_EQUAL_EPSILON, equalUpTo } from './utils/math-util';
|
|
22
22
|
import { AnyObject, hasNumberProp } from './utils/type-util';
|
|
23
23
|
|
|
24
24
|
declare module 'sprotty-protocol/lib/utils/geometry' {
|
|
@@ -113,6 +113,37 @@ declare module 'sprotty-protocol/lib/utils/geometry' {
|
|
|
113
113
|
* @returns the movement from `from` in the `vector` direction
|
|
114
114
|
*/
|
|
115
115
|
function moveTowards(from: Point, vector: Vector): Movement;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Returns whether the given points are vertically aligned, i.e. they
|
|
119
|
+
* share (approximately) the same x coordinate.
|
|
120
|
+
*
|
|
121
|
+
* @param a first point
|
|
122
|
+
* @param b second point
|
|
123
|
+
* @param eps tolerance on the x axis (defaults to {@link ALMOST_EQUAL_EPSILON})
|
|
124
|
+
*/
|
|
125
|
+
function isVerticalAligned(a: Point, b: Point, eps?: number): boolean;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Returns whether the given points are horizontally aligned, i.e. they
|
|
129
|
+
* share (approximately) the same y coordinate.
|
|
130
|
+
*
|
|
131
|
+
* @param a first point
|
|
132
|
+
* @param b second point
|
|
133
|
+
* @param eps tolerance on the y axis (defaults to {@link ALMOST_EQUAL_EPSILON})
|
|
134
|
+
*/
|
|
135
|
+
function isHorizontalAligned(a: Point, b: Point, eps?: number): boolean;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Returns whether the given points are axis-aligned, i.e. either
|
|
139
|
+
* {@link isVerticalAligned vertically} or
|
|
140
|
+
* {@link isHorizontalAligned horizontally} aligned.
|
|
141
|
+
*
|
|
142
|
+
* @param a first point
|
|
143
|
+
* @param b second point
|
|
144
|
+
* @param eps tolerance (defaults to {@link ALMOST_EQUAL_EPSILON})
|
|
145
|
+
*/
|
|
146
|
+
function isAxisAligned(a: Point, b: Point, eps?: number): boolean;
|
|
116
147
|
}
|
|
117
148
|
}
|
|
118
149
|
|
|
@@ -153,4 +184,9 @@ Point.moveTowards = (from: Point, vector: Vector): Movement => {
|
|
|
153
184
|
|
|
154
185
|
Point.equals = (one: Point, other: Point, eps?: number): boolean => equalUpTo(one.x, other.x, eps) && equalUpTo(one.y, other.y, eps);
|
|
155
186
|
|
|
187
|
+
Point.isVerticalAligned = (a: Point, b: Point, eps: number = ALMOST_EQUAL_EPSILON): boolean => equalUpTo(a.x, b.x, eps);
|
|
188
|
+
Point.isHorizontalAligned = (a: Point, b: Point, eps: number = ALMOST_EQUAL_EPSILON): boolean => equalUpTo(a.y, b.y, eps);
|
|
189
|
+
Point.isAxisAligned = (a: Point, b: Point, eps: number = ALMOST_EQUAL_EPSILON): boolean =>
|
|
190
|
+
Point.isVerticalAligned(a, b, eps) || Point.isHorizontalAligned(a, b, eps);
|
|
191
|
+
|
|
156
192
|
export { Point };
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2026 EclipseSource and others.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
* http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
*
|
|
8
|
+
* This Source Code may also be made available under the following Secondary
|
|
9
|
+
* Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
* with the GNU Classpath Exception which is available at
|
|
12
|
+
* https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
*
|
|
14
|
+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
15
|
+
********************************************************************************/
|
|
16
|
+
|
|
17
|
+
import { expect } from 'chai';
|
|
18
|
+
import { TypeGuard } from './type-util';
|
|
19
|
+
import {
|
|
20
|
+
arrayOf,
|
|
21
|
+
asArray,
|
|
22
|
+
distinctAdd,
|
|
23
|
+
first,
|
|
24
|
+
flatPush,
|
|
25
|
+
groupBy,
|
|
26
|
+
isArrayOfClass,
|
|
27
|
+
isArrayOfType,
|
|
28
|
+
isStringArray,
|
|
29
|
+
last,
|
|
30
|
+
partition,
|
|
31
|
+
pluck,
|
|
32
|
+
remove
|
|
33
|
+
} from './array-util';
|
|
34
|
+
|
|
35
|
+
describe('ArrayUtil', () => {
|
|
36
|
+
describe('remove', () => {
|
|
37
|
+
it('should remove a present value and leave others intact', () => {
|
|
38
|
+
const arr = [1, 2, 3, 4];
|
|
39
|
+
remove(arr, 2, 4);
|
|
40
|
+
expect(arr).to.deep.equal([1, 3]);
|
|
41
|
+
});
|
|
42
|
+
it('should only remove the first occurrence', () => {
|
|
43
|
+
const arr = [1, 2, 2, 3];
|
|
44
|
+
remove(arr, 2);
|
|
45
|
+
expect(arr).to.deep.equal([1, 2, 3]);
|
|
46
|
+
});
|
|
47
|
+
it('should do nothing for absent values', () => {
|
|
48
|
+
const arr = [1, 2];
|
|
49
|
+
remove(arr, 99);
|
|
50
|
+
expect(arr).to.deep.equal([1, 2]);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('flatPush', () => {
|
|
55
|
+
it('should push single values and nested arrays', () => {
|
|
56
|
+
const arr: number[] = [1];
|
|
57
|
+
flatPush(arr, [2, [3, 4], 5]);
|
|
58
|
+
expect(arr).to.deep.equal([1, 2, 3, 4, 5]);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('distinctAdd', () => {
|
|
63
|
+
it('should add only values not already present', () => {
|
|
64
|
+
const arr = [1, 2];
|
|
65
|
+
distinctAdd(arr, 2, 3, 4, 1);
|
|
66
|
+
expect(arr).to.deep.equal([1, 2, 3, 4]);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('first', () => {
|
|
71
|
+
it('should return the first element without n', () => {
|
|
72
|
+
expect(first([10, 20, 30])).to.equal(10);
|
|
73
|
+
});
|
|
74
|
+
it('should return first n elements', () => {
|
|
75
|
+
expect(first([10, 20, 30], 2)).to.deep.equal([10, 20]);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('last', () => {
|
|
80
|
+
it('should return the last element without n', () => {
|
|
81
|
+
expect(last([10, 20, 30])).to.equal(30);
|
|
82
|
+
});
|
|
83
|
+
it('should return last n elements', () => {
|
|
84
|
+
expect(last([10, 20, 30], 2)).to.deep.equal([20, 30]);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('pluck', () => {
|
|
89
|
+
it('should extract the given property from each element', () => {
|
|
90
|
+
const items = [
|
|
91
|
+
{ id: 1, name: 'a' },
|
|
92
|
+
{ id: 2, name: 'b' }
|
|
93
|
+
];
|
|
94
|
+
expect(pluck(items, 'name')).to.deep.equal(['a', 'b']);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('asArray', () => {
|
|
99
|
+
it('should wrap a single value in an array', () => {
|
|
100
|
+
expect(asArray(5)).to.deep.equal([5]);
|
|
101
|
+
});
|
|
102
|
+
it('should return the array as-is', () => {
|
|
103
|
+
const arr = [1, 2];
|
|
104
|
+
expect(asArray(arr)).to.equal(arr);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('isArrayOfType', () => {
|
|
109
|
+
const isNumber: TypeGuard<number> = (v: unknown): v is number => typeof v === 'number';
|
|
110
|
+
|
|
111
|
+
it('should return true for a matching array', () => {
|
|
112
|
+
expect(isArrayOfType([1, 2, 3], isNumber)).to.be.true;
|
|
113
|
+
});
|
|
114
|
+
it('should return false for an empty array by default', () => {
|
|
115
|
+
expect(isArrayOfType([], isNumber)).to.be.false;
|
|
116
|
+
});
|
|
117
|
+
it('should return true for an empty array with supportEmpty', () => {
|
|
118
|
+
expect(isArrayOfType([], isNumber, true)).to.be.true;
|
|
119
|
+
});
|
|
120
|
+
it('should return false for a mixed array', () => {
|
|
121
|
+
expect(isArrayOfType([1, 'two'], isNumber)).to.be.false;
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('isArrayOfClass', () => {
|
|
126
|
+
it('should return true when all elements are instances of the class', () => {
|
|
127
|
+
expect(isArrayOfClass([new Date(), new Date()], Date)).to.be.true;
|
|
128
|
+
});
|
|
129
|
+
it('should return false for non-instances', () => {
|
|
130
|
+
expect(isArrayOfClass([{}, new Date()], Date)).to.be.false;
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('isStringArray', () => {
|
|
135
|
+
it('should return true for a string array', () => {
|
|
136
|
+
expect(isStringArray(['a', 'b'])).to.be.true;
|
|
137
|
+
});
|
|
138
|
+
it('should return false for a mixed array', () => {
|
|
139
|
+
expect(isStringArray(['a', 1])).to.be.false;
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe('partition', () => {
|
|
144
|
+
const isString = (v: unknown): v is string => typeof v === 'string';
|
|
145
|
+
|
|
146
|
+
it('should split elements by predicate', () => {
|
|
147
|
+
const result = partition(['a', 1, 'b', 2] as (string | number)[], isString as TypeGuard<string | number>);
|
|
148
|
+
expect(result.match).to.deep.equal(['a', 'b']);
|
|
149
|
+
expect(result.rest).to.deep.equal([1, 2]);
|
|
150
|
+
});
|
|
151
|
+
it('should handle all-match case', () => {
|
|
152
|
+
const result = partition(['a', 'b'], isString as TypeGuard<string>);
|
|
153
|
+
expect(result.match).to.deep.equal(['a', 'b']);
|
|
154
|
+
expect(result.rest).to.deep.equal([]);
|
|
155
|
+
});
|
|
156
|
+
it('should handle empty array', () => {
|
|
157
|
+
const result = partition([], isString as TypeGuard<string>);
|
|
158
|
+
expect(result.match).to.deep.equal([]);
|
|
159
|
+
expect(result.rest).to.deep.equal([]);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe('arrayOf', () => {
|
|
164
|
+
it('should filter out undefined values', () => {
|
|
165
|
+
expect(arrayOf(1, undefined, 2, undefined, 3)).to.deep.equal([1, 2, 3]);
|
|
166
|
+
});
|
|
167
|
+
it('should return empty array when all undefined', () => {
|
|
168
|
+
expect(arrayOf(undefined, undefined)).to.deep.equal([]);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe('groupBy', () => {
|
|
173
|
+
it('should group elements by the key function', () => {
|
|
174
|
+
const items = [
|
|
175
|
+
{ type: 'fruit', name: 'apple' },
|
|
176
|
+
{ type: 'veggie', name: 'carrot' },
|
|
177
|
+
{ type: 'fruit', name: 'banana' }
|
|
178
|
+
];
|
|
179
|
+
const result = groupBy(items, i => i.type);
|
|
180
|
+
expect(result.size).to.equal(2);
|
|
181
|
+
expect(result.get('fruit')!.map(i => i.name)).to.deep.equal(['apple', 'banana']);
|
|
182
|
+
expect(result.get('veggie')!.map(i => i.name)).to.deep.equal(['carrot']);
|
|
183
|
+
});
|
|
184
|
+
it('should return an empty map for an empty array', () => {
|
|
185
|
+
expect(groupBy([], () => 'k').size).to.equal(0);
|
|
186
|
+
});
|
|
187
|
+
it('should support numeric keys', () => {
|
|
188
|
+
const result = groupBy([1, 2, 3, 4, 5], n => n % 2);
|
|
189
|
+
expect(result.get(0)).to.deep.equal([2, 4]);
|
|
190
|
+
expect(result.get(1)).to.deep.equal([1, 3, 5]);
|
|
191
|
+
});
|
|
192
|
+
it('should sort groups by key when sorted is true', () => {
|
|
193
|
+
const items = [
|
|
194
|
+
{ rank: 3, name: 'c' },
|
|
195
|
+
{ rank: 1, name: 'a' },
|
|
196
|
+
{ rank: 2, name: 'b' },
|
|
197
|
+
{ rank: 1, name: 'a2' }
|
|
198
|
+
];
|
|
199
|
+
const result = groupBy(items, i => i.rank, true);
|
|
200
|
+
const keys = [...result.keys()];
|
|
201
|
+
expect(keys).to.deep.equal([1, 2, 3]);
|
|
202
|
+
expect(result.get(1)!.map(i => i.name)).to.deep.equal(['a', 'a2']);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
});
|
package/src/utils/array-util.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/********************************************************************************
|
|
2
|
-
* Copyright (c) 2019-
|
|
2
|
+
* Copyright (c) 2019-2026 EclipseSource and others.
|
|
3
3
|
*
|
|
4
4
|
* This program and the accompanying materials are made available under the
|
|
5
5
|
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
@@ -202,3 +202,20 @@ export function partition<T>(source: T[], matchGuard: TypeGuard<T>): { match: T[
|
|
|
202
202
|
export function arrayOf<T>(...values: (T | undefined)[]): T[] {
|
|
203
203
|
return values.filter(element => element !== undefined) as T[];
|
|
204
204
|
}
|
|
205
|
+
|
|
206
|
+
export function groupBy<T, K>(array: T[], keyFn: (item: T) => K, sorted?: boolean): Map<K, T[]> {
|
|
207
|
+
const grouped = new Map<K, T[]>();
|
|
208
|
+
for (const item of array) {
|
|
209
|
+
const key = keyFn(item);
|
|
210
|
+
const values = grouped.get(key);
|
|
211
|
+
if (values) {
|
|
212
|
+
values.push(item);
|
|
213
|
+
} else {
|
|
214
|
+
grouped.set(key, [item]);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (sorted) {
|
|
218
|
+
return new Map([...grouped.entries()].sort());
|
|
219
|
+
}
|
|
220
|
+
return grouped;
|
|
221
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2026 EclipseSource and others.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
* http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
*
|
|
8
|
+
* This Source Code may also be made available under the following Secondary
|
|
9
|
+
* Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
* with the GNU Classpath Exception which is available at
|
|
12
|
+
* https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
*
|
|
14
|
+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
15
|
+
********************************************************************************/
|
|
16
|
+
|
|
17
|
+
import { expect } from 'chai';
|
|
18
|
+
import * as sinon from 'sinon';
|
|
19
|
+
import { debounce } from './function-util';
|
|
20
|
+
|
|
21
|
+
describe('FunctionUtil', () => {
|
|
22
|
+
let clock: sinon.SinonFakeTimers;
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
clock = sinon.useFakeTimers();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
clock.restore();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('debounce', () => {
|
|
33
|
+
it('should invoke the function after the wait period (trailing by default)', () => {
|
|
34
|
+
const spy = sinon.spy();
|
|
35
|
+
const debounced = debounce(spy, 100);
|
|
36
|
+
debounced();
|
|
37
|
+
expect(spy.called).to.be.false;
|
|
38
|
+
clock.tick(100);
|
|
39
|
+
expect(spy.calledOnce).to.be.true;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should reset the timer on rapid calls and only invoke once', () => {
|
|
43
|
+
const spy = sinon.spy();
|
|
44
|
+
const debounced = debounce(spy, 100);
|
|
45
|
+
debounced();
|
|
46
|
+
clock.tick(50);
|
|
47
|
+
debounced();
|
|
48
|
+
clock.tick(50);
|
|
49
|
+
expect(spy.called).to.be.false;
|
|
50
|
+
clock.tick(50);
|
|
51
|
+
expect(spy.calledOnce).to.be.true;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should pass the latest arguments', () => {
|
|
55
|
+
const spy = sinon.spy();
|
|
56
|
+
const debounced = debounce(spy, 100);
|
|
57
|
+
debounced('a');
|
|
58
|
+
debounced('b');
|
|
59
|
+
clock.tick(100);
|
|
60
|
+
expect(spy.calledOnce).to.be.true;
|
|
61
|
+
expect(spy.firstCall.args).to.deep.equal(['b']);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should invoke immediately with leading: true', () => {
|
|
65
|
+
const spy = sinon.spy();
|
|
66
|
+
const debounced = debounce(spy, 100, { leading: true });
|
|
67
|
+
debounced();
|
|
68
|
+
expect(spy.calledOnce).to.be.true;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should not invoke trailing call when leading: true, trailing: false', () => {
|
|
72
|
+
const spy = sinon.spy();
|
|
73
|
+
const debounced = debounce(spy, 100, { leading: true, trailing: false });
|
|
74
|
+
debounced();
|
|
75
|
+
debounced();
|
|
76
|
+
expect(spy.calledOnce).to.be.true;
|
|
77
|
+
clock.tick(100);
|
|
78
|
+
expect(spy.calledOnce).to.be.true;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should invoke both leading and trailing when both are true', () => {
|
|
82
|
+
const spy = sinon.spy();
|
|
83
|
+
const debounced = debounce(spy, 100, { leading: true, trailing: true });
|
|
84
|
+
debounced('first');
|
|
85
|
+
expect(spy.calledOnce).to.be.true;
|
|
86
|
+
debounced('second');
|
|
87
|
+
clock.tick(100);
|
|
88
|
+
expect(spy.calledTwice).to.be.true;
|
|
89
|
+
expect(spy.secondCall.args).to.deep.equal(['second']);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('cancel', () => {
|
|
93
|
+
it('should prevent a pending trailing invocation', () => {
|
|
94
|
+
const spy = sinon.spy();
|
|
95
|
+
const debounced = debounce(spy, 100);
|
|
96
|
+
debounced();
|
|
97
|
+
debounced.cancel();
|
|
98
|
+
clock.tick(100);
|
|
99
|
+
expect(spy.called).to.be.false;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should be safe to call when nothing is pending', () => {
|
|
103
|
+
const spy = sinon.spy();
|
|
104
|
+
const debounced = debounce(spy, 100);
|
|
105
|
+
expect(() => debounced.cancel()).to.not.throw();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|