Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

start playing with hat tests #1815

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# do not modify line endings of our hat test golden files
packages/cursorless-engine/src/test/fixtures/hat-stats/*.golden -text
packages/cursorless-engine/src/test/fixtures/hat-stats/*.stats -text
17 changes: 17 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -156,6 +156,23 @@
"!**/node_modules/**"
]
},
{
"name": "Update fixtures, unit tests only",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/packages/test-harness/out/scripts/runUnitTestsOnly",
"env": {
"CURSORLESS_TEST": "true",
"CURSORLESS_TEST_UPDATE_FIXTURES": "true",
"CURSORLESS_REPO_ROOT": "${workspaceFolder}"
},
"outFiles": ["${workspaceFolder}/**/out/**/*.js"],
"preLaunchTask": "${defaultBuildTask}",
"resolveSourceMapLocations": [
"${workspaceFolder}/**",
"!**/node_modules/**"
]
},
{
"name": "Docusaurus start",
"type": "node",
1 change: 1 addition & 0 deletions packages/common/package.json
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@
"@types/lodash": "4.14.181",
"@types/mocha": "^8.0.4",
"@types/sinon": "^10.0.2",
"fast-check": "3.12.0",
"js-yaml": "^4.1.0",
"mocha": "^10.2.0",
"sinon": "^11.1.1"
1 change: 1 addition & 0 deletions packages/common/src/index.ts
Original file line number Diff line number Diff line change
@@ -87,3 +87,4 @@ export * from "./getFakeCommandServerApi";
export * from "./types/TestCaseFixture";
export * from "./util/getEnvironmentVariableStrict";
export * from "./util/CompositeKeyDefaultMap";
export { MockTextDocument, MockTextEditor } from "./testUtil/mockEditor";
78 changes: 78 additions & 0 deletions packages/common/src/testUtil/mockEditor.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import * as assert from "assert";
import { MockTextDocument, Range } from "..";
import * as fc from "fast-check";

suite("mockEditor", () => {
test("basic", () => {
const s = "abc\n\n123\n";
const doc: MockTextDocument = new MockTextDocument(
"test.txt",
"plaintext",
s,
);

for (let i = 0; i < s.length; i++) {
const pos = doc.positionAt(i);
const offset = doc.offsetAt(pos);
assert.equal(offset, i);
}
const line0 = doc.lineAt(0);
assert.equal(line0.text, "abc");
assert.equal(line0.firstNonWhitespaceCharacterIndex, 0);
assert.equal(line0.isEmptyOrWhitespace, false);
assert.equal(line0.lineNumber, 0);
assert.ok(line0.range.isEqual(new Range(0, 0, 0, 3)));
assert.equal(line0.rangeIncludingLineBreak.start.character, 0);
assert.equal(line0.lastNonWhitespaceCharacterIndex, 2);

const line1 = doc.lineAt(1);
assert.equal(line1.text, "");
assert.equal(line1.firstNonWhitespaceCharacterIndex, 0);
assert.equal(line1.isEmptyOrWhitespace, true);
assert.equal(line1.lineNumber, 1);
assert.ok(line1.range.isEqual(new Range(1, 0, 1, 0)));
assert.equal(line1.rangeIncludingLineBreak.start.character, 0);
assert.equal(line1.lastNonWhitespaceCharacterIndex, 0);
});

test("fastcheck", () => {
fc.assert(
fc.property(fc.string(), (contents) => {
const doc: MockTextDocument = new MockTextDocument(
"test.txt",
"plaintext",
contents,
);
let tot: number = 0;
for (let lineno = 0; lineno < doc.lineCount; lineno++) {
const line = doc.lineAt(lineno);
tot += line.rangeIncludingLineBreak.end.character;
assert.equal(line.lineNumber, lineno);
assert.equal(line.range.start.line, lineno);
assert.equal(line.range.end.line, lineno);
assert.equal(line.rangeIncludingLineBreak.start.line, lineno);
assert.equal(line.rangeIncludingLineBreak.end.line, lineno);
assert.equal(
line.rangeIncludingLineBreak.end.character,
line.text.length,
);
assert.equal(
line.rangeIncludingLineBreak.end.character,
line.range.end.character,
);
}
assert.equal(tot, contents.length);

for (let i = 0; i < contents.length; i++) {
const pos = doc.positionAt(i);
// positions must be within the range of a line
assert.ok(pos.character <= doc.lineAt(pos.line).range.end.character);
const offset = doc.offsetAt(pos);
// positionAt and offsetAt are inverses
assert.equal(offset, i);
return true;
}
}),
);
});
});
169 changes: 169 additions & 0 deletions packages/common/src/testUtil/mockEditor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import { URI } from "vscode-uri";
import {
EndOfLine,
Position,
Range,
Selection,
TextDocument,
TextEditor,
TextEditorOptions,
TextLine,
} from "..";

// See the TextLine, TextEditor, and TextDocument interfaces
// for documentation of these classes and their fields.

export class MockTextLine implements TextLine {
readonly lineNumber: number;
readonly text: string;
readonly range: Range;
readonly rangeIncludingLineBreak: Range;
readonly firstNonWhitespaceCharacterIndex: number;
readonly lastNonWhitespaceCharacterIndex: number;
readonly isEmptyOrWhitespace: boolean;

constructor(lineNumber: number, text: string) {
if (lineNumber < 0) {
throw new Error("lineNumber must be non-negative");
}
this.lineNumber = lineNumber;
// capture any trailing \r\n or \n as eol (possibly neither is present)
const eol = text.match(/(\r?\n)$/)?.[1] ?? "";
if (eol.length > 0) {
this.text = text.slice(0, -eol.length);
} else {
this.text = text;
}
this.range = new Range(
this.lineNumber,
0,
this.lineNumber,
this.text.length,
);
this.rangeIncludingLineBreak = new Range(
this.lineNumber,
0,
this.lineNumber,
this.text.length + eol.length,
);
const first = this.text.search(/\S/);
this.firstNonWhitespaceCharacterIndex =
first === -1 ? this.text.length : first;
const all = this.text.match(/\S/g);
this.lastNonWhitespaceCharacterIndex = all
? this.text.lastIndexOf(all[all.length - 1])
: 0;
this.isEmptyOrWhitespace =
this.firstNonWhitespaceCharacterIndex === this.text.length;
}
}

export class MockTextDocument implements TextDocument {
readonly uri: URI;
readonly languageId: string;
readonly version: number;
readonly range: Range;
readonly eol: EndOfLine;
private lines: MockTextLine[];
private contents: string;

constructor(filename: string, languageId: string, contents: string) {
this.uri = URI.file(filename);
this.languageId = languageId;
this.version = 1;
this.contents = contents;
const rawLines: string[] = contents.match(/[^\n]*\n|[^\n]+/g) ?? [];
this.lines = rawLines.map((line, i) => {
return new MockTextLine(i, line);
});
if (this.lines.length === 0) {
this.range = new Range(0, 0, 0, 0);
} else {
const lastLineRange = this.lines[this.lines.length - 1].range;
this.range = new Range(
0,
0,
lastLineRange.end.line,
lastLineRange.end.character,
);
}
this.eol = "LF";
}

get lineCount(): number {
return this.lines.length;
}

public lineAt(x: number | Position): TextLine {
if (typeof x === "number") {
return this.lines[x];
}
return this.lines[x.line];
}

public offsetAt(position: Position): number {
let offset = 0;
for (let i = 0; i < position.line; i++) {
offset += this.lineAt(i).rangeIncludingLineBreak.end.character;
}
offset += position.character;
return offset;
}

public positionAt(offset: number): Position {
let line = 0;
while (offset >= this.lineAt(line).rangeIncludingLineBreak.end.character) {
offset -= this.lineAt(line).rangeIncludingLineBreak.end.character;
line++;
}
return new Position(line, offset);
}

public getText(range?: Range): string {
if (range === undefined) {
return this.contents;
}
const startOffset = this.offsetAt(range.start);
const endOffset = this.offsetAt(range.end);
return this.contents.slice(startOffset, endOffset);
}
}

export class MockTextEditor implements TextEditor {
public primarySelection: Selection;
readonly id: string;
readonly document: TextDocument;
readonly options: TextEditorOptions;
readonly isActive: boolean;

constructor(document: TextDocument, active: boolean) {
this.id = document.uri.toString();
this.document = document;
this.primarySelection = new Selection(0, 0, 0, 0);
this.options = new DefaultTextEditorOptions();
this.isActive = active;
// TODO: support visible ranges, multiple selections, options
}

get visibleRanges(): Range[] {
return [this.document.range];
}

get selections(): Selection[] {
return [this.primarySelection];
}

isEqual(other: TextEditor): boolean {
return this.id === other.id;
}
}

class DefaultTextEditorOptions implements TextEditorOptions {
get tabSize(): number | string {
return 4;
}

get insertSpaces(): boolean | string {
return true;
}
}
11 changes: 11 additions & 0 deletions packages/common/src/types/Range.ts
Original file line number Diff line number Diff line change
@@ -64,6 +64,17 @@ export class Range {
return this.start.isEqual(this.end);
}

/**
* Check if this range is equal to `other`.
*
* @param other A range.
* @return `true` if the start and end of the given range are equal to
* the start and end of this range.
*/
public isEqual(other: Range): boolean {
return this.start.isEqual(other.start) && this.end.isEqual(other.end);
}

/**
* `true` if `start.line` and `end.line` are equal.
*/
Loading