@vnejs/plugins.scenario.point_and_click 0.2.1
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/dist/index.d.ts +1 -0
- package/dist/index.js +15 -0
- package/dist/modules/exec.d.ts +8 -0
- package/dist/modules/exec.js +6 -0
- package/dist/modules/point_and_click.d.ts +14 -0
- package/dist/modules/point_and_click.js +65 -0
- package/dist/modules/scenario.block.d.ts +8 -0
- package/dist/modules/scenario.block.js +22 -0
- package/dist/modules/scenario.module.d.ts +11 -0
- package/dist/modules/scenario.module.js +30 -0
- package/dist/modules/scenario.option.d.ts +8 -0
- package/dist/modules/scenario.option.js +13 -0
- package/dist/types.d.ts +10 -0
- package/dist/types.js +1 -0
- package/dist/utils/point-and-click.d.ts +1 -0
- package/dist/utils/point-and-click.js +1 -0
- package/package.json +52 -0
- package/src/index.ts +18 -0
- package/src/modules/exec.ts +17 -0
- package/src/modules/point_and_click.ts +109 -0
- package/src/modules/scenario.block.ts +38 -0
- package/src/modules/scenario.module.ts +48 -0
- package/src/modules/scenario.option.ts +27 -0
- package/src/tests/exec.test.ts +21 -0
- package/src/tests/point_and_click.test.ts +88 -0
- package/src/tests/scenario.test.ts +111 -0
- package/src/tests/setup.ts +34 -0
- package/src/types.ts +26 -0
- package/src/utils/point-and-click.ts +10 -0
- package/tsconfig.json +9 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "@vnejs/contracts.scenario.point_and_click";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import "@vnejs/contracts.scenario.point_and_click";
|
|
2
|
+
import { regPlugin } from "@vnejs/shared";
|
|
3
|
+
import { CONSTANTS, PARAMS, PLUGIN_NAME, SUBSCRIBE_EVENTS } from "@vnejs/contracts.scenario.point_and_click";
|
|
4
|
+
import { PointAndClick } from "./modules/point_and_click.js";
|
|
5
|
+
import { PointAndClickExec } from "./modules/exec.js";
|
|
6
|
+
import { PointAndClickScenario } from "./modules/scenario.block.js";
|
|
7
|
+
import { PointAndClickScenarioModule } from "./modules/scenario.module.js";
|
|
8
|
+
import { PointAndClickScenarioOption } from "./modules/scenario.option.js";
|
|
9
|
+
regPlugin(PLUGIN_NAME, { constants: CONSTANTS, events: SUBSCRIBE_EVENTS, params: PARAMS }, [
|
|
10
|
+
PointAndClick,
|
|
11
|
+
PointAndClickExec,
|
|
12
|
+
PointAndClickScenario,
|
|
13
|
+
PointAndClickScenarioModule,
|
|
14
|
+
PointAndClickScenarioOption,
|
|
15
|
+
]);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ModuleReplaceExec } from "@vnejs/module.replace.exec";
|
|
2
|
+
import type { PointAndClickPluginConstants, PointAndClickPluginEvents, PointAndClickPluginParams, PointAndClickPluginSettings } from "../types.js";
|
|
3
|
+
import type { PointAndClickExecGetPayload } from "../utils/point-and-click.js";
|
|
4
|
+
export declare class PointAndClickExec extends ModuleReplaceExec<PointAndClickPluginEvents, PointAndClickPluginConstants, PointAndClickPluginSettings, PointAndClickPluginParams, PointAndClickExecGetPayload> {
|
|
5
|
+
name: string;
|
|
6
|
+
EXEC_REG_EVENT: "vne:point_and_click:exec_reg";
|
|
7
|
+
EXEC_GET_EVENT: "vne:point_and_click:exec_get";
|
|
8
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ModuleReplaceExec } from "@vnejs/module.replace.exec";
|
|
2
|
+
export class PointAndClickExec extends ModuleReplaceExec {
|
|
3
|
+
name = "point-and-click.exec";
|
|
4
|
+
EXEC_REG_EVENT = this.EVENTS.POINT_AND_CLICK.EXEC_REG;
|
|
5
|
+
EXEC_GET_EVENT = this.EVENTS.POINT_AND_CLICK.EXEC_GET;
|
|
6
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ModuleCore, type ModuleGlobalStateRegistry } from "@vnejs/module.core";
|
|
2
|
+
import type { PointAndClickPluginConstants, PointAndClickPluginEvents, PointAndClickPluginParams, PointAndClickPluginSettings } from "../types.js";
|
|
3
|
+
import type { PointAndClickEmitPayload, PointAndClickOption, PointAndClickOptionPayload, PointAndClickPluginModelState } from "../utils/point-and-click.js";
|
|
4
|
+
export declare class PointAndClick extends ModuleCore<PointAndClickPluginEvents, PointAndClickPluginConstants, PointAndClickPluginSettings, PointAndClickPluginParams, PointAndClickPluginModelState> {
|
|
5
|
+
name: string;
|
|
6
|
+
subscribe: () => void;
|
|
7
|
+
onEmit: ({ options, args }?: PointAndClickEmitPayload) => Promise<unknown[] | undefined>;
|
|
8
|
+
onSkip: () => Promise<undefined>;
|
|
9
|
+
onOption: ({ index }?: PointAndClickOptionPayload) => Promise<void>;
|
|
10
|
+
onStateSet: (payload: ModuleGlobalStateRegistry) => Promise<unknown[] | undefined>;
|
|
11
|
+
onStateClear: () => Promise<unknown[]> | undefined;
|
|
12
|
+
mapOptions: ({ optionLine: { line, args }, index }: NonNullable<PointAndClickEmitPayload["options"]>[number]) => Promise<PointAndClickOption>;
|
|
13
|
+
getDefaultState: () => PointAndClickPluginModelState;
|
|
14
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { splitLineKeywords, tokenizeExecLine } from "@vnejs/helpers";
|
|
2
|
+
import { ModuleCore } from "@vnejs/module.core";
|
|
3
|
+
export class PointAndClick extends ModuleCore {
|
|
4
|
+
name = "point_and_click";
|
|
5
|
+
subscribe = () => {
|
|
6
|
+
this.on(this.EVENTS.POINT_AND_CLICK.EMIT, this.onEmit);
|
|
7
|
+
this.on(this.EVENTS.POINT_AND_CLICK.OPTION, this.onOption);
|
|
8
|
+
this.on(this.EVENTS.POINT_AND_CLICK.SKIP, this.onSkip);
|
|
9
|
+
this.on(this.EVENTS.STATE.SET, this.onStateSet);
|
|
10
|
+
this.on(this.EVENTS.STATE.CLEAR, this.onStateClear);
|
|
11
|
+
};
|
|
12
|
+
onEmit = async ({ options = [], args = {} } = {}) => {
|
|
13
|
+
this.emitLog({ action: "emit", options, args });
|
|
14
|
+
const mappedOptions = await Promise.all(options.map(this.mapOptions));
|
|
15
|
+
if (!mappedOptions.length || mappedOptions.every((option) => option.hided)) {
|
|
16
|
+
return this.emit(this.EVENTS.POINT_AND_CLICK.SKIP);
|
|
17
|
+
}
|
|
18
|
+
this.updateState({ isShow: true, options: mappedOptions, args });
|
|
19
|
+
return this.emit(this.EVENTS.POINT_AND_CLICK.OPENED);
|
|
20
|
+
};
|
|
21
|
+
onSkip = async () => {
|
|
22
|
+
const { curLine = 0, label } = this.globalState.scenario;
|
|
23
|
+
const lines = (await this.emitOne(this.EVENTS.SCENARIO.LINES_GET, { label }));
|
|
24
|
+
const parentIndent = lines[curLine]?.indent ?? 0;
|
|
25
|
+
let indexShift = 1;
|
|
26
|
+
let curLineObj = lines[curLine + indexShift];
|
|
27
|
+
while (curLineObj && (curLineObj.indent ?? 0) > parentIndent) {
|
|
28
|
+
indexShift++;
|
|
29
|
+
curLineObj = lines[curLine + indexShift];
|
|
30
|
+
}
|
|
31
|
+
this.emitLog({ action: "skip", label, from: curLine, to: curLine + indexShift });
|
|
32
|
+
this.globalState.scenario.curLine = curLine + indexShift;
|
|
33
|
+
return this.emitNext();
|
|
34
|
+
};
|
|
35
|
+
onOption = async ({ index = 0 } = {}) => {
|
|
36
|
+
this.emitLog({ action: "point_and_click", index });
|
|
37
|
+
await this.emit(this.EVENTS.POINT_AND_CLICK.CLOSED);
|
|
38
|
+
this.globalState.scenario.curLine = this.state.options[index].index + 1;
|
|
39
|
+
this.setDefaultState();
|
|
40
|
+
this.emitNext();
|
|
41
|
+
};
|
|
42
|
+
onStateSet = async (payload) => {
|
|
43
|
+
const moduleState = this.getStateSlice(payload);
|
|
44
|
+
if (!moduleState || this.state.isShow === moduleState.isShow)
|
|
45
|
+
return;
|
|
46
|
+
const clonedState = await this.emitClone(moduleState);
|
|
47
|
+
this.setState(clonedState);
|
|
48
|
+
return this.emit(clonedState.isShow ? this.EVENTS.POINT_AND_CLICK.OPENED : this.EVENTS.POINT_AND_CLICK.CLOSED, {
|
|
49
|
+
isForce: true,
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
onStateClear = () => this.emit(this.EVENTS.POINT_AND_CLICK.CLOSED, { isForce: true });
|
|
53
|
+
mapOptions = async ({ optionLine: { line = "", args = {} }, index }) => {
|
|
54
|
+
const [, , icon, x, y] = tokenizeExecLine(line).map(String);
|
|
55
|
+
const result = { index, coords: [x, y].map(Number), icon, ...args };
|
|
56
|
+
await Promise.all(Object.keys(args).map(async (key) => {
|
|
57
|
+
if (typeof args[key] !== "string" || !args[key].startsWith(this.CONST.SCENARIO.LINE_PREFIXES.MODULE))
|
|
58
|
+
return;
|
|
59
|
+
const { line: execLine = "", module = "", keywords = [] } = splitLineKeywords(String(args[key]));
|
|
60
|
+
result[key] = await this.emitOne(this.EVENTS.POINT_AND_CLICK.EXEC_GET, { line: execLine, module, keywords });
|
|
61
|
+
}));
|
|
62
|
+
return result;
|
|
63
|
+
};
|
|
64
|
+
getDefaultState = () => ({ options: [], args: {}, isShow: false });
|
|
65
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ModuleCore, type LineExecHandlerArg } from "@vnejs/module.core";
|
|
2
|
+
import type { PointAndClickPluginConstants, PointAndClickPluginEvents, PointAndClickPluginParams, PointAndClickPluginSettings } from "../types.js";
|
|
3
|
+
export declare class PointAndClickScenario extends ModuleCore<PointAndClickPluginEvents, PointAndClickPluginConstants, PointAndClickPluginSettings, PointAndClickPluginParams> {
|
|
4
|
+
name: string;
|
|
5
|
+
execName: string;
|
|
6
|
+
init: () => Promise<unknown[]> | undefined;
|
|
7
|
+
onLineExec: ({ args }?: LineExecHandlerArg) => Promise<unknown[] | undefined>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ModuleCore } from "@vnejs/module.core";
|
|
2
|
+
export class PointAndClickScenario extends ModuleCore {
|
|
3
|
+
name = "point-and-click.scenario";
|
|
4
|
+
execName = "point-and-click";
|
|
5
|
+
init = () => this.emit(this.EVENTS.SCENARIO.LINE_EXEC_REG, { block: this.execName, handler: this.onLineExec });
|
|
6
|
+
onLineExec = async ({ args = {} } = {}) => {
|
|
7
|
+
const { curLine = 0, label } = this.globalState.scenario;
|
|
8
|
+
const lines = (await this.emitOne(this.EVENTS.SCENARIO.LINES_GET, { label }));
|
|
9
|
+
const optionsArr = [];
|
|
10
|
+
let indexShift = 1;
|
|
11
|
+
let curLineObj = lines[curLine + indexShift];
|
|
12
|
+
while (curLineObj && (curLineObj.indent ?? 0) > (lines[curLine]?.indent ?? 0)) {
|
|
13
|
+
curLineObj.line.startsWith(`% ${this.execName}.option`) &&
|
|
14
|
+
curLineObj.indent === (lines[curLine]?.indent ?? 0) + 2 &&
|
|
15
|
+
optionsArr.push(curLine + indexShift);
|
|
16
|
+
indexShift++;
|
|
17
|
+
curLineObj = lines[curLine + indexShift];
|
|
18
|
+
}
|
|
19
|
+
const options = optionsArr.map((index) => ({ index, optionLine: lines[index] }));
|
|
20
|
+
return this.emit(this.EVENTS.POINT_AND_CLICK.EMIT, { args, options });
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ModuleCore, type LineExecHandlerArg } from "@vnejs/module.core";
|
|
2
|
+
import type { PointAndClickPluginConstants, PointAndClickPluginEvents, PointAndClickPluginParams, PointAndClickPluginSettings } from "../types.js";
|
|
3
|
+
import type { PointAndClickOptionLine } from "../utils/point-and-click.js";
|
|
4
|
+
export declare class PointAndClickScenarioModule extends ModuleCore<PointAndClickPluginEvents, PointAndClickPluginConstants, PointAndClickPluginSettings, PointAndClickPluginParams> {
|
|
5
|
+
name: string;
|
|
6
|
+
execName: string;
|
|
7
|
+
init: () => Promise<unknown[]> | undefined;
|
|
8
|
+
onLineExec: ({ line }?: LineExecHandlerArg) => Promise<undefined>;
|
|
9
|
+
findPreviousBlockLineIndex: (lines: PointAndClickOptionLine[], curLine: number) => number;
|
|
10
|
+
isBlockLine: (line: string) => boolean;
|
|
11
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { tokenizeExecLine } from "@vnejs/helpers";
|
|
2
|
+
import { ModuleCore } from "@vnejs/module.core";
|
|
3
|
+
export class PointAndClickScenarioModule extends ModuleCore {
|
|
4
|
+
name = "point-and-click.scenario.module";
|
|
5
|
+
execName = "point-and-click";
|
|
6
|
+
init = () => this.emit(this.EVENTS.SCENARIO.LINE_EXEC_REG, { module: this.execName, handler: this.onLineExec });
|
|
7
|
+
onLineExec = async ({ line = "" } = {}) => {
|
|
8
|
+
const [action = ""] = tokenizeExecLine(line).map(String);
|
|
9
|
+
if (action !== this.CONST.POINT_AND_CLICK.EXEC_ACTIONS.REPEAT)
|
|
10
|
+
return;
|
|
11
|
+
const { label, curLine } = this.globalState.scenario;
|
|
12
|
+
const lines = (await this.emitOne(this.EVENTS.SCENARIO.LINES_GET, { label }));
|
|
13
|
+
const targetIndex = this.findPreviousBlockLineIndex(lines, curLine);
|
|
14
|
+
if (targetIndex === -1) {
|
|
15
|
+
console.error(`[point-and-click] repeat: no preceding "% ${this.execName}" in label "${label}"`);
|
|
16
|
+
this.globalState.scenario.curLine = curLine + 1;
|
|
17
|
+
return this.emitNext();
|
|
18
|
+
}
|
|
19
|
+
this.emitLog({ action: this.CONST.POINT_AND_CLICK.EXEC_ACTIONS.REPEAT, label, from: curLine, to: targetIndex });
|
|
20
|
+
this.globalState.scenario.curLine = targetIndex;
|
|
21
|
+
return this.emitNext();
|
|
22
|
+
};
|
|
23
|
+
findPreviousBlockLineIndex = (lines, curLine) => {
|
|
24
|
+
for (let i = curLine - 1; i >= 0; i--)
|
|
25
|
+
if (this.isBlockLine(lines[i]?.line ?? ""))
|
|
26
|
+
return i;
|
|
27
|
+
return -1;
|
|
28
|
+
};
|
|
29
|
+
isBlockLine = (line) => line.startsWith(`% ${this.execName}`) && !line.startsWith(`% ${this.execName}.option`);
|
|
30
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ModuleCore } from "@vnejs/module.core";
|
|
2
|
+
import type { PointAndClickPluginConstants, PointAndClickPluginEvents, PointAndClickPluginParams, PointAndClickPluginSettings } from "../types.js";
|
|
3
|
+
export declare class PointAndClickScenarioOption extends ModuleCore<PointAndClickPluginEvents, PointAndClickPluginConstants, PointAndClickPluginSettings, PointAndClickPluginParams> {
|
|
4
|
+
name: string;
|
|
5
|
+
execName: string;
|
|
6
|
+
init: () => Promise<unknown[]> | undefined;
|
|
7
|
+
onLineExec: () => Promise<void>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { getOptionExecShift } from "@vnejs/helpers";
|
|
2
|
+
import { ModuleCore } from "@vnejs/module.core";
|
|
3
|
+
export class PointAndClickScenarioOption extends ModuleCore {
|
|
4
|
+
name = "point-and-click.scenario.option";
|
|
5
|
+
execName = "point-and-click";
|
|
6
|
+
init = () => this.emit(this.EVENTS.SCENARIO.LINE_EXEC_REG, { block: `${this.execName}.option`, handler: this.onLineExec });
|
|
7
|
+
onLineExec = async () => {
|
|
8
|
+
const { label, curLine } = this.globalState.scenario;
|
|
9
|
+
const lines = (await this.emitOne(this.EVENTS.SCENARIO.LINES_GET, { label }));
|
|
10
|
+
this.globalState.scenario.curLine = curLine + getOptionExecShift(lines, curLine);
|
|
11
|
+
this.emitNext();
|
|
12
|
+
};
|
|
13
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ModuleCoreConstants, ModuleCoreEvents, ModuleCoreParams, ModuleCoreSettings } from "@vnejs/module.core";
|
|
2
|
+
import type { PluginName as LogsPluginName, SubscribeEvents as LogsSubscribeEvents } from "@vnejs/contracts.core.logs";
|
|
3
|
+
import type { Constants as ScenarioConstants, PluginName as ScenarioPluginName, SubscribeEvents as ScenarioSubscribeEvents } from "@vnejs/contracts.core.scenario";
|
|
4
|
+
import type { PluginName as StatePluginName, SubscribeEvents as StateSubscribeEvents } from "@vnejs/contracts.core.state";
|
|
5
|
+
import type { PluginName as VendorsPluginName, SubscribeEvents as VendorsSubscribeEvents } from "@vnejs/contracts.core.vendors";
|
|
6
|
+
import type { Constants as PointAndClickConstants, PluginName, SubscribeEvents, Params } from "@vnejs/contracts.scenario.point_and_click";
|
|
7
|
+
export type PointAndClickPluginEvents = ModuleCoreEvents & Record<PluginName, SubscribeEvents> & Record<ScenarioPluginName, ScenarioSubscribeEvents> & Record<StatePluginName, StateSubscribeEvents> & Record<VendorsPluginName, VendorsSubscribeEvents> & Record<LogsPluginName, LogsSubscribeEvents>;
|
|
8
|
+
export type PointAndClickPluginConstants = ModuleCoreConstants & Record<PluginName, PointAndClickConstants> & Record<ScenarioPluginName, ScenarioConstants>;
|
|
9
|
+
export type PointAndClickPluginSettings = ModuleCoreSettings;
|
|
10
|
+
export type PointAndClickPluginParams = ModuleCoreParams & Record<PluginName, Params>;
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { PointAndClickEmitPayload, PointAndClickExecGetPayload, PointAndClickExecRegPayload, PointAndClickOption, PointAndClickOptionLine, PointAndClickOptionPayload, PointAndClickPluginModelState, PointAndClickVisibilityPayload, } from "@vnejs/contracts.scenario.point_and_click";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vnejs/plugins.scenario.point_and_click",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"src",
|
|
18
|
+
"tsconfig.json"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"test": "npx @vnejs/monorepo test",
|
|
22
|
+
"build": "npx @vnejs/monorepo package",
|
|
23
|
+
"publish:major:plugin": "npm run publish:major",
|
|
24
|
+
"publish:minor:plugin": "npm run publish:minor",
|
|
25
|
+
"publish:patch:plugin": "npm run publish:patch",
|
|
26
|
+
"publish:major": "npx @vnejs/monorepo publish major --access public",
|
|
27
|
+
"publish:minor": "npx @vnejs/monorepo publish minor --access public",
|
|
28
|
+
"publish:patch": "npx @vnejs/monorepo publish patch --access public"
|
|
29
|
+
},
|
|
30
|
+
"author": "",
|
|
31
|
+
"license": "ISC",
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@vnejs/contracts.scenario.point_and_click": "~0.2.0"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@vnejs/helpers": "~0.2.0",
|
|
37
|
+
"@vnejs/module.core": "~0.2.0",
|
|
38
|
+
"@vnejs/module.replace.exec": "~0.2.0",
|
|
39
|
+
"@vnejs/shared": "~0.2.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@vnejs/configs.ts-common": "~0.2.0",
|
|
43
|
+
"@vnejs/configs.vitest": "~0.2.0",
|
|
44
|
+
"@vnejs/test-utils": "~0.2.0",
|
|
45
|
+
"@vnejs/contracts.scenario.point_and_click": "~0.2.0",
|
|
46
|
+
"@vnejs/helpers": "~0.2.0",
|
|
47
|
+
"@vnejs/module.core": "~0.2.0",
|
|
48
|
+
"@vnejs/module.replace.exec": "~0.2.0",
|
|
49
|
+
"@vnejs/shared": "~0.2.0",
|
|
50
|
+
"@vnejs/sources-set": "~0.2.0"
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import "@vnejs/contracts.scenario.point_and_click";
|
|
2
|
+
|
|
3
|
+
import { regPlugin } from "@vnejs/shared";
|
|
4
|
+
import { CONSTANTS, PARAMS, PLUGIN_NAME, SUBSCRIBE_EVENTS } from "@vnejs/contracts.scenario.point_and_click";
|
|
5
|
+
|
|
6
|
+
import { PointAndClick } from "./modules/point_and_click.js";
|
|
7
|
+
import { PointAndClickExec } from "./modules/exec.js";
|
|
8
|
+
import { PointAndClickScenario } from "./modules/scenario.block.js";
|
|
9
|
+
import { PointAndClickScenarioModule } from "./modules/scenario.module.js";
|
|
10
|
+
import { PointAndClickScenarioOption } from "./modules/scenario.option.js";
|
|
11
|
+
|
|
12
|
+
regPlugin(PLUGIN_NAME, { constants: CONSTANTS, events: SUBSCRIBE_EVENTS, params: PARAMS }, [
|
|
13
|
+
PointAndClick,
|
|
14
|
+
PointAndClickExec,
|
|
15
|
+
PointAndClickScenario,
|
|
16
|
+
PointAndClickScenarioModule,
|
|
17
|
+
PointAndClickScenarioOption,
|
|
18
|
+
]);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ModuleReplaceExec } from "@vnejs/module.replace.exec";
|
|
2
|
+
|
|
3
|
+
import type { PointAndClickPluginConstants, PointAndClickPluginEvents, PointAndClickPluginParams, PointAndClickPluginSettings } from "../types.js";
|
|
4
|
+
import type { PointAndClickExecGetPayload } from "../utils/point-and-click.js";
|
|
5
|
+
|
|
6
|
+
export class PointAndClickExec extends ModuleReplaceExec<
|
|
7
|
+
PointAndClickPluginEvents,
|
|
8
|
+
PointAndClickPluginConstants,
|
|
9
|
+
PointAndClickPluginSettings,
|
|
10
|
+
PointAndClickPluginParams,
|
|
11
|
+
PointAndClickExecGetPayload
|
|
12
|
+
> {
|
|
13
|
+
name = "point-and-click.exec";
|
|
14
|
+
|
|
15
|
+
EXEC_REG_EVENT = this.EVENTS.POINT_AND_CLICK.EXEC_REG;
|
|
16
|
+
EXEC_GET_EVENT = this.EVENTS.POINT_AND_CLICK.EXEC_GET;
|
|
17
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { splitLineKeywords, tokenizeExecLine } from "@vnejs/helpers";
|
|
2
|
+
import { ModuleCore, type ModuleGlobalStateRegistry } from "@vnejs/module.core";
|
|
3
|
+
|
|
4
|
+
import type { PointAndClickPluginConstants, PointAndClickPluginEvents, PointAndClickPluginParams, PointAndClickPluginSettings } from "../types.js";
|
|
5
|
+
import type {
|
|
6
|
+
PointAndClickEmitPayload,
|
|
7
|
+
PointAndClickExecGetPayload,
|
|
8
|
+
PointAndClickOption,
|
|
9
|
+
PointAndClickOptionLine,
|
|
10
|
+
PointAndClickOptionPayload,
|
|
11
|
+
PointAndClickPluginModelState,
|
|
12
|
+
PointAndClickVisibilityPayload,
|
|
13
|
+
} from "../utils/point-and-click.js";
|
|
14
|
+
|
|
15
|
+
export class PointAndClick extends ModuleCore<
|
|
16
|
+
PointAndClickPluginEvents,
|
|
17
|
+
PointAndClickPluginConstants,
|
|
18
|
+
PointAndClickPluginSettings,
|
|
19
|
+
PointAndClickPluginParams,
|
|
20
|
+
PointAndClickPluginModelState
|
|
21
|
+
> {
|
|
22
|
+
name = "point_and_click";
|
|
23
|
+
|
|
24
|
+
subscribe = () => {
|
|
25
|
+
this.on(this.EVENTS.POINT_AND_CLICK.EMIT, this.onEmit);
|
|
26
|
+
this.on(this.EVENTS.POINT_AND_CLICK.OPTION, this.onOption);
|
|
27
|
+
this.on(this.EVENTS.POINT_AND_CLICK.SKIP, this.onSkip);
|
|
28
|
+
|
|
29
|
+
this.on(this.EVENTS.STATE.SET, this.onStateSet);
|
|
30
|
+
this.on(this.EVENTS.STATE.CLEAR, this.onStateClear);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
onEmit = async ({ options = [], args = {} }: PointAndClickEmitPayload = {}) => {
|
|
34
|
+
this.emitLog({ action: "emit", options, args });
|
|
35
|
+
const mappedOptions = await Promise.all(options.map(this.mapOptions));
|
|
36
|
+
|
|
37
|
+
if (!mappedOptions.length || mappedOptions.every((option) => option.hided)) {
|
|
38
|
+
return this.emit(this.EVENTS.POINT_AND_CLICK.SKIP);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
this.updateState({ isShow: true, options: mappedOptions, args });
|
|
42
|
+
|
|
43
|
+
return this.emit(this.EVENTS.POINT_AND_CLICK.OPENED);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
onSkip = async () => {
|
|
47
|
+
const { curLine = 0, label } = this.globalState.scenario;
|
|
48
|
+
const lines = (await this.emitOne(this.EVENTS.SCENARIO.LINES_GET, { label })) as PointAndClickOptionLine[];
|
|
49
|
+
const parentIndent = lines[curLine]?.indent ?? 0;
|
|
50
|
+
|
|
51
|
+
let indexShift = 1;
|
|
52
|
+
let curLineObj = lines[curLine + indexShift];
|
|
53
|
+
|
|
54
|
+
while (curLineObj && (curLineObj.indent ?? 0) > parentIndent) {
|
|
55
|
+
indexShift++;
|
|
56
|
+
curLineObj = lines[curLine + indexShift];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this.emitLog({ action: "skip", label, from: curLine, to: curLine + indexShift });
|
|
60
|
+
this.globalState.scenario.curLine = curLine + indexShift;
|
|
61
|
+
|
|
62
|
+
return this.emitNext();
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
onOption = async ({ index = 0 }: PointAndClickOptionPayload = {}) => {
|
|
66
|
+
this.emitLog({ action: "point_and_click", index });
|
|
67
|
+
|
|
68
|
+
await this.emit(this.EVENTS.POINT_AND_CLICK.CLOSED);
|
|
69
|
+
|
|
70
|
+
this.globalState.scenario.curLine = this.state.options[index].index + 1;
|
|
71
|
+
this.setDefaultState();
|
|
72
|
+
this.emitNext();
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
onStateSet = async (payload: ModuleGlobalStateRegistry) => {
|
|
76
|
+
const moduleState = this.getStateSlice(payload);
|
|
77
|
+
|
|
78
|
+
if (!moduleState || this.state.isShow === moduleState.isShow) return;
|
|
79
|
+
|
|
80
|
+
const clonedState = await this.emitClone<PointAndClickPluginModelState>(moduleState);
|
|
81
|
+
|
|
82
|
+
this.setState(clonedState);
|
|
83
|
+
|
|
84
|
+
return this.emit(clonedState.isShow ? this.EVENTS.POINT_AND_CLICK.OPENED : this.EVENTS.POINT_AND_CLICK.CLOSED, {
|
|
85
|
+
isForce: true,
|
|
86
|
+
} satisfies PointAndClickVisibilityPayload);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
onStateClear = () => this.emit(this.EVENTS.POINT_AND_CLICK.CLOSED, { isForce: true } satisfies PointAndClickVisibilityPayload);
|
|
90
|
+
|
|
91
|
+
mapOptions = async ({ optionLine: { line = "", args = {} }, index }: NonNullable<PointAndClickEmitPayload["options"]>[number]) => {
|
|
92
|
+
const [, , icon, x, y] = tokenizeExecLine(line).map(String);
|
|
93
|
+
const result: PointAndClickOption = { index, coords: [x, y].map(Number), icon, ...args };
|
|
94
|
+
|
|
95
|
+
await Promise.all(
|
|
96
|
+
Object.keys(args).map(async (key) => {
|
|
97
|
+
if (typeof args[key] !== "string" || !args[key].startsWith(this.CONST.SCENARIO.LINE_PREFIXES.MODULE)) return;
|
|
98
|
+
|
|
99
|
+
const { line: execLine = "", module = "", keywords = [] } = splitLineKeywords(String(args[key]));
|
|
100
|
+
|
|
101
|
+
result[key] = await this.emitOne(this.EVENTS.POINT_AND_CLICK.EXEC_GET, { line: execLine, module, keywords } satisfies PointAndClickExecGetPayload);
|
|
102
|
+
}),
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
return result;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
getDefaultState = (): PointAndClickPluginModelState => ({ options: [], args: {}, isShow: false });
|
|
109
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ModuleCore, type LineExecHandlerArg } from "@vnejs/module.core";
|
|
2
|
+
|
|
3
|
+
import type { PointAndClickPluginConstants, PointAndClickPluginEvents, PointAndClickPluginParams, PointAndClickPluginSettings } from "../types.js";
|
|
4
|
+
import type { PointAndClickEmitPayload, PointAndClickOptionLine } from "../utils/point-and-click.js";
|
|
5
|
+
|
|
6
|
+
export class PointAndClickScenario extends ModuleCore<
|
|
7
|
+
PointAndClickPluginEvents,
|
|
8
|
+
PointAndClickPluginConstants,
|
|
9
|
+
PointAndClickPluginSettings,
|
|
10
|
+
PointAndClickPluginParams
|
|
11
|
+
> {
|
|
12
|
+
name = "point-and-click.scenario";
|
|
13
|
+
|
|
14
|
+
execName = "point-and-click";
|
|
15
|
+
|
|
16
|
+
init = () => this.emit(this.EVENTS.SCENARIO.LINE_EXEC_REG, { block: this.execName, handler: this.onLineExec });
|
|
17
|
+
|
|
18
|
+
onLineExec = async ({ args = {} }: LineExecHandlerArg = {}) => {
|
|
19
|
+
const { curLine = 0, label } = this.globalState.scenario;
|
|
20
|
+
const lines = (await this.emitOne(this.EVENTS.SCENARIO.LINES_GET, { label })) as PointAndClickOptionLine[];
|
|
21
|
+
const optionsArr: number[] = [];
|
|
22
|
+
|
|
23
|
+
let indexShift = 1;
|
|
24
|
+
let curLineObj = lines[curLine + indexShift];
|
|
25
|
+
|
|
26
|
+
while (curLineObj && (curLineObj.indent ?? 0) > (lines[curLine]?.indent ?? 0)) {
|
|
27
|
+
curLineObj.line.startsWith(`% ${this.execName}.option`) &&
|
|
28
|
+
curLineObj.indent === (lines[curLine]?.indent ?? 0) + 2 &&
|
|
29
|
+
optionsArr.push(curLine + indexShift);
|
|
30
|
+
indexShift++;
|
|
31
|
+
curLineObj = lines[curLine + indexShift];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const options = optionsArr.map((index) => ({ index, optionLine: lines[index] }));
|
|
35
|
+
|
|
36
|
+
return this.emit(this.EVENTS.POINT_AND_CLICK.EMIT, { args, options } satisfies PointAndClickEmitPayload);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { tokenizeExecLine } from "@vnejs/helpers";
|
|
2
|
+
import { ModuleCore, type LineExecHandlerArg } from "@vnejs/module.core";
|
|
3
|
+
|
|
4
|
+
import type { PointAndClickPluginConstants, PointAndClickPluginEvents, PointAndClickPluginParams, PointAndClickPluginSettings } from "../types.js";
|
|
5
|
+
import type { PointAndClickOptionLine } from "../utils/point-and-click.js";
|
|
6
|
+
|
|
7
|
+
export class PointAndClickScenarioModule extends ModuleCore<
|
|
8
|
+
PointAndClickPluginEvents,
|
|
9
|
+
PointAndClickPluginConstants,
|
|
10
|
+
PointAndClickPluginSettings,
|
|
11
|
+
PointAndClickPluginParams
|
|
12
|
+
> {
|
|
13
|
+
name = "point-and-click.scenario.module";
|
|
14
|
+
|
|
15
|
+
execName = "point-and-click";
|
|
16
|
+
|
|
17
|
+
init = () => this.emit(this.EVENTS.SCENARIO.LINE_EXEC_REG, { module: this.execName, handler: this.onLineExec });
|
|
18
|
+
|
|
19
|
+
onLineExec = async ({ line = "" }: LineExecHandlerArg = {}) => {
|
|
20
|
+
const [action = ""] = tokenizeExecLine(line).map(String);
|
|
21
|
+
|
|
22
|
+
if (action !== this.CONST.POINT_AND_CLICK.EXEC_ACTIONS.REPEAT) return;
|
|
23
|
+
|
|
24
|
+
const { label, curLine } = this.globalState.scenario;
|
|
25
|
+
const lines = (await this.emitOne(this.EVENTS.SCENARIO.LINES_GET, { label })) as PointAndClickOptionLine[];
|
|
26
|
+
const targetIndex = this.findPreviousBlockLineIndex(lines, curLine);
|
|
27
|
+
|
|
28
|
+
if (targetIndex === -1) {
|
|
29
|
+
console.error(`[point-and-click] repeat: no preceding "% ${this.execName}" in label "${label}"`);
|
|
30
|
+
this.globalState.scenario.curLine = curLine + 1;
|
|
31
|
+
|
|
32
|
+
return this.emitNext();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
this.emitLog({ action: this.CONST.POINT_AND_CLICK.EXEC_ACTIONS.REPEAT, label, from: curLine, to: targetIndex });
|
|
36
|
+
this.globalState.scenario.curLine = targetIndex;
|
|
37
|
+
|
|
38
|
+
return this.emitNext();
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
findPreviousBlockLineIndex = (lines: PointAndClickOptionLine[], curLine: number) => {
|
|
42
|
+
for (let i = curLine - 1; i >= 0; i--) if (this.isBlockLine(lines[i]?.line ?? "")) return i;
|
|
43
|
+
|
|
44
|
+
return -1;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
isBlockLine = (line: string) => line.startsWith(`% ${this.execName}`) && !line.startsWith(`% ${this.execName}.option`);
|
|
48
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { getOptionExecShift } from "@vnejs/helpers";
|
|
2
|
+
import { ModuleCore } from "@vnejs/module.core";
|
|
3
|
+
|
|
4
|
+
import type { PointAndClickPluginConstants, PointAndClickPluginEvents, PointAndClickPluginParams, PointAndClickPluginSettings } from "../types.js";
|
|
5
|
+
import type { PointAndClickOptionLine } from "../utils/point-and-click.js";
|
|
6
|
+
|
|
7
|
+
export class PointAndClickScenarioOption extends ModuleCore<
|
|
8
|
+
PointAndClickPluginEvents,
|
|
9
|
+
PointAndClickPluginConstants,
|
|
10
|
+
PointAndClickPluginSettings,
|
|
11
|
+
PointAndClickPluginParams
|
|
12
|
+
> {
|
|
13
|
+
name = "point-and-click.scenario.option";
|
|
14
|
+
|
|
15
|
+
execName = "point-and-click";
|
|
16
|
+
|
|
17
|
+
init = () => this.emit(this.EVENTS.SCENARIO.LINE_EXEC_REG, { block: `${this.execName}.option`, handler: this.onLineExec });
|
|
18
|
+
|
|
19
|
+
onLineExec = async () => {
|
|
20
|
+
const { label, curLine } = this.globalState.scenario;
|
|
21
|
+
const lines = (await this.emitOne(this.EVENTS.SCENARIO.LINES_GET, { label })) as PointAndClickOptionLine[];
|
|
22
|
+
|
|
23
|
+
this.globalState.scenario.curLine = curLine + getOptionExecShift(lines, curLine);
|
|
24
|
+
|
|
25
|
+
this.emitNext();
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { PointAndClickExec } from "../modules/exec.js";
|
|
4
|
+
import { createPointAndClickTestModule, registerPointAndClickPluginVne, SUBSCRIBE_EVENTS } from "./setup.js";
|
|
5
|
+
|
|
6
|
+
describe("PointAndClickExec", () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
registerPointAndClickPluginVne();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("EXEC_REG stores handler and EXEC_GET invokes it", async () => {
|
|
12
|
+
const { observer } = createPointAndClickTestModule(PointAndClickExec);
|
|
13
|
+
const handler = vi.fn(() => "ok");
|
|
14
|
+
|
|
15
|
+
await observer.emit(SUBSCRIBE_EVENTS.EXEC_REG, { module: "vars", handler });
|
|
16
|
+
const [result] = await observer.emit(SUBSCRIBE_EVENTS.EXEC_GET, { module: "vars", line: "test", keywords: [] });
|
|
17
|
+
|
|
18
|
+
expect(handler).toHaveBeenCalledOnce();
|
|
19
|
+
expect(result).toBe("ok");
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { spyEvent } from "@vnejs/test-utils";
|
|
4
|
+
|
|
5
|
+
import { PointAndClick } from "../modules/point_and_click.js";
|
|
6
|
+
import { createPointAndClickTestModule, registerPointAndClickPluginVne, SCENARIO_EVENTS, SUBSCRIBE_EVENTS } from "./setup.js";
|
|
7
|
+
|
|
8
|
+
describe("PointAndClick", () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
registerPointAndClickPluginVne();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("EMIT stores options and emits OPENED", async () => {
|
|
14
|
+
const { module, observer } = createPointAndClickTestModule(PointAndClick);
|
|
15
|
+
const opened = spyEvent(observer, SUBSCRIBE_EVENTS.OPENED);
|
|
16
|
+
const skip = spyEvent(observer, SUBSCRIBE_EVENTS.SKIP);
|
|
17
|
+
|
|
18
|
+
await observer.emit(SUBSCRIBE_EVENTS.EMIT, {
|
|
19
|
+
options: [{ index: 0, optionLine: { line: "% point-and-click.option star 100 200", args: {} } }],
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
expect((module as PointAndClick).state.isShow).toBe(true);
|
|
23
|
+
expect((module as PointAndClick).state.options[0]).toMatchObject({ index: 0, icon: "star", coords: [100, 200] });
|
|
24
|
+
expect(opened).toHaveBeenCalledOnce();
|
|
25
|
+
expect(skip).not.toHaveBeenCalled();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("EMIT with all hided options emits SKIP and advances past the block", async () => {
|
|
29
|
+
const { module, observer, state } = createPointAndClickTestModule(PointAndClick);
|
|
30
|
+
const opened = spyEvent(observer, SUBSCRIBE_EVENTS.OPENED);
|
|
31
|
+
const skip = spyEvent(observer, SUBSCRIBE_EVENTS.SKIP);
|
|
32
|
+
const next = spyEvent(observer, SCENARIO_EVENTS.NEXT);
|
|
33
|
+
const lines = [
|
|
34
|
+
{ line: "% point-and-click", indent: 0 },
|
|
35
|
+
{ line: "% point-and-click.option star 100 200", indent: 2 },
|
|
36
|
+
{ line: '"after"', indent: 0 },
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
observer.subscribe(SCENARIO_EVENTS.LINES_GET, () => lines);
|
|
40
|
+
state.scenario.curLine = 0;
|
|
41
|
+
|
|
42
|
+
await observer.emit(SUBSCRIBE_EVENTS.EMIT, {
|
|
43
|
+
options: [{ index: 1, optionLine: { line: "% point-and-click.option star 100 200", args: { hided: true } } }],
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
expect(skip).toHaveBeenCalledOnce();
|
|
47
|
+
expect(opened).not.toHaveBeenCalled();
|
|
48
|
+
expect((module as PointAndClick).state.isShow).toBe(false);
|
|
49
|
+
expect(state.scenario.curLine).toBe(2);
|
|
50
|
+
expect(next).toHaveBeenCalledOnce();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("EMIT with empty options emits SKIP and advances past the block", async () => {
|
|
54
|
+
const { observer, state } = createPointAndClickTestModule(PointAndClick);
|
|
55
|
+
const skip = spyEvent(observer, SUBSCRIBE_EVENTS.SKIP);
|
|
56
|
+
const next = spyEvent(observer, SCENARIO_EVENTS.NEXT);
|
|
57
|
+
const lines = [
|
|
58
|
+
{ line: "% point-and-click", indent: 0 },
|
|
59
|
+
{ line: '"after"', indent: 0 },
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
observer.subscribe(SCENARIO_EVENTS.LINES_GET, () => lines);
|
|
63
|
+
state.scenario.curLine = 0;
|
|
64
|
+
|
|
65
|
+
await observer.emit(SUBSCRIBE_EVENTS.EMIT, { options: [] });
|
|
66
|
+
|
|
67
|
+
expect(skip).toHaveBeenCalledOnce();
|
|
68
|
+
expect(state.scenario.curLine).toBe(1);
|
|
69
|
+
expect(next).toHaveBeenCalledOnce();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("OPTION emits CLOSED, updates curLine and resets state", async () => {
|
|
73
|
+
const { module, observer, state } = createPointAndClickTestModule(PointAndClick);
|
|
74
|
+
const closed = spyEvent(observer, SUBSCRIBE_EVENTS.CLOSED);
|
|
75
|
+
|
|
76
|
+
(module as PointAndClick).state = {
|
|
77
|
+
options: [{ index: 3, icon: "star", coords: [0, 0] }],
|
|
78
|
+
args: {},
|
|
79
|
+
isShow: true,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
await observer.emit(SUBSCRIBE_EVENTS.OPTION, { index: 0 });
|
|
83
|
+
|
|
84
|
+
expect(closed).toHaveBeenCalledOnce();
|
|
85
|
+
expect(state.scenario.curLine).toBe(4);
|
|
86
|
+
expect((module as PointAndClick).state.isShow).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { spyEvent } from "@vnejs/test-utils";
|
|
4
|
+
|
|
5
|
+
import { PointAndClickScenario } from "../modules/scenario.block.js";
|
|
6
|
+
import { PointAndClickScenarioModule } from "../modules/scenario.module.js";
|
|
7
|
+
import { PointAndClickScenarioOption } from "../modules/scenario.option.js";
|
|
8
|
+
import { createPointAndClickTestModule, registerPointAndClickPluginVne, SCENARIO_EVENTS, SUBSCRIBE_EVENTS } from "./setup.js";
|
|
9
|
+
|
|
10
|
+
describe("PointAndClickScenario", () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
registerPointAndClickPluginVne();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("init registers % point-and-click block handler", async () => {
|
|
16
|
+
const { module, observer } = createPointAndClickTestModule(PointAndClickScenario, { subscribe: false });
|
|
17
|
+
const lineExecReg = spyEvent(observer, SCENARIO_EVENTS.LINE_EXEC_REG);
|
|
18
|
+
const scenario = module as PointAndClickScenario;
|
|
19
|
+
|
|
20
|
+
await scenario.init();
|
|
21
|
+
|
|
22
|
+
expect(lineExecReg).toHaveBeenCalledOnce();
|
|
23
|
+
expect(lineExecReg).toHaveBeenCalledWith({ block: "point-and-click", handler: scenario.onLineExec });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("onLineExec collects option lines and emits EMIT", async () => {
|
|
27
|
+
const { module, observer } = createPointAndClickTestModule(PointAndClickScenario);
|
|
28
|
+
const emit = spyEvent(observer, SUBSCRIBE_EVENTS.EMIT);
|
|
29
|
+
const lines = [
|
|
30
|
+
{ line: "point-and-click", indent: 0 },
|
|
31
|
+
{ line: "% point-and-click.option star 10 20", indent: 2 },
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
observer.subscribe(SCENARIO_EVENTS.LINES_GET, () => lines);
|
|
35
|
+
|
|
36
|
+
await (module as PointAndClickScenario).onLineExec({ args: { test: 1 } });
|
|
37
|
+
|
|
38
|
+
expect(emit).toHaveBeenCalledOnce();
|
|
39
|
+
expect(emit.mock.calls[0][0]).toMatchObject({ args: { test: 1 }, options: [{ index: 1, optionLine: lines[1] }] });
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("PointAndClickScenarioOption", () => {
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
registerPointAndClickPluginVne();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("init registers % point-and-click.option block handler", async () => {
|
|
49
|
+
const { module, observer } = createPointAndClickTestModule(PointAndClickScenarioOption, { subscribe: false });
|
|
50
|
+
const lineExecReg = spyEvent(observer, SCENARIO_EVENTS.LINE_EXEC_REG);
|
|
51
|
+
const scenario = module as PointAndClickScenarioOption;
|
|
52
|
+
|
|
53
|
+
await scenario.init();
|
|
54
|
+
|
|
55
|
+
expect(lineExecReg).toHaveBeenCalledOnce();
|
|
56
|
+
expect(lineExecReg).toHaveBeenCalledWith({ block: "point-and-click.option", handler: scenario.onLineExec });
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("PointAndClickScenarioModule", () => {
|
|
61
|
+
beforeEach(() => {
|
|
62
|
+
registerPointAndClickPluginVne();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("init registers $ point-and-click module handler", async () => {
|
|
66
|
+
const { module, observer } = createPointAndClickTestModule(PointAndClickScenarioModule, { subscribe: false });
|
|
67
|
+
const lineExecReg = spyEvent(observer, SCENARIO_EVENTS.LINE_EXEC_REG);
|
|
68
|
+
const scenario = module as PointAndClickScenarioModule;
|
|
69
|
+
|
|
70
|
+
await scenario.init();
|
|
71
|
+
|
|
72
|
+
expect(lineExecReg).toHaveBeenCalledOnce();
|
|
73
|
+
expect(lineExecReg).toHaveBeenCalledWith({ module: "point-and-click", handler: scenario.onLineExec });
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("repeat sets curLine to preceding point-and-click block", async () => {
|
|
77
|
+
const { module, observer, state } = createPointAndClickTestModule(PointAndClickScenarioModule);
|
|
78
|
+
const next = spyEvent(observer, SCENARIO_EVENTS.NEXT);
|
|
79
|
+
const lines = [
|
|
80
|
+
{ line: "% point-and-click", indent: 0 },
|
|
81
|
+
{ line: "% point-and-click.option star 10 20", indent: 2 },
|
|
82
|
+
{ line: '"text"', indent: 2 },
|
|
83
|
+
{ line: "$ point-and-click repeat", indent: 2 },
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
observer.subscribe(SCENARIO_EVENTS.LINES_GET, () => lines);
|
|
87
|
+
state.scenario.curLine = 3;
|
|
88
|
+
|
|
89
|
+
await (module as PointAndClickScenarioModule).onLineExec({ line: "repeat" });
|
|
90
|
+
|
|
91
|
+
expect(state.scenario.curLine).toBe(0);
|
|
92
|
+
expect(next).toHaveBeenCalledOnce();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("repeat without preceding block increments curLine and emits NEXT", async () => {
|
|
96
|
+
const { module, observer, state } = createPointAndClickTestModule(PointAndClickScenarioModule);
|
|
97
|
+
const next = spyEvent(observer, SCENARIO_EVENTS.NEXT);
|
|
98
|
+
const consoleError = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
99
|
+
|
|
100
|
+
observer.subscribe(SCENARIO_EVENTS.LINES_GET, () => [{ line: "$ point-and-click repeat", indent: 0 }]);
|
|
101
|
+
state.scenario.curLine = 0;
|
|
102
|
+
|
|
103
|
+
await (module as PointAndClickScenarioModule).onLineExec({ line: "repeat" });
|
|
104
|
+
|
|
105
|
+
expect(state.scenario.curLine).toBe(1);
|
|
106
|
+
expect(next).toHaveBeenCalledOnce();
|
|
107
|
+
expect(consoleError).toHaveBeenCalledOnce();
|
|
108
|
+
|
|
109
|
+
consoleError.mockRestore();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { SUBSCRIBE_EVENTS as SCENARIO_EVENTS } from "@vnejs/contracts.core.scenario";
|
|
2
|
+
import { CONSTANTS, PARAMS, PLUGIN_NAME, SUBSCRIBE_EVENTS } from "@vnejs/contracts.scenario.point_and_click";
|
|
3
|
+
import { SourcesSet } from "@vnejs/sources-set";
|
|
4
|
+
import { createTestModule as baseCreateTestModule, registerCoreVne, registerVnePlugin, stubSilentEvent } from "@vnejs/test-utils";
|
|
5
|
+
|
|
6
|
+
export const registerPointAndClickPluginVne = () => {
|
|
7
|
+
registerCoreVne();
|
|
8
|
+
registerVnePlugin(PLUGIN_NAME, { constants: CONSTANTS, events: SUBSCRIBE_EVENTS, params: PARAMS });
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const createPointAndClickTestModule = <T extends Parameters<typeof baseCreateTestModule>[0]>(
|
|
12
|
+
ModuleClass: T,
|
|
13
|
+
options: Parameters<typeof baseCreateTestModule>[1] = {},
|
|
14
|
+
) => {
|
|
15
|
+
const result = baseCreateTestModule(ModuleClass, {
|
|
16
|
+
state: {
|
|
17
|
+
point_and_click: { options: [], args: {}, isShow: false },
|
|
18
|
+
scenario: { curLine: 0, label: "start" },
|
|
19
|
+
...(options.state ?? {}),
|
|
20
|
+
},
|
|
21
|
+
shared: {
|
|
22
|
+
viewForceAnimationSources: new SourcesSet(),
|
|
23
|
+
...(options.shared ?? {}),
|
|
24
|
+
},
|
|
25
|
+
...options,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
stubSilentEvent(result.observer, SUBSCRIBE_EVENTS.OPENED);
|
|
29
|
+
stubSilentEvent(result.observer, SUBSCRIBE_EVENTS.CLOSED);
|
|
30
|
+
|
|
31
|
+
return result;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export { CONSTANTS, SUBSCRIBE_EVENTS, PARAMS, PLUGIN_NAME, SCENARIO_EVENTS };
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ModuleCoreConstants, ModuleCoreEvents, ModuleCoreParams, ModuleCoreSettings } from "@vnejs/module.core";
|
|
2
|
+
|
|
3
|
+
import type { PluginName as LogsPluginName, SubscribeEvents as LogsSubscribeEvents } from "@vnejs/contracts.core.logs";
|
|
4
|
+
import type {
|
|
5
|
+
Constants as ScenarioConstants,
|
|
6
|
+
PluginName as ScenarioPluginName,
|
|
7
|
+
SubscribeEvents as ScenarioSubscribeEvents,
|
|
8
|
+
} from "@vnejs/contracts.core.scenario";
|
|
9
|
+
import type { PluginName as StatePluginName, SubscribeEvents as StateSubscribeEvents } from "@vnejs/contracts.core.state";
|
|
10
|
+
import type { PluginName as VendorsPluginName, SubscribeEvents as VendorsSubscribeEvents } from "@vnejs/contracts.core.vendors";
|
|
11
|
+
import type { Constants as PointAndClickConstants, PluginName, SubscribeEvents, Params } from "@vnejs/contracts.scenario.point_and_click";
|
|
12
|
+
|
|
13
|
+
export type PointAndClickPluginEvents = ModuleCoreEvents &
|
|
14
|
+
Record<PluginName, SubscribeEvents> &
|
|
15
|
+
Record<ScenarioPluginName, ScenarioSubscribeEvents> &
|
|
16
|
+
Record<StatePluginName, StateSubscribeEvents> &
|
|
17
|
+
Record<VendorsPluginName, VendorsSubscribeEvents> &
|
|
18
|
+
Record<LogsPluginName, LogsSubscribeEvents>;
|
|
19
|
+
|
|
20
|
+
export type PointAndClickPluginConstants = ModuleCoreConstants &
|
|
21
|
+
Record<PluginName, PointAndClickConstants> &
|
|
22
|
+
Record<ScenarioPluginName, ScenarioConstants>;
|
|
23
|
+
|
|
24
|
+
export type PointAndClickPluginSettings = ModuleCoreSettings;
|
|
25
|
+
|
|
26
|
+
export type PointAndClickPluginParams = ModuleCoreParams & Record<PluginName, Params>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
PointAndClickEmitPayload,
|
|
3
|
+
PointAndClickExecGetPayload,
|
|
4
|
+
PointAndClickExecRegPayload,
|
|
5
|
+
PointAndClickOption,
|
|
6
|
+
PointAndClickOptionLine,
|
|
7
|
+
PointAndClickOptionPayload,
|
|
8
|
+
PointAndClickPluginModelState,
|
|
9
|
+
PointAndClickVisibilityPayload,
|
|
10
|
+
} from "@vnejs/contracts.scenario.point_and_click";
|