-
Notifications
You must be signed in to change notification settings - Fork 226
Expand file tree
/
Copy pathastViewer.test.ts
More file actions
162 lines (141 loc) · 4.71 KB
/
astViewer.test.ts
File metadata and controls
162 lines (141 loc) · 4.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import * as fs from 'fs-extra';
import * as chai from 'chai';
import * as chaiAsPromised from 'chai-as-promised';
import * as sinon from 'sinon';
import * as yaml from 'js-yaml';
import { AstViewer, AstItem } from '../../astViewer';
import { commands, Range, Uri } from 'vscode';
import { DatabaseItem } from '../../databases';
import { testDisposeHandler } from '../test-dispose-handler';
chai.use(chaiAsPromised);
const expect = chai.expect;
describe('AstViewer', () => {
let astRoots: AstItem[];
let viewer: AstViewer | undefined;
let sandbox: sinon.SinonSandbox;
beforeEach(async () => {
sandbox = sinon.createSandbox();
// the ast is stored in yaml because there are back pointers
// making a json representation impossible.
// The complication here is that yaml files are not copied into the 'out' directory by tsc.
astRoots = await buildAst();
sandbox.stub(commands, 'registerCommand');
sandbox.stub(commands, 'executeCommand');
});
afterEach(() => {
sandbox.restore();
if (viewer) {
viewer.dispose(testDisposeHandler);
viewer = undefined;
}
});
it('should update the viewer roots', () => {
const item = {} as DatabaseItem;
viewer = new AstViewer();
viewer.updateRoots(astRoots, item, Uri.file('def/abc'));
expect((viewer as any).treeDataProvider.roots).to.eq(astRoots);
expect((viewer as any).treeDataProvider.db).to.eq(item);
expect((viewer as any).treeView.message).to.eq('AST for abc');
});
it('should update the tree selection based on a change in the editor selection', () => {
// Should select the namespace
doSelectionTest(astRoots[0], astRoots[0].fileLocation?.range);
});
it('should select an AssignExpr', () => {
// this one is interesting because it spans a couple of other nodes
const expr = findNodeById(300, astRoots);
expect(expr.label).to.eq('[AssignExpr] ... = ...');
doSelectionTest(expr, expr.fileLocation?.range);
});
it('should select nothing because of no overlap in range', () => {
doSelectionTest(undefined, new Range(2, 3, 4, 5));
});
it('should select nothing because of different file', () => {
doSelectionTest(undefined, astRoots[0].fileLocation?.range, Uri.file('def'));
});
const defaultUri = Uri.file('def/abc');
function doSelectionTest(
expectedSelection: any,
selectionRange: Range | undefined,
fileUri = defaultUri
) {
const item = {} as DatabaseItem;
viewer = new AstViewer();
viewer.updateRoots(astRoots, item, defaultUri);
const spy = sandbox.spy();
(viewer as any).treeView.reveal = spy;
Object.defineProperty((viewer as any).treeView, 'visible', {
value: true
});
const mockEvent = createMockEvent(selectionRange, fileUri);
(viewer as any).updateTreeSelection(mockEvent);
if (expectedSelection) {
expect(spy).to.have.been.calledWith(expectedSelection);
} else {
expect(spy).not.to.have.been.called;
}
}
function createMockEvent(
selectionRange: Range | undefined,
uri: Uri,
) {
return {
selections: [{
anchor: selectionRange?.start,
active: selectionRange?.end
}],
textEditor: {
document: {
uri: {
fsPath: uri.fsPath
}
}
}
};
}
function findNodeById(id: number, ast: any): any {
if (Array.isArray(ast)) {
for (const elt of ast) {
const candidate = findNodeById(id, elt);
if (candidate) {
return candidate;
}
}
} else if (typeof ast === 'object' && ast) {
if (ast.id === id) {
return ast;
} else {
for (const [name, prop] of Object.entries(ast)) {
if (name !== 'parent') {
const candidate = findNodeById(id, prop);
if (candidate) {
return candidate;
}
}
}
}
}
}
async function buildAst() {
const astRoots = yaml.safeLoad(await fs.readFile(`${__dirname}/data/astViewer.yml`, 'utf8')) as AstItem[];
// convert range properties into vscode.Range instances
function convertToRangeInstances(obj: any) {
if (Array.isArray(obj)) {
obj.forEach(elt => convertToRangeInstances(elt));
} else if (typeof obj === 'object' && obj) {
if ('range' in obj && '_start' in obj.range && '_end' in obj.range) {
obj.range = new Range(
obj.range._start._line,
obj.range._start._character,
obj.range._end._line,
obj.range._end._character,
);
} else {
Object.entries(obj).forEach(([name, prop]) => name !== 'parent' && convertToRangeInstances(prop));
}
}
}
convertToRangeInstances(astRoots);
return astRoots;
}
});