Skip to content
This repository was archived by the owner on Nov 25, 2021. It is now read-only.

Commit 005604f

Browse files
ijsnowfelixfbecker
authored andcommitted
refactor: more consistent naming in dom testutils and add doc blocks
1 parent 2afc5b7 commit 005604f

File tree

9 files changed

+120
-73
lines changed

9 files changed

+120
-73
lines changed

src/hoverifier.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ describe('Hoverifier', () => {
5353
scrollElement: HTMLElement
5454
}>()
5555

56-
const positionEvents = of(codeView.element).pipe(findPositionsFromEvents(codeView))
56+
const positionEvents = of(codeView.codeView).pipe(findPositionsFromEvents(codeView))
5757

5858
const subscriptions = new Subscription()
5959

src/hoverifier.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { PositionEvent, SupportedMouseEvent } from './positions'
2323
import { createObservableStateContainer } from './state'
2424
import {
2525
convertNode,
26-
DOMOptions,
26+
DOMFunctions,
2727
findElementWithOffset,
2828
getCodeElementsInRange,
2929
getTokenAtPosition,
@@ -55,7 +55,7 @@ interface HoverifierOptions {
5555

5656
hoverOverlayElements: Observable<HTMLElement | null>
5757

58-
dom: DOMOptions
58+
dom: DOMFunctions
5959

6060
/**
6161
* Called for programmatic navigation (like `history.push()`)

src/positions.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ describe('position_listener', () => {
4242
},
4343
}
4444

45-
const clickedTokens = of(codeView.element).pipe(
45+
const clickedTokens = of(codeView.codeView).pipe(
4646
findPositionsFromEvents(codeView),
4747
filter(propertyIsDefined('position')),
4848
map(({ position: { line, character } }) => ({ line, character }))

src/positions.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { fromEvent, merge, Observable } from 'rxjs'
22
import { filter, map, switchMap, tap } from 'rxjs/operators'
33
import { Position } from 'vscode-languageserver-types'
4-
import { convertCodeElementIdempotent, DOMOptions, HoveredToken, locateTarget } from './token_position'
5-
4+
import { convertCodeElementIdempotent, DOMFunctions, HoveredToken, locateTarget } from './token_position'
65
export type SupportedMouseEvent = 'click' | 'mousemove' | 'mouseover'
76

87
export interface PositionEvent {
@@ -25,7 +24,7 @@ export interface PositionEvent {
2524
codeView: HTMLElement
2625
}
2726

28-
export const findPositionsFromEvents = (options: DOMOptions) => (
27+
export const findPositionsFromEvents = (options: DOMFunctions) => (
2928
elements: Observable<HTMLElement>
3029
): Observable<PositionEvent> =>
3130
merge(

src/testutils/dom.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@ describe('can create dom elements from generated code tables', () => {
77
after(dom.cleanup)
88

99
it('can create the code view test cases and their helper function work', () => {
10-
for (const codeView of dom.createCodeViews()) {
10+
for (const codeViewProps of dom.createCodeViews()) {
1111
const {
12-
element,
12+
codeView,
1313
getCodeElementFromTarget,
1414
getCodeElementFromLineNumber,
1515
getLineNumberFromCodeElement,
16-
} = codeView
16+
} = codeViewProps
1717

1818
for (let i = 1; i < 10; i++) {
19-
const cellFromLine = getCodeElementFromLineNumber(element, i)
19+
const cellFromLine = getCodeElementFromLineNumber(codeView, i)
2020
expect(cellFromLine).to.not.equal(null)
2121
const cellFromTarget = getCodeElementFromTarget(cellFromLine!)
2222
expect(cellFromTarget).to.equal(cellFromLine)

src/testutils/dom.ts

Lines changed: 50 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import githubCode from '../../testdata/generated/github.html'
22
import sourcegraphCode from '../../testdata/generated/sourcegraph.html'
3-
import { DOMOptions, getTextNodes } from '../token_position'
3+
import { DOMFunctions } from '../token_position'
44
import { TEST_DATA_REVSPEC } from './rev'
55

6-
const createElementFromString = (html: string): HTMLElement => {
6+
const createElementFromString = (html: string): HTMLDivElement => {
77
const elem = document.createElement('div')
88

99
elem.innerHTML = html
@@ -17,43 +17,30 @@ const createElementFromString = (html: string): HTMLElement => {
1717
return elem
1818
}
1919

20-
export const getCharacterWidth = (character: string): number =>
21-
createElementFromString(character).getBoundingClientRect().width
20+
/** Gets all of the text nodes within a given node. Used for testing. */
21+
export const getTextNodes = (node: Node): Node[] => {
22+
if (node.childNodes.length === 0 && node.TEXT_NODE === node.nodeType && node.nodeValue) {
23+
return [node]
24+
}
2225

23-
export const getCharacterWidthInContainer = (container: HTMLElement, character: string, idx: number): number => {
24-
const span = document.createElement('span')
25-
span.innerHTML = character
26-
span.dataset.char = idx + ''
27-
span.dataset.charCode = character.charCodeAt(0) + ''
28-
span.style.visibility = 'hidden'
29-
span.style.cssFloat = 'left'
30-
span.style.height = '0'
26+
const nodes: Node[] = []
3127

32-
container.appendChild(span)
33-
const width = span.getBoundingClientRect().width
34-
container.removeChild(span)
28+
for (const child of Array.from(node.childNodes)) {
29+
nodes.push(...getTextNodes(child))
30+
}
3531

36-
return width
32+
return nodes
3733
}
3834

39-
const getCharactersInCell = (cell: HTMLElement) =>
40-
Array.from(
41-
getTextNodes(cell)
42-
.map(node => node.nodeValue)
43-
.join('')
44-
)
45-
46-
export const getNumberOfCharactersFromCell = (cell: HTMLElement): number => getCharactersInCell(cell).length
47-
export const getWidthOfCharactersFromCell = (cell: HTMLElement): number =>
48-
getCharactersInCell(cell)
49-
.map((c, i) => getCharacterWidthInContainer(cell, c, i))
50-
.reduce((a, b) => a + b, 0)
51-
52-
export interface CodeViewProps extends DOMOptions {
53-
element: HTMLElement
54-
injectedElement: HTMLElement
35+
/** The props used for the generated test cases(e.g. GitHub and Sourcegraph flavored dom). */
36+
export interface CodeViewProps extends DOMFunctions {
37+
/** The code view for the given test case(e.g. a <code> element in Sourcegraph and <table> in GitHub) */
38+
codeView: HTMLElement
39+
/** The container of the code view. (e.g. The scrollable contaienr in Sourcegraph and a parent <div> in GitHub) */
40+
container: HTMLElement
41+
/** The revision and repository information for the file used in the generated test cases. */
5542
revSpec: typeof TEST_DATA_REVSPEC
56-
43+
/** A helper method for adding rows to the test case. */
5744
insertRow: (text: string) => HTMLElement
5845
}
5946

@@ -62,6 +49,8 @@ export const wrapCharsInSpans = (line: string) =>
6249
.map((c, j) => `<span data-char="${j}">${c}</span>`)
6350
.join('')
6451

52+
// BEGIN setup test cases
53+
6554
const getDiffCodePart = (codeElement: HTMLElement): 'new' | 'old' | undefined => {
6655
switch (codeElement.textContent!.charAt(0)) {
6756
case '+':
@@ -123,8 +112,8 @@ const createGitHubCodeView = (): CodeViewProps => {
123112
}
124113

125114
return {
126-
injectedElement: codeView,
127-
element: codeView,
115+
container: codeView,
116+
codeView,
128117

129118
revSpec: TEST_DATA_REVSPEC,
130119
getCodeElementFromTarget,
@@ -204,9 +193,9 @@ const createSourcegraphCodeView = (): CodeViewProps => {
204193
}
205194

206195
return {
207-
injectedElement: codeView,
196+
container: codeView,
208197

209-
element: codeView.querySelector('code')!,
198+
codeView: codeView.querySelector('code')!,
210199
revSpec: TEST_DATA_REVSPEC,
211200
getCodeElementFromTarget,
212201
getCodeElementFromLineNumber,
@@ -231,31 +220,51 @@ const createSourcegraphCodeView = (): CodeViewProps => {
231220
}
232221
}
233222

223+
// END setup test cases
224+
225+
/**
226+
* DOM is a testing utility class that keeps track of all elements a test suite is adding to the DOM
227+
* so that we can clean up after the test suite has finished.
228+
*/
234229
export class DOM {
230+
/** The inserted nodes. We save them so that we can remove them on cleanup. */
235231
private nodes = new Set<Element>()
236232

233+
/**
234+
* Creates and inserts the generated test cases into the DOM
235+
* @returns the CodeViewProps for the test cases added to the DOM.
236+
*/
237237
public createCodeViews(): CodeViewProps[] {
238238
const codeViews: CodeViewProps[] = [createSourcegraphCodeView(), createGitHubCodeView()]
239239

240-
for (const { injectedElement } of codeViews) {
241-
this.insert(injectedElement)
240+
for (const { container } of codeViews) {
241+
this.insert(container)
242242
}
243243

244244
return codeViews
245245
}
246246

247-
public createElementFromString(html: string): HTMLElement {
247+
/**
248+
* Creates a div with some arbitrary content.
249+
* @param html the content of the element you wish to create.
250+
* @returns the created div.
251+
*/
252+
public createElementFromString(html: string): HTMLDivElement {
248253
const element = createElementFromString(html)
249254
this.insert(element)
250-
return element as HTMLElement
255+
return element as HTMLDivElement
251256
}
252257

258+
/**
259+
* Removes all nodes that were inserted from the DOM. This should be called after a test suite has ran.
260+
*/
253261
public cleanup = (): void => {
254262
for (const node of this.nodes) {
255263
document.body.removeChild(node)
256264
}
257265
}
258266

267+
/** The funnel for inserting elements into the DOM so that we know to remove it in `cleanup()`. */
259268
private insert(node: Element): void {
260269
document.body.appendChild(node)
261270

src/testutils/mouse.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ const invalidPosition = ({ line, character }: Position, message: string) =>
3131
* @param codeViewProps the codeViewProps props from the test cases.
3232
* @param position the position to click.
3333
*/
34-
export const clickPositionImpure = ({ element, getCodeElementFromLineNumber }: CodeViewProps, position: Position) => {
35-
const line = getCodeElementFromLineNumber(element, position.line)
34+
export const clickPositionImpure = ({ codeView, getCodeElementFromLineNumber }: CodeViewProps, position: Position) => {
35+
const line = getCodeElementFromLineNumber(codeView, position.line)
3636
if (!line) {
3737
throw new Error(invalidPosition(position, 'Line not found'))
3838
}
@@ -72,8 +72,8 @@ export const clickPositionImpure = ({ element, getCodeElementFromLineNumber }: C
7272
* @param codeViewProps the CodeViewProps from the generated test cases
7373
* @param position the 0-indexed position to click
7474
*/
75-
export const clickPosition = ({ element, getCodeElementFromLineNumber }: CodeViewProps, position: Position) => {
76-
const line = getCodeElementFromLineNumber(element, position.line)
75+
export const clickPosition = ({ codeView, getCodeElementFromLineNumber }: CodeViewProps, position: Position) => {
76+
const line = getCodeElementFromLineNumber(codeView, position.line)
7777
if (!line) {
7878
throw new Error(invalidPosition(position, 'Line not found'))
7979
}

src/token_position.test.ts

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import { Position } from 'vscode-languageserver-types'
2+
13
import { CodeViewProps, DOM } from './testutils/dom'
2-
import { convertNode, findElementWithOffset, getTokenAtPosition, locateTarget } from './token_position'
4+
import { convertNode, findElementWithOffset, getTokenAtPosition, HoveredToken, locateTarget } from './token_position'
35

46
const { expect } = chai
57

@@ -153,9 +155,9 @@ describe('token_positions', () => {
153155
},
154156
]
155157

156-
for (const { element, ...domOptions } of testcases) {
158+
for (const { codeView, ...domOptions } of testcases) {
157159
for (const { token, position } of tokens) {
158-
const found = getTokenAtPosition(element, position, domOptions)
160+
const found = getTokenAtPosition(codeView, position, domOptions)
159161

160162
expect(found).to.not.equal(undefined)
161163
expect(found!.textContent).to.equal(token)
@@ -164,22 +166,32 @@ describe('token_positions', () => {
164166
})
165167

166168
it('locateTarget finds the correct token for a target', () => {
167-
const positions = [
168-
{ position: { line: 24, character: 6 }, token: 'NewRouter' },
169-
{ position: { line: 7, character: 3 }, token: 'import' },
170-
{ position: { line: 154, character: 2 }, token: 'if' },
171-
{ position: { line: 257, character: 5 }, token: '=' },
172-
{ position: { line: 121, character: 9 }, token: '*' },
173-
{ position: { line: 128, character: 8 }, token: ':' },
169+
const positions: {
170+
/** A position within the expected token. */
171+
atPosition: Position
172+
/** The position that locateTarget found. If it works correctly, it is the position of the first character in the token. */
173+
foundPosition: Position
174+
}[] = [
175+
{ atPosition: { line: 24, character: 8 }, foundPosition: { line: 24, character: 6 } }, // NewRouter
176+
{ atPosition: { line: 7, character: 3 }, foundPosition: { line: 7, character: 1 } }, // import
177+
{ atPosition: { line: 154, character: 3 }, foundPosition: { line: 154, character: 2 } }, // if
178+
{ atPosition: { line: 257, character: 5 }, foundPosition: { line: 257, character: 5 } }, // =
179+
{ atPosition: { line: 121, character: 9 }, foundPosition: { line: 121, character: 9 } }, // *
180+
{ atPosition: { line: 128, character: 8 }, foundPosition: { line: 128, character: 8 } }, // :
174181
]
175182

176-
for (const { element, ...domOptions } of testcases) {
177-
for (const { position } of positions) {
178-
const target = getTokenAtPosition(element, position, domOptions)
183+
for (const { codeView, ...domOptions } of testcases) {
184+
for (const { atPosition, foundPosition } of positions) {
185+
const target = getTokenAtPosition(codeView, atPosition, domOptions)
179186

180187
const found = locateTarget(target!, domOptions)
181188

182189
expect(found).to.not.equal(undefined)
190+
191+
const token = found as HoveredToken
192+
193+
expect(token.line).to.equal(foundPosition.line)
194+
expect(token.character).to.equal(foundPosition.character)
183195
}
184196
}
185197
})

src/token_position.ts

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,37 @@
11
import { Position } from 'vscode-languageserver-types'
22
import { LineOrPositionOrRange } from './url'
33

4-
export interface DOMOptions {
4+
/**
5+
* A collection of methods needed to tell codeintellify how to look at the DOM. These are required for
6+
* ensuring that we don't rely on any sort of specific DOM structure.
7+
*
8+
*
9+
*/
10+
export interface DOMFunctions {
11+
/**
12+
* Get the element containing the code for a line from an event target.
13+
* @param target is the event target.
14+
* @returns the element containing the code for a line or null if it can't be found. For example, the second <td> inside a <tr> on Sourcegraph and Github.
15+
*/
516
getCodeElementFromTarget: (target: HTMLElement) => HTMLElement | null
17+
/**
18+
* Get the element containing the code for a line from a code view given a line number.
19+
* @param codeView is the code view itself. For example, the <code> element on Sourcegraph or a <table> on GitHub.
20+
* @returns the element containing the code for the given line number or null if it can't be found.
21+
*/
622
getCodeElementFromLineNumber: (codeView: HTMLElement, line: number) => HTMLElement | null
23+
/**
24+
* Gets the line number for a given element containing code for a line.
25+
* @param codeElement is the element containing code for a line. When this function is called,
26+
* it will be passed the result of either `getCodeElementFromTarget` or `getCodeElementFromLineNumber`.
27+
* @returns the line number.
28+
*/
729
getLineNumberFromCodeElement: (codeElement: HTMLElement) => number
30+
/**
31+
* Determine whether a code element is from the old or new part of a diff or not part of a diff.
32+
* @param codeElement is the element containing a line of code.
33+
* @returns whether the line is `'old'`, `'new'` or `undefined` if not part of a diff.
34+
*/
835
getDiffCodePart?: (codeElement: HTMLElement) => 'old' | 'new' | undefined
936
}
1037

@@ -189,7 +216,7 @@ export interface HoveredToken {
189216
part?: 'old' | 'new'
190217
}
191218

192-
interface LocateTargetOptions extends DOMOptions {
219+
interface LocateTargetOptions extends DOMFunctions {
193220
ignoreFirstChar?: boolean
194221
}
195222

@@ -254,7 +281,7 @@ export function locateTarget(
254281
return { line }
255282
}
256283

257-
interface GetCodeElementsInRangeOptions extends DOMOptions {
284+
interface GetCodeElementsInRangeOptions extends DOMFunctions {
258285
position?: LineOrPositionOrRange
259286
}
260287

@@ -291,7 +318,7 @@ export const getCodeElementsInRange = (
291318
export const getTokenAtPosition = (
292319
codeView: HTMLElement,
293320
position: Position,
294-
options: DOMOptions
321+
options: DOMFunctions
295322
): HTMLElement | undefined => {
296323
const codeCell = options.getCodeElementFromLineNumber(codeView, position.line)
297324
if (!codeCell) {

0 commit comments

Comments
 (0)