@hydranium/conformance 1.0.0-next.254 → 1.0.0-next.256
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/README.md +3 -1
- package/lib/glsp/index.d.ts +18 -2
- package/lib/glsp/index.d.ts.map +1 -1
- package/lib/glsp/index.js +32 -1
- package/lib/glsp/index.js.map +1 -1
- package/lib/glsp/node/index.d.ts +4 -2
- package/lib/glsp/node/index.d.ts.map +1 -1
- package/lib/glsp/node/index.js +17 -5
- package/lib/glsp/node/index.js.map +1 -1
- package/package.json +3 -3
- package/src/glsp/index.ts +56 -3
- package/src/glsp/node/index.ts +23 -7
package/README.md
CHANGED
|
@@ -57,7 +57,9 @@ Each function takes `connect` and `languages`:
|
|
|
57
57
|
|
|
58
58
|
The GLSP slice is generic over your action type and takes a `GlspFixture` per
|
|
59
59
|
diagram type. The fixture builds your native actions, and the kit matches the
|
|
60
|
-
responses by `kind`.
|
|
60
|
+
responses by `kind`. A create check also waits for the operation's receipt,
|
|
61
|
+
the `setDirtyState` with reason `'operation'` GLSP sends for an operation that
|
|
62
|
+
ran, so the session must declare `setDirtyState`.
|
|
61
63
|
|
|
62
64
|
To check a deployed GLSP server rather than one built in your test process,
|
|
63
65
|
return `connectGlspSocketDriver({ port, diagramType, clientActionKinds })` from
|
package/lib/glsp/index.d.ts
CHANGED
|
@@ -20,7 +20,12 @@ export interface GlspConformanceDriver<TAction> extends Harness {
|
|
|
20
20
|
start(): Promise<void>;
|
|
21
21
|
/** Send an action to the server. Fire-and-forget (GLSP `process` is `void`). */
|
|
22
22
|
dispatch(action: TAction): void;
|
|
23
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Resolve with the next action whose `kind` matches and that arrived after
|
|
25
|
+
* the last {@link dispatch}, or since `start` before any; reject on timeout.
|
|
26
|
+
* Only after the dispatch, so an action an earlier step sent cannot pass for
|
|
27
|
+
* the answer to this one.
|
|
28
|
+
*/
|
|
24
29
|
nextAction<T extends TAction = TAction>(kind: string, timeoutMs?: number): Promise<T>;
|
|
25
30
|
}
|
|
26
31
|
/**
|
|
@@ -43,7 +48,18 @@ export interface GlspCreateOperationSpec<TAction, TDriver extends GlspConformanc
|
|
|
43
48
|
* loaded.
|
|
44
49
|
*/
|
|
45
50
|
readonly action: (driver: TDriver) => TAction;
|
|
46
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* The action kind the server settles the operation with (e.g. a
|
|
53
|
+
* re-`RequestBounds` for client-laid-out diagrams).
|
|
54
|
+
*
|
|
55
|
+
* The kit also waits for the operation's receipt: GLSP's
|
|
56
|
+
* `OperationActionHandler` submits every operation that ran with reason
|
|
57
|
+
* `'operation'`, and `ModelSubmissionHandler` ends that submission with a
|
|
58
|
+
* `setDirtyState` carrying it. A declined operation sends none,
|
|
59
|
+
* so an action of this kind from something else, such as a late submission
|
|
60
|
+
* of the load, cannot pass for the operation's. The driver's session must
|
|
61
|
+
* receive `setDirtyState`.
|
|
62
|
+
*/
|
|
47
63
|
readonly expectedResponseKind: string;
|
|
48
64
|
/**
|
|
49
65
|
* Returns whether the source model gained the element — the adopter reads its
|
package/lib/glsp/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/glsp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/glsp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAsBlF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB,CAAC,OAAO,CAAE,SAAQ,OAAO;IAC5D,uFAAuF;IACvF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,gFAAgF;IAChF,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;IAChC;;;;;OAKG;IACH,UAAU,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACxF;AAED;;;;GAIG;AACH,MAAM,WAAW,uBAAuB,CAAC,OAAO,EAAE,OAAO,SAAS,qBAAqB,CAAC,OAAO,CAAC;IAC7F;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;IAC9C;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC;;;;;;;;;;;;;;;;;;OAkBG;IACH,QAAQ,CAAC,aAAa,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1E;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW,CAAC,OAAO,EAAE,OAAO,SAAS,qBAAqB,CAAC,OAAO,CAAC;IACjF;;;;;;;;OAQG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,gGAAgG;IAChG,QAAQ,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D;;;;;;;;OAQG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,OAAO,CAAC;IACrC,oGAAoG;IACpG,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,6HAA6H;IAC7H,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC;IACzD,oEAAoE;IACpE,QAAQ,CAAC,eAAe,CAAC,EAAE,uBAAuB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;CACvE;AAED,wCAAwC;AACxC,MAAM,WAAW,sBAAsB,CAAC,OAAO,EAAE,OAAO,SAAS,qBAAqB,CAAC,OAAO,CAAC;IAC5F;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,iCAAiC;IACjC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,WAAW,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IAChE,2DAA2D;IAC3D,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC/B;AAoCD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,SAAS,qBAAqB,CAAC,OAAO,CAAC,EACpF,OAAO,EAAE,sBAAsB,CAAC,OAAO,EAAE,OAAO,CAAC,GACjD,gBAAgB,EAAE,CAqEpB"}
|
package/lib/glsp/index.js
CHANGED
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
* head, generic over the adopter's action type `TAction`. The kit imports NO
|
|
12
12
|
* `@eclipse-glsp/*` types: the FIXTURE supplies the native actions
|
|
13
13
|
* (`requestModel()`, `createOperation.action()`) and the kit matches responses
|
|
14
|
-
* by `kind` (a `string`) via the driver's `nextAction`.
|
|
14
|
+
* by `kind` (a `string`) via the driver's `nextAction`. The one field it reads
|
|
15
|
+
* beyond `kind` is the `reason` of the `setDirtyState` that is an operation's
|
|
16
|
+
* receipt.
|
|
15
17
|
*
|
|
16
18
|
* The driver port (`GlspConformanceDriver<TAction>`) is the minimal
|
|
17
19
|
* `start` / `dispatch` / `nextAction` surface that `@hydranium/glsp-server/
|
|
@@ -23,6 +25,34 @@
|
|
|
23
25
|
* its harness.
|
|
24
26
|
*/
|
|
25
27
|
import assert from 'node:assert/strict';
|
|
28
|
+
/** GLSP's `SetDirtyStateAction.KIND`, named here because the kit imports no GLSP type. */
|
|
29
|
+
const SET_DIRTY_STATE_KIND = 'setDirtyState';
|
|
30
|
+
/** The reason GLSP's `OperationActionHandler` submits an operation that ran with, which its dirty state carries. */
|
|
31
|
+
const OPERATION_REASON = 'operation';
|
|
32
|
+
function isOperationReceipt(action) {
|
|
33
|
+
return typeof action === 'object' && action !== null && 'reason' in action && action.reason === OPERATION_REASON;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Resolve once the operation's receipt arrives, passing over a dirty state
|
|
37
|
+
* with any other reason. Rejects with the wait's own failure, which may be a
|
|
38
|
+
* closed connection rather than a missing receipt.
|
|
39
|
+
*/
|
|
40
|
+
async function nextOperationReceipt(driver) {
|
|
41
|
+
for (;;) {
|
|
42
|
+
let dirtyState;
|
|
43
|
+
try {
|
|
44
|
+
dirtyState = await driver.nextAction(SET_DIRTY_STATE_KIND);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
48
|
+
throw new Error(`Waiting for the operation's receipt, a ${SET_DIRTY_STATE_KIND} with reason '${OPERATION_REASON}', failed: ${reason}. ` +
|
|
49
|
+
`If none arrived in time, either no operation ran or the session does not declare ${SET_DIRTY_STATE_KIND}.`, { cause: error });
|
|
50
|
+
}
|
|
51
|
+
if (isOperationReceipt(dirtyState)) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
26
56
|
/**
|
|
27
57
|
* Build the GLSP check battery — per diagram type: `start()` resolves;
|
|
28
58
|
* `RequestModel` responds with the expected kind (+ optional `expectResponse`
|
|
@@ -78,6 +108,7 @@ export function buildGlspChecks(options) {
|
|
|
78
108
|
driver.dispatch(diagram.requestModel());
|
|
79
109
|
await driver.nextAction(diagram.expectedResponseKind);
|
|
80
110
|
driver.dispatch(operation.action(driver));
|
|
111
|
+
await nextOperationReceipt(driver);
|
|
81
112
|
await driver.nextAction(operation.expectedResponseKind);
|
|
82
113
|
assert.ok(await operation.expectMutated(driver), 'expectMutated was false — the create operation did not mutate the source model');
|
|
83
114
|
}
|
package/lib/glsp/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/glsp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/glsp/index.ts"],"names":[],"mappings":"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"}
|
package/lib/glsp/node/index.d.ts
CHANGED
|
@@ -38,8 +38,10 @@ export interface GlspSocketDriverOptions<TAction extends {
|
|
|
38
38
|
* The client's reply to a received action, sent back to the server, or
|
|
39
39
|
* `undefined` for none. A server that lays out on the client finishes a
|
|
40
40
|
* `requestModel` only once its `requestBounds` is answered with
|
|
41
|
-
* `computedBounds`, so a load without this stops half way. A
|
|
42
|
-
*
|
|
41
|
+
* `computedBounds`, so a load without this stops half way. A reply is not a
|
|
42
|
+
* dispatch, as the GLSP client's own is an automatic answer to the server,
|
|
43
|
+
* so it leaves the point `nextAction` matches from where it was. A throw
|
|
44
|
+
* fails every pending and later wait with it.
|
|
43
45
|
*/
|
|
44
46
|
readonly respond?: (action: TAction) => TAction | undefined;
|
|
45
47
|
/** Default bound for {@link GlspConformanceDriver.nextAction}, and the bound on the TCP connect and on each `start()` request. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/glsp/node/index.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAclF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAMzD,yDAAyD;AACzD,MAAM,WAAW,uBAAuB,CAAC,OAAO,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE;IAC3G,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+BAA+B;IAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,yDAAyD;IACzD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9C,8FAA8F;IAC9F,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC;;;;OAIG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/glsp/node/index.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAclF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAMzD,yDAAyD;AACzD,MAAM,WAAW,uBAAuB,CAAC,OAAO,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE;IAC3G,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+BAA+B;IAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,yDAAyD;IACzD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9C,8FAA8F;IAC9F,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC;;;;OAIG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,GAAG,SAAS,CAAC;IAC5D,kIAAkI;IAClI,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,mFAAmF;AACnF,MAAM,WAAW,gBAAgB,CAAC,OAAO,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAE,SAAQ,qBAAqB,CAAC,OAAO,CAAC;IAChH,mFAAmF;IACnF,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;CAC3C;AAgBD;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,OAAO,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACpF,OAAO,EAAE,uBAAuB,CAAC,OAAO,CAAC,GACzC,OAAO,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAqJpC"}
|
package/lib/glsp/node/index.js
CHANGED
|
@@ -47,6 +47,8 @@ export async function connectGlspSocketDriver(options) {
|
|
|
47
47
|
}
|
|
48
48
|
};
|
|
49
49
|
const consumed = new WeakSet();
|
|
50
|
+
// Where the last dispatch fell in `actions`; a wait matches only from here.
|
|
51
|
+
let dispatchedAt = 0;
|
|
50
52
|
const waiters = [];
|
|
51
53
|
// Why no awaited action can arrive any more, once something ended the session.
|
|
52
54
|
let ended;
|
|
@@ -104,9 +106,12 @@ export async function connectGlspSocketDriver(options) {
|
|
|
104
106
|
clientActionKinds: options.clientActionKinds
|
|
105
107
|
}), timeoutMs, 'initializeClientSession');
|
|
106
108
|
},
|
|
107
|
-
dispatch
|
|
109
|
+
dispatch(action) {
|
|
110
|
+
dispatchedAt = actions.length;
|
|
111
|
+
send(action);
|
|
112
|
+
},
|
|
108
113
|
nextAction(kind, waitMs = timeoutMs) {
|
|
109
|
-
const existing = actions.find(action => action.kind === kind && !consumed.has(action));
|
|
114
|
+
const existing = actions.find((action, index) => index >= dispatchedAt && action.kind === kind && !consumed.has(action));
|
|
110
115
|
if (existing) {
|
|
111
116
|
consumed.add(existing);
|
|
112
117
|
return Promise.resolve(existing);
|
|
@@ -132,9 +137,16 @@ export async function connectGlspSocketDriver(options) {
|
|
|
132
137
|
waiters.splice(at, 1);
|
|
133
138
|
}
|
|
134
139
|
// Naming what did arrive tells an operation the server declined
|
|
135
|
-
// (a status or nothing at all) apart from a transport that hangs
|
|
136
|
-
|
|
137
|
-
|
|
140
|
+
// (a status or nothing at all) apart from a transport that hangs,
|
|
141
|
+
// split at the last dispatch, before which no match counts.
|
|
142
|
+
const kindsIn = (from, to) => actions
|
|
143
|
+
.slice(from, to)
|
|
144
|
+
.map(action => action.kind)
|
|
145
|
+
.join(', ') || 'nothing';
|
|
146
|
+
const seen = dispatchedAt === 0
|
|
147
|
+
? `received: ${kindsIn(0)}`
|
|
148
|
+
: `received before the last dispatch, which a wait does not match: ${kindsIn(0, dispatchedAt)}; since: ${kindsIn(dispatchedAt)}`;
|
|
149
|
+
reject(new Error(`No '${kind}' action within ${waitMs}ms on session ${clientSessionId}; ${seen}`));
|
|
138
150
|
}, waitMs);
|
|
139
151
|
waiters.push(waiter);
|
|
140
152
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/glsp/node/index.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,mBAAmB,EAA0B,MAAM,qBAAqB,CAAC;AAGhI,MAAM,wBAAwB,GAAG,OAAO,CAAC;AAEzC,MAAM,kBAAkB,GAAG,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/glsp/node/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hydranium/conformance",
|
|
3
|
-
"version": "1.0.0-next.
|
|
3
|
+
"version": "1.0.0-next.256",
|
|
4
4
|
"description": "Protocol conformance kit (TCK) for hydranium adopters. Per-head slices (/data, /lsp, /glsp) of fixture-parameterized, protocol-only smoke checks an adopter runs against its own server.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"conformance",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"watch": "tsc -b -w --preserveWatchOutput"
|
|
76
76
|
},
|
|
77
77
|
"devDependencies": {
|
|
78
|
-
"@hydranium/protocol": "1.0.0-next.
|
|
78
|
+
"@hydranium/protocol": "1.0.0-next.256",
|
|
79
79
|
"@jest/globals": "^29.7.0",
|
|
80
80
|
"@types/jest": "^29.5.12",
|
|
81
81
|
"esbuild": "^0.28.0",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"vscode-jsonrpc": "9.0.0"
|
|
87
87
|
},
|
|
88
88
|
"peerDependencies": {
|
|
89
|
-
"@hydranium/protocol": "1.0.0-next.
|
|
89
|
+
"@hydranium/protocol": "1.0.0-next.256",
|
|
90
90
|
"@jest/globals": "^29.0.0",
|
|
91
91
|
"vitest": "^4.0.0",
|
|
92
92
|
"vscode-jsonrpc": "^9.0.0"
|
package/src/glsp/index.ts
CHANGED
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
* head, generic over the adopter's action type `TAction`. The kit imports NO
|
|
13
13
|
* `@eclipse-glsp/*` types: the FIXTURE supplies the native actions
|
|
14
14
|
* (`requestModel()`, `createOperation.action()`) and the kit matches responses
|
|
15
|
-
* by `kind` (a `string`) via the driver's `nextAction`.
|
|
15
|
+
* by `kind` (a `string`) via the driver's `nextAction`. The one field it reads
|
|
16
|
+
* beyond `kind` is the `reason` of the `setDirtyState` that is an operation's
|
|
17
|
+
* receipt.
|
|
16
18
|
*
|
|
17
19
|
* The driver port (`GlspConformanceDriver<TAction>`) is the minimal
|
|
18
20
|
* `start` / `dispatch` / `nextAction` surface that `@hydranium/glsp-server/
|
|
@@ -40,7 +42,12 @@ export interface GlspConformanceDriver<TAction> extends Harness {
|
|
|
40
42
|
start(): Promise<void>;
|
|
41
43
|
/** Send an action to the server. Fire-and-forget (GLSP `process` is `void`). */
|
|
42
44
|
dispatch(action: TAction): void;
|
|
43
|
-
/**
|
|
45
|
+
/**
|
|
46
|
+
* Resolve with the next action whose `kind` matches and that arrived after
|
|
47
|
+
* the last {@link dispatch}, or since `start` before any; reject on timeout.
|
|
48
|
+
* Only after the dispatch, so an action an earlier step sent cannot pass for
|
|
49
|
+
* the answer to this one.
|
|
50
|
+
*/
|
|
44
51
|
nextAction<T extends TAction = TAction>(kind: string, timeoutMs?: number): Promise<T>;
|
|
45
52
|
}
|
|
46
53
|
|
|
@@ -64,7 +71,18 @@ export interface GlspCreateOperationSpec<TAction, TDriver extends GlspConformanc
|
|
|
64
71
|
* loaded.
|
|
65
72
|
*/
|
|
66
73
|
readonly action: (driver: TDriver) => TAction;
|
|
67
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* The action kind the server settles the operation with (e.g. a
|
|
76
|
+
* re-`RequestBounds` for client-laid-out diagrams).
|
|
77
|
+
*
|
|
78
|
+
* The kit also waits for the operation's receipt: GLSP's
|
|
79
|
+
* `OperationActionHandler` submits every operation that ran with reason
|
|
80
|
+
* `'operation'`, and `ModelSubmissionHandler` ends that submission with a
|
|
81
|
+
* `setDirtyState` carrying it. A declined operation sends none,
|
|
82
|
+
* so an action of this kind from something else, such as a late submission
|
|
83
|
+
* of the load, cannot pass for the operation's. The driver's session must
|
|
84
|
+
* receive `setDirtyState`.
|
|
85
|
+
*/
|
|
68
86
|
readonly expectedResponseKind: string;
|
|
69
87
|
/**
|
|
70
88
|
* Returns whether the source model gained the element — the adopter reads its
|
|
@@ -140,6 +158,40 @@ export interface GlspConformanceOptions<TAction, TDriver extends GlspConformance
|
|
|
140
158
|
readonly suiteTitle?: string;
|
|
141
159
|
}
|
|
142
160
|
|
|
161
|
+
/** GLSP's `SetDirtyStateAction.KIND`, named here because the kit imports no GLSP type. */
|
|
162
|
+
const SET_DIRTY_STATE_KIND = 'setDirtyState';
|
|
163
|
+
|
|
164
|
+
/** The reason GLSP's `OperationActionHandler` submits an operation that ran with, which its dirty state carries. */
|
|
165
|
+
const OPERATION_REASON = 'operation';
|
|
166
|
+
|
|
167
|
+
function isOperationReceipt(action: unknown): boolean {
|
|
168
|
+
return typeof action === 'object' && action !== null && 'reason' in action && action.reason === OPERATION_REASON;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Resolve once the operation's receipt arrives, passing over a dirty state
|
|
173
|
+
* with any other reason. Rejects with the wait's own failure, which may be a
|
|
174
|
+
* closed connection rather than a missing receipt.
|
|
175
|
+
*/
|
|
176
|
+
async function nextOperationReceipt<TAction>(driver: GlspConformanceDriver<TAction>): Promise<void> {
|
|
177
|
+
for (;;) {
|
|
178
|
+
let dirtyState: TAction;
|
|
179
|
+
try {
|
|
180
|
+
dirtyState = await driver.nextAction(SET_DIRTY_STATE_KIND);
|
|
181
|
+
} catch (error: unknown) {
|
|
182
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
183
|
+
throw new Error(
|
|
184
|
+
`Waiting for the operation's receipt, a ${SET_DIRTY_STATE_KIND} with reason '${OPERATION_REASON}', failed: ${reason}. ` +
|
|
185
|
+
`If none arrived in time, either no operation ran or the session does not declare ${SET_DIRTY_STATE_KIND}.`,
|
|
186
|
+
{ cause: error }
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
if (isOperationReceipt(dirtyState)) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
143
195
|
/**
|
|
144
196
|
* Build the GLSP check battery — per diagram type: `start()` resolves;
|
|
145
197
|
* `RequestModel` responds with the expected kind (+ optional `expectResponse`
|
|
@@ -199,6 +251,7 @@ export function buildGlspChecks<TAction, TDriver extends GlspConformanceDriver<T
|
|
|
199
251
|
driver.dispatch(diagram.requestModel());
|
|
200
252
|
await driver.nextAction(diagram.expectedResponseKind);
|
|
201
253
|
driver.dispatch(operation.action(driver));
|
|
254
|
+
await nextOperationReceipt(driver);
|
|
202
255
|
await driver.nextAction(operation.expectedResponseKind);
|
|
203
256
|
assert.ok(
|
|
204
257
|
await operation.expectMutated(driver),
|
package/src/glsp/node/index.ts
CHANGED
|
@@ -52,8 +52,10 @@ export interface GlspSocketDriverOptions<TAction extends { readonly kind: string
|
|
|
52
52
|
* The client's reply to a received action, sent back to the server, or
|
|
53
53
|
* `undefined` for none. A server that lays out on the client finishes a
|
|
54
54
|
* `requestModel` only once its `requestBounds` is answered with
|
|
55
|
-
* `computedBounds`, so a load without this stops half way. A
|
|
56
|
-
*
|
|
55
|
+
* `computedBounds`, so a load without this stops half way. A reply is not a
|
|
56
|
+
* dispatch, as the GLSP client's own is an automatic answer to the server,
|
|
57
|
+
* so it leaves the point `nextAction` matches from where it was. A throw
|
|
58
|
+
* fails every pending and later wait with it.
|
|
57
59
|
*/
|
|
58
60
|
readonly respond?: (action: TAction) => TAction | undefined;
|
|
59
61
|
/** Default bound for {@link GlspConformanceDriver.nextAction}, and the bound on the TCP connect and on each `start()` request. */
|
|
@@ -103,6 +105,8 @@ export async function connectGlspSocketDriver<TAction extends { readonly kind: s
|
|
|
103
105
|
}
|
|
104
106
|
};
|
|
105
107
|
const consumed = new WeakSet<TAction>();
|
|
108
|
+
// Where the last dispatch fell in `actions`; a wait matches only from here.
|
|
109
|
+
let dispatchedAt = 0;
|
|
106
110
|
const waiters: Array<{ readonly kind: string; readonly resolve: (action: TAction) => void; readonly fail: (error: Error) => void }> = [];
|
|
107
111
|
// Why no awaited action can arrive any more, once something ended the session.
|
|
108
112
|
let ended: { readonly reason: string; readonly cause?: unknown } | undefined;
|
|
@@ -173,10 +177,13 @@ export async function connectGlspSocketDriver<TAction extends { readonly kind: s
|
|
|
173
177
|
);
|
|
174
178
|
},
|
|
175
179
|
|
|
176
|
-
dispatch:
|
|
180
|
+
dispatch(action: TAction): void {
|
|
181
|
+
dispatchedAt = actions.length;
|
|
182
|
+
send(action);
|
|
183
|
+
},
|
|
177
184
|
|
|
178
185
|
nextAction<T extends TAction = TAction>(kind: string, waitMs = timeoutMs): Promise<T> {
|
|
179
|
-
const existing = actions.find(action => action.kind === kind && !consumed.has(action));
|
|
186
|
+
const existing = actions.find((action, index) => index >= dispatchedAt && action.kind === kind && !consumed.has(action));
|
|
180
187
|
if (existing) {
|
|
181
188
|
consumed.add(existing);
|
|
182
189
|
return Promise.resolve(existing as T);
|
|
@@ -202,9 +209,18 @@ export async function connectGlspSocketDriver<TAction extends { readonly kind: s
|
|
|
202
209
|
waiters.splice(at, 1);
|
|
203
210
|
}
|
|
204
211
|
// Naming what did arrive tells an operation the server declined
|
|
205
|
-
// (a status or nothing at all) apart from a transport that hangs
|
|
206
|
-
|
|
207
|
-
|
|
212
|
+
// (a status or nothing at all) apart from a transport that hangs,
|
|
213
|
+
// split at the last dispatch, before which no match counts.
|
|
214
|
+
const kindsIn = (from: number, to?: number): string =>
|
|
215
|
+
actions
|
|
216
|
+
.slice(from, to)
|
|
217
|
+
.map(action => action.kind)
|
|
218
|
+
.join(', ') || 'nothing';
|
|
219
|
+
const seen =
|
|
220
|
+
dispatchedAt === 0
|
|
221
|
+
? `received: ${kindsIn(0)}`
|
|
222
|
+
: `received before the last dispatch, which a wait does not match: ${kindsIn(0, dispatchedAt)}; since: ${kindsIn(dispatchedAt)}`;
|
|
223
|
+
reject(new Error(`No '${kind}' action within ${waitMs}ms on session ${clientSessionId}; ${seen}`));
|
|
208
224
|
}, waitMs);
|
|
209
225
|
waiters.push(waiter);
|
|
210
226
|
});
|