Skip to content

Commit 9993331

Browse files
authored
Add support for testing our Talon api; test "phones" and "format" (#1728)
- Fixes #1721 ## Checklist - [ ] I have added [tests](https://www.cursorless.org/docs/contributing/test-case-recorder/) - [ ] I have updated the [docs](https://github.com/cursorless-dev/cursorless/tree/main/docs) and [cheatsheet](https://github.com/cursorless-dev/cursorless/tree/main/cursorless-talon/src/cheatsheet) - [ ] I have not broken the cheatsheet
1 parent 41e0c26 commit 9993331

File tree

9 files changed

+279
-52
lines changed

9 files changed

+279
-52
lines changed

.vscode/launch.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,23 @@
5959
"!**/node_modules/**"
6060
]
6161
},
62+
{
63+
"name": "Talon tests subset",
64+
"type": "node",
65+
"request": "launch",
66+
"program": "${workspaceFolder}/packages/test-harness/out/scripts/runTalonTests",
67+
"env": {
68+
"CURSORLESS_TEST": "true",
69+
"CURSORLESS_RUN_TEST_SUBSET": "true",
70+
"CURSORLESS_REPO_ROOT": "${workspaceFolder}"
71+
},
72+
"outFiles": ["${workspaceFolder}/**/out/**/*.js"],
73+
"preLaunchTask": "${defaultBuildTask}",
74+
"resolveSourceMapLocations": [
75+
"${workspaceFolder}/**",
76+
"!**/node_modules/**"
77+
]
78+
},
6279
{
6380
"name": "Unit tests only",
6481
"type": "node",
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
mode: user.cursorless_spoken_form_test
2+
tag: user.cursorless
3+
-
4+
5+
# For testing our public api
6+
test api command <user.cursorless_target>:
7+
user.cursorless_command("setSelection", cursorless_target)
8+
test api insert snippet:
9+
user.cursorless_insert_snippet("Hello, $foo! My name is $bar!")
10+
test api insert snippet by name:
11+
user.cursorless_insert_snippet_by_name("functionDeclaration")
12+
test api wrap with snippet <user.cursorless_target>:
13+
user.cursorless_wrap_with_snippet("Hello, $foo! My name is $bar!", cursorless_target, "foo", "statement")
14+
test api wrap with snippet by name <user.cursorless_target>:
15+
user.cursorless_wrap_with_snippet_by_name("functionDeclaration", "body", cursorless_target)

cursorless-talon-dev/src/spoken_form_test.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import json
2-
from typing import Any
2+
from typing import Any, Optional
33

44
from talon import Context, Module, actions, scope
55

@@ -31,6 +31,8 @@
3131
# Keeps a list of commands run over the course of a spoken form test
3232
commands_run = []
3333

34+
mockedGetValue = ""
35+
3436

3537
@ctx.action_class("user")
3638
class UserActions:
@@ -51,6 +53,7 @@ def private_cursorless_run_rpc_command_get(
5153
command_id: str, arg1: Any, arg2: Any = None
5254
) -> Any:
5355
commands_run.append(arg1)
56+
return mockedGetValue
5457

5558

5659
@mod.action_class
@@ -79,10 +82,14 @@ def private_cursorless_spoken_form_test_mode(enable: bool):
7982
"Cursorless spoken form tests are done. Talon microphone is re-enabled."
8083
)
8184

82-
def private_cursorless_spoken_form_test(phrase: str):
85+
def private_cursorless_spoken_form_test(
86+
phrase: str, mockedGetValue_: Optional[str]
87+
):
8388
"""Run Cursorless spoken form test"""
84-
global commands_run
89+
global commands_run, mockedGetValue
8590
commands_run = []
91+
if mockedGetValue_:
92+
mockedGetValue = json.loads(mockedGetValue_)
8693

8794
try:
8895
actions.mimic(phrase)

cursorless-talon/src/actions/get_text.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,18 @@ def cursorless_get_text_action(
1111
ensure_single_target: Optional[bool] = None,
1212
) -> list[str]:
1313
"""Get target texts"""
14+
options = {}
15+
16+
if show_decorations is not None:
17+
options["showDecorations"] = show_decorations
18+
19+
if ensure_single_target is not None:
20+
options["ensureSingleTarget"] = ensure_single_target
21+
1422
return actions.user.private_cursorless_command_get(
1523
{
1624
"name": "getText",
17-
"options": {
18-
"showDecorations": show_decorations,
19-
"ensureSingleTarget": ensure_single_target,
20-
},
25+
"options": options,
2126
"target": target,
2227
}
2328
)
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { ActionDescriptor } from "@cursorless/common";
2+
import { multiActionSpokenFormTest } from "./spokenFormTest";
3+
4+
const getTextAction: ActionDescriptor = {
5+
name: "getText",
6+
target: {
7+
type: "primitive",
8+
mark: { type: "cursor" },
9+
},
10+
options: {
11+
showDecorations: false,
12+
},
13+
};
14+
15+
const phonesReplaceAction: ActionDescriptor = {
16+
name: "replace",
17+
destination: {
18+
type: "primitive",
19+
insertionMode: "to",
20+
target: {
21+
type: "primitive",
22+
mark: { type: "cursor" },
23+
},
24+
},
25+
replaceWith: ["beet"],
26+
};
27+
28+
const reformatReplaceAction: ActionDescriptor = {
29+
name: "replace",
30+
destination: {
31+
type: "primitive",
32+
insertionMode: "to",
33+
target: {
34+
type: "primitive",
35+
mark: { type: "cursor" },
36+
},
37+
},
38+
replaceWith: ["helloWorld"],
39+
};
40+
41+
/**
42+
* These test actions that are implemented Talon-side using multiple steps
43+
*/
44+
export const multiActionFixture = [
45+
multiActionSpokenFormTest(
46+
"phones this",
47+
[getTextAction, phonesReplaceAction],
48+
["beat"],
49+
),
50+
multiActionSpokenFormTest(
51+
"format camel at this",
52+
[getTextAction, reformatReplaceAction],
53+
["hello_world"],
54+
),
55+
];
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { ActionDescriptor, CommandV6 } from "@cursorless/common";
2+
3+
export interface SpokenFormTest {
4+
/**
5+
* The spoken form that should be tested. Will be passed to Talon's
6+
* `actions.mimic` action.
7+
*/
8+
spokenForm: string;
9+
10+
/**
11+
* If this is set, we will mock the return value of the
12+
* `user.private_cursorless_run_rpc_command_get` action to return the given
13+
* value.
14+
*/
15+
mockedGetValue: unknown | undefined;
16+
17+
/**
18+
* The sequence of Cursorless commands that should be executed when
19+
* {@link spokenForm} is spoken.
20+
*/
21+
commands: CommandV6[];
22+
}
23+
24+
export function spokenFormTest(
25+
spokenForm: string,
26+
action: ActionDescriptor,
27+
): SpokenFormTest {
28+
return {
29+
spokenForm,
30+
mockedGetValue: undefined,
31+
commands: [command(spokenForm, action)],
32+
};
33+
}
34+
35+
export function multiActionSpokenFormTest(
36+
spokenForm: string,
37+
actions: ActionDescriptor[],
38+
mockedGetValue?: unknown,
39+
): SpokenFormTest {
40+
return {
41+
spokenForm,
42+
mockedGetValue,
43+
commands: actions.map((action) => command(spokenForm, action)),
44+
};
45+
}
46+
47+
function command(spokenForm: string, action: ActionDescriptor): CommandV6 {
48+
return {
49+
version: 6,
50+
spokenForm,
51+
usePrePhraseSnapshot: true,
52+
action,
53+
};
54+
}

packages/cursorless-engine/src/test/fixtures/spokenForms.fixture.ts renamed to packages/cursorless-engine/src/test/fixtures/synonymousSpokenForms.fixture.ts

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { ActionDescriptor, CommandV6 } from "@cursorless/common";
1+
import { ActionDescriptor } from "@cursorless/common";
2+
import { spokenFormTest } from "./spokenFormTest";
23

34
const verticalRangeAction: ActionDescriptor = {
45
name: "setSelection",
@@ -41,23 +42,11 @@ const tokenForwardAction: ActionDescriptor = {
4142
* pick one in our spoken form generator, meaning we can't test the other in our
4243
* Talon tests by relying on our recorded test fixtures alone.
4344
*/
44-
export const spokenFormsFixture: Required<CommandV6>[] = [
45-
command("take air slice past bat", verticalRangeAction),
46-
command("take air slice bat", verticalRangeAction),
45+
export const synonymousSpokenFormsFixture = [
46+
spokenFormTest("take air slice past bat", verticalRangeAction),
47+
spokenFormTest("take air slice bat", verticalRangeAction),
4748

48-
command("take one tokens forward", tokenForwardAction),
49-
command("take one tokens", tokenForwardAction),
50-
command("take token forward", tokenForwardAction),
49+
spokenFormTest("take one tokens forward", tokenForwardAction),
50+
spokenFormTest("take one tokens", tokenForwardAction),
51+
spokenFormTest("take token forward", tokenForwardAction),
5152
];
52-
53-
function command(
54-
spokenForm: string,
55-
action: ActionDescriptor,
56-
): Required<CommandV6> {
57-
return {
58-
version: 6,
59-
spokenForm,
60-
usePrePhraseSnapshot: true,
61-
action,
62-
};
63-
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { ActionDescriptor } from "@cursorless/common";
2+
import { spokenFormTest } from "./spokenFormTest";
3+
4+
// See cursorless-talon-dev/src/cursorless_test.talon
5+
const setSelectionAction: ActionDescriptor = {
6+
name: "setSelection",
7+
target: {
8+
type: "primitive",
9+
mark: { type: "cursor" },
10+
},
11+
};
12+
const insertSnippetAction: ActionDescriptor = {
13+
name: "insertSnippet",
14+
destination: { type: "implicit" },
15+
snippetDescription: {
16+
type: "custom",
17+
body: "Hello, $foo! My name is $bar!",
18+
},
19+
};
20+
const insertSnippetByNameAction: ActionDescriptor = {
21+
name: "insertSnippet",
22+
destination: { type: "implicit" },
23+
snippetDescription: {
24+
type: "named",
25+
name: "functionDeclaration",
26+
},
27+
};
28+
const wrapWithSnippetAction: ActionDescriptor = {
29+
name: "wrapWithSnippet",
30+
target: {
31+
type: "primitive",
32+
mark: { type: "cursor" },
33+
},
34+
snippetDescription: {
35+
type: "custom",
36+
body: "Hello, $foo! My name is $bar!",
37+
variableName: "foo",
38+
scopeType: { type: "statement" },
39+
},
40+
};
41+
const wrapWithSnippetByNameAction: ActionDescriptor = {
42+
name: "wrapWithSnippet",
43+
target: {
44+
type: "primitive",
45+
mark: { type: "cursor" },
46+
},
47+
snippetDescription: {
48+
type: "named",
49+
name: "functionDeclaration",
50+
variableName: "body",
51+
},
52+
};
53+
54+
/**
55+
* These test our Talon api using dummy spoken forms defined in
56+
* cursorless-talon-dev/src/cursorless_test.talon
57+
*/
58+
export const talonApiFixture = [
59+
spokenFormTest("test api command this", setSelectionAction),
60+
spokenFormTest("test api insert snippet", insertSnippetAction),
61+
spokenFormTest("test api insert snippet by name", insertSnippetByNameAction),
62+
spokenFormTest("test api wrap with snippet this", wrapWithSnippetAction),
63+
spokenFormTest(
64+
"test api wrap with snippet by name this",
65+
wrapWithSnippetByNameAction,
66+
),
67+
];

0 commit comments

Comments
 (0)