diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 324eec16..ca9de632 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -2,7 +2,8 @@ * @license MIT */ -import { LinkMatcherValidationCallback } from './Types'; +import { LinkMatcherOptions } from './Interfaces'; +import { LinkMatcherHandler, LinkMatcherValidationCallback } from './Types'; export interface IBrowser { isNode: boolean; @@ -52,6 +53,13 @@ export interface ICharMeasure { measure(): void; } +export interface ILinkifier { + linkifyRow(rowIndex: number): void; + attachHypertextLinkHandler(handler: LinkMatcherHandler): void; + registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: LinkMatcherOptions): number; + deregisterLinkMatcher(matcherId: number): boolean; +} + interface ICircularList { length: number; maxLength: number; diff --git a/src/Linkifier.phantom.ts b/src/Linkifier.phantom.ts new file mode 100644 index 00000000..01de4efc --- /dev/null +++ b/src/Linkifier.phantom.ts @@ -0,0 +1,76 @@ +/** + * @license MIT + */ +import { ITerminal, ILinkifier } from './Interfaces'; + +declare var assert: Chai.Assert; +declare var Terminal: ITerminal; + +// Do not describe tests unless in PhantomJS environment +if (typeof Terminal !== 'undefined') { + + const Linkifier = (Terminal).Linkifier; + Linkifier.setTimeBeforeLinkifyForTest(0); + + describe('Linkifier', () => { + let container: HTMLElement; + let rows: HTMLElement[]; + let linkifier: ILinkifier; + + beforeEach(() => { + container = document.createElement('div'); + document.querySelector('#xterm').appendChild(container); + rows = []; + linkifier = new Linkifier(rows); + }); + + afterEach(() => { + while (rows.length) { + container.removeChild(rows.pop()); + } + document.querySelector('#xterm').removeChild(container); + }); + + function addRow(text: string) { + const element = document.createElement('div'); + element.textContent = text; + container.appendChild(element); + rows.push(element); + } + + function clickElement(element: Node) { + const event = document.createEvent('MouseEvent'); + event.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); + element.dispatchEvent(event); + } + + describe('validationCallback', () => { + it('should enable link if true', done => { + addRow('test'); + linkifier.registerLinkMatcher(/test/, () => done(), { + validationCallback: (url, cb) => { + cb(true); + assert.equal((rows[0].firstChild).tagName, 'A'); + setTimeout(() => clickElement(rows[0].firstChild), 0); + } + }); + linkifier.linkifyRow(0); + }); + + it('should disable link if false', done => { + addRow('test'); + linkifier.registerLinkMatcher(/test/, () => assert.fail(), { + validationCallback: (url, cb) => { + cb(false); + assert.equal((rows[0].firstChild).tagName, 'A'); + setTimeout(() => clickElement(rows[0].firstChild), 0); + } + }); + linkifier.linkifyRow(0); + // Allow time for the click to be performed + setTimeout(() => done(), 10); + }); + }); + }); + +} diff --git a/src/Linkifier.ts b/src/Linkifier.ts index 73ad04a5..9f29ba58 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -3,14 +3,12 @@ */ import { LinkMatcherOptions } from './Interfaces'; -import { LinkMatcherValidationCallback } from './Types'; - -export type LinkHandler = (uri: string) => void; +import { LinkMatcherHandler, LinkMatcherValidationCallback } from './Types'; type LinkMatcher = { id: number, regex: RegExp, - handler: LinkHandler, + handler: LinkMatcherHandler, matchIndex?: number, validationCallback?: LinkMatcherValidationCallback; }; @@ -43,7 +41,7 @@ const HYPERTEXT_LINK_MATCHER_ID = 0; * the costly operation of searching every row multiple times, pntentially a * huge aount of times. */ -const TIME_BEFORE_LINKIFY = 200; +let TIME_BEFORE_LINKIFY = 200; /** * The Linkifier applies links to rows shortly after they have been refreshed. @@ -79,7 +77,7 @@ export class Linkifier { * @param {LinkHandler} handler The handler to use, this can be cleared with * null. */ - public attachHypertextLinkHandler(handler: LinkHandler): void { + public attachHypertextLinkHandler(handler: LinkMatcherHandler): void { this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].handler = handler; } @@ -93,7 +91,7 @@ export class Linkifier { * @param {LinkMatcherOptions} [options] Options for the link matcher. * @return {number} The ID of the new matcher, this can be used to deregister. */ - public registerLinkMatcher(regex: RegExp, handler: LinkHandler, options: LinkMatcherOptions = {}): number { + public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options: LinkMatcherOptions = {}): number { if (this._nextLinkMatcherId !== HYPERTEXT_LINK_MATCHER_ID && !handler) { throw new Error('handler cannot be falsy'); } @@ -161,7 +159,7 @@ export class Linkifier { * @param {handler} handler The handler to trigger when the link is triggered. * @return The link element if it was added, otherwise undefined. */ - private _doLinkifyRow(rowIndex: number, uri: string, handler?: LinkHandler): HTMLElement { + private _doLinkifyRow(rowIndex: number, uri: string, handler?: LinkMatcherHandler): HTMLElement { // Iterate over nodes as we want to consider text nodes const nodes = this._rows[rowIndex].childNodes; for (let i = 0; i < nodes.length; i++) { @@ -210,7 +208,7 @@ export class Linkifier { * @param {string} uri The uri of the link. * @return {HTMLAnchorElement} The link. */ - private _createAnchorElement(uri: string, handler: LinkHandler): HTMLAnchorElement { + private _createAnchorElement(uri: string, handler: LinkMatcherHandler): HTMLAnchorElement { const element = document.createElement('a'); element.textContent = uri; if (handler) { @@ -283,4 +281,11 @@ export class Linkifier { this._replaceNode(node, leftTextNode, newNode, rightTextNode); } } + + public static setTimeBeforeLinkifyForTest(time: number) { + // This is necessary since it's needs to be used in PhantomJS. Ideally the + // time variable would be a protected static member and a TestLinkifier + // would expose it for the test. + TIME_BEFORE_LINKIFY = time; + } } diff --git a/src/Types.ts b/src/Types.ts index 1eafa101..038a1f7a 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -2,4 +2,5 @@ * @license MIT */ +export type LinkMatcherHandler = (uri: string) => void; export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void; diff --git a/src/xterm.js b/src/xterm.js index 69eb0a1f..4d5f0205 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -2302,6 +2302,7 @@ Terminal.inherits = inherits; // Expose for Phantom.JS tests Terminal.CharMeasure = CharMeasure; +Terminal.Linkifier = Linkifier; /** * Adds an event listener to the terminal. diff --git a/test-harness.html b/test-harness.html index 8c5f0b30..bad4d36e 100644 --- a/test-harness.html +++ b/test-harness.html @@ -14,6 +14,7 @@ assert = chai.assert +