-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathexplorerViewRegistration.test.ts
More file actions
110 lines (92 loc) · 4.56 KB
/
Copy pathexplorerViewRegistration.test.ts
File metadata and controls
110 lines (92 loc) · 4.56 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
import { describe, it, expect } from 'vitest';
import * as fs from 'fs';
import * as path from 'path';
// GemStone Explorer panes are created eagerly with `vscode.window.createTreeView`
// (gemstoneExplorer.ts). VS Code registers a contributed view only once its `when`
// clause is satisfied, and createTreeView throws "No view is registered with id:
// <id>" for a view whose `when` is still false. So a createTreeView-backed view
// must NOT be gated on a context key that is false at activation/login — that was
// the login crash shipped in 1.8.1. These tests pin that so it can't come back.
const pkgPath = path.resolve(__dirname, '..', '..', '..', 'package.json');
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
const explorerViews: Array<{ id: string; when?: string }> = pkg.contributes.views.gemstoneExplorer;
describe('GemStone Explorer views are registrable when created', () => {
it.each(explorerViews)('does not gate $id on a content key that is false at login', (view) => {
expect(view.when ?? '').not.toContain('explorerHasOpenEditors');
});
});
// The Class Hierarchy pane starts collapsed so it doesn't crowd out the other
// panes. VS Code only applies a view's declared `visibility` when it has no
// stored layout for that view id, so the pane id must also stay in sync with
// the id passed to createTreeView — a mismatch throws "No view is registered".
const HIERARCHY = 'gemstoneExplorerClassHierarchy';
describe('GemStone Explorer Class Hierarchy pane', () => {
it('starts collapsed to leave room for the other panes', () => {
const hierarchy = explorerViews.find((v) => v.id === HIERARCHY) as
{ visibility?: string } | undefined;
expect(hierarchy?.visibility).toBe('collapsed');
});
it('registers the pane under the id the source creates it with', () => {
const src = fs.readFileSync(path.resolve(__dirname, '..', 'gemstoneExplorer.ts'), 'utf-8');
expect(src).toContain(`createTreeView('${HIERARCHY}'`);
});
});
// A command referenced from a menu (title bar, row context, palette) but never
// declared in contributes.commands shows up only at runtime as "command not
// found". Guard our own gemstone.* commands so a new menu entry can't drift out
// of sync with its declaration.
describe('command manifest consistency', () => {
const declared = new Set<string>(
(pkg.contributes.commands as Array<{ command: string }>).map((c) => c.command),
);
const menus = pkg.contributes.menus as Record<string, Array<{ command?: string }>>;
const menuCommands = new Set<string>();
for (const group of Object.values(menus)) {
for (const item of group) if (item.command) menuCommands.add(item.command);
}
it('declares every gemstone command that a menu references', () => {
const missing = [...menuCommands]
.filter((c) => c.startsWith('gemstone'))
.filter((c) => !declared.has(c))
.sort();
expect(missing).toEqual([]);
});
});
// The mirror of the command check above, for view ids. A menu `when` that gates
// on a `view ==` id no view declares is silently, permanently false — the entry
// just never appears, with no runtime error. That is exactly how the Insert /
// Extract Superclass items vanished when the Hierarchy pane was renamed but two
// of its menu clauses weren't. Guard every view reference so a renamed or
// mistyped id fails a test instead of quietly dropping a menu item.
describe('menu view-id consistency', () => {
const declaredViews = new Set<string>();
for (const views of Object.values(
pkg.contributes.views as Record<string, Array<{ id: string }>>,
)) {
for (const v of views) declaredViews.add(v.id);
}
const whens: string[] = [];
for (const group of Object.values(
pkg.contributes.menus as Record<string, Array<{ when?: string }>>,
)) {
for (const item of group) if (item.when) whens.push(item.when);
}
it('references only declared view ids in exact `view ==` menu clauses', () => {
const referenced = new Set<string>();
for (const when of whens) {
for (const m of when.matchAll(/view\s*==\s*([A-Za-z0-9_.]+)/g)) referenced.add(m[1]);
}
const missing = [...referenced].filter((id) => !declaredViews.has(id)).sort();
expect(missing).toEqual([]);
});
it('uses only `view =~` menu patterns that match at least one declared view', () => {
const dead: string[] = [];
for (const when of whens) {
for (const m of when.matchAll(/view\s*=~\s*\/([^/]+)\//g)) {
const re = new RegExp(m[1]);
if (![...declaredViews].some((id) => re.test(id))) dead.push(m[1]);
}
}
expect(dead).toEqual([]);
});
});