2019-05-31 19:02:54 -07:00
|
|
|
/**
|
|
|
|
|
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
|
|
|
|
* @license MIT
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { assert } from 'chai';
|
2021-08-31 12:50:54 +02:00
|
|
|
import { openTerminal, pollFor, writeSync, launchBrowser } from '../../../out-test/api/TestUtils';
|
2023-08-27 08:08:42 -07:00
|
|
|
import { Browser, Page } from '@playwright/test';
|
2019-05-31 19:02:54 -07:00
|
|
|
|
2021-04-06 15:48:48 +01:00
|
|
|
const APP = 'http://127.0.0.1:3001/test';
|
2019-05-31 19:02:54 -07:00
|
|
|
|
2020-02-13 00:07:07 -06:00
|
|
|
let browser: Browser;
|
|
|
|
|
let page: Page;
|
2019-05-31 19:02:54 -07:00
|
|
|
const width = 800;
|
|
|
|
|
const height = 600;
|
|
|
|
|
|
2022-12-19 15:49:10 +01:00
|
|
|
|
|
|
|
|
interface ILinkStateData {
|
|
|
|
|
uri?: string;
|
|
|
|
|
range?: {
|
|
|
|
|
start: {
|
|
|
|
|
x: number;
|
|
|
|
|
y: number;
|
|
|
|
|
};
|
|
|
|
|
end: {
|
|
|
|
|
x: number;
|
|
|
|
|
y: number;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2019-05-31 21:25:35 -07:00
|
|
|
describe('WebLinksAddon', () => {
|
2020-02-12 16:00:21 -06:00
|
|
|
before(async function(): Promise<any> {
|
2021-08-31 12:50:54 +02:00
|
|
|
browser = await launchBrowser();
|
2020-02-15 05:18:46 -08:00
|
|
|
page = await (await browser.newContext()).newPage();
|
|
|
|
|
await page.setViewportSize({ width, height });
|
2023-08-19 05:00:45 -07:00
|
|
|
await page.goto(APP);
|
|
|
|
|
await openTerminal(page, { cols: 40 });
|
2019-05-31 19:02:54 -07:00
|
|
|
});
|
|
|
|
|
|
2020-02-15 04:29:04 -08:00
|
|
|
after(async () => await browser.close());
|
2023-08-19 05:00:45 -07:00
|
|
|
beforeEach(async () => {
|
|
|
|
|
await page.evaluate(`
|
|
|
|
|
window._linkaddon?.dispose();
|
|
|
|
|
window.term.reset();
|
|
|
|
|
window._linkaddon = new window.WebLinksAddon();
|
|
|
|
|
window.term.loadAddon(window._linkaddon);
|
|
|
|
|
`);
|
2019-05-31 19:02:54 -07:00
|
|
|
});
|
|
|
|
|
|
2023-08-19 05:06:46 -07:00
|
|
|
const countryTlds = [
|
|
|
|
|
'.ac', '.ad', '.ae', '.af', '.ag', '.ai', '.al', '.am', '.ao', '.aq', '.ar', '.as', '.at',
|
|
|
|
|
'.au', '.aw', '.ax', '.az', '.ba', '.bb', '.bd', '.be', '.bf', '.bg', '.bh', '.bi', '.bj',
|
|
|
|
|
'.bm', '.bn', '.bo', '.bq', '.br', '.bs', '.bt', '.bw', '.by', '.bz', '.ca', '.cc', '.cd',
|
|
|
|
|
'.cf', '.cg', '.ch', '.ci', '.ck', '.cl', '.cm', '.cn', '.co', '.cr', '.cu', '.cv', '.cw',
|
|
|
|
|
'.cx', '.cy', '.cz', '.de', '.dj', '.dk', '.dm', '.do', '.dz', '.ec', '.ee', '.eg', '.eh',
|
|
|
|
|
'.er', '.es', '.et', '.eu', '.fi', '.fj', '.fk', '.fm', '.fo', '.fr', '.ga', '.gd', '.ge',
|
|
|
|
|
'.gf', '.gg', '.gh', '.gi', '.gl', '.gm', '.gn', '.gp', '.gq', '.gr', '.gs', '.gt', '.gu',
|
|
|
|
|
'.gw', '.gy', '.hk', '.hm', '.hn', '.hr', '.ht', '.hu', '.id', '.ie', '.il', '.im', '.in',
|
|
|
|
|
'.io', '.iq', '.ir', '.is', '.it', '.je', '.jm', '.jo', '.jp', '.ke', '.kg', '.kh', '.ki',
|
|
|
|
|
'.km', '.kn', '.kp', '.kr', '.kw', '.ky', '.kz', '.la', '.lb', '.lc', '.li', '.lk', '.lr',
|
|
|
|
|
'.ls', '.lt', '.lu', '.lv', '.ly', '.ma', '.mc', '.md', '.me', '.mg', '.mh', '.mk', '.ml',
|
|
|
|
|
'.mm', '.mn', '.mo', '.mp', '.mq', '.mr', '.ms', '.mt', '.mu', '.mv', '.mw', '.mx', '.my',
|
|
|
|
|
'.mz', '.na', '.nc', '.ne', '.nf', '.ng', '.ni', '.nl', '.no', '.np', '.nr', '.nu', '.nz',
|
|
|
|
|
'.om', '.pa', '.pe', '.pf', '.pg', '.ph', '.pk', '.pl', '.pm', '.pn', '.pr', '.ps', '.pt',
|
|
|
|
|
'.pw', '.py', '.qa', '.re', '.ro', '.rs', '.ru', '.rw', '.sa', '.sb', '.sc', '.sd', '.se',
|
|
|
|
|
'.sg', '.sh', '.si', '.sk', '.sl', '.sm', '.sn', '.so', '.sr', '.ss', '.st', '.su', '.sv',
|
|
|
|
|
'.sx', '.sy', '.sz', '.tc', '.td', '.tf', '.tg', '.th', '.tj', '.tk', '.tl', '.tm', '.tn',
|
|
|
|
|
'.to', '.tr', '.tt', '.tv', '.tw', '.tz', '.ua', '.ug', '.uk', '.us', '.uy', '.uz', '.va',
|
|
|
|
|
'.vc', '.ve', '.vg', '.vi', '.vn', '.vu', '.wf', '.ws', '.ye', '.yt', '.za', '.zm', '.zw'
|
|
|
|
|
];
|
|
|
|
|
for (const tld of countryTlds) {
|
|
|
|
|
it(tld, async () => await testHostName(`foo${tld}`));
|
|
|
|
|
}
|
|
|
|
|
it(`.com`, async () => await testHostName(`foo.com`));
|
|
|
|
|
for (const tld of countryTlds) {
|
|
|
|
|
it(`.com${tld}`, async () => await testHostName(`foo.com${tld}`));
|
|
|
|
|
}
|
2022-12-04 22:26:29 +01:00
|
|
|
|
2022-12-05 22:49:25 +01:00
|
|
|
describe('correct buffer offsets & uri', () => {
|
2023-08-19 05:00:45 -07:00
|
|
|
beforeEach(async () => {
|
|
|
|
|
await page.evaluate(`
|
|
|
|
|
window._linkStateData = {uri:''};
|
|
|
|
|
window._linkaddon._options.hover = (event, uri, range) => { window._linkStateData = { uri, range }; };
|
|
|
|
|
`);
|
|
|
|
|
});
|
2022-12-05 22:49:25 +01:00
|
|
|
it('all half width', async () => {
|
|
|
|
|
await writeSync(page, 'aaa http://example.com aaa http://example.com aaa');
|
2023-07-23 12:36:15 +02:00
|
|
|
await resetAndHover(5, 0);
|
2022-12-19 15:49:10 +01:00
|
|
|
await evalLinkStateData('http://example.com', { start: { x: 5, y: 1 }, end: { x: 22, y: 1 } });
|
2023-07-23 12:36:15 +02:00
|
|
|
await resetAndHover(1, 1);
|
2022-12-19 15:49:10 +01:00
|
|
|
await evalLinkStateData('http://example.com', { start: { x: 28, y: 1 }, end: { x: 5, y: 2 } });
|
2022-12-05 22:49:25 +01:00
|
|
|
});
|
|
|
|
|
it('url after full width', async () => {
|
|
|
|
|
await writeSync(page, '¥¥¥ http://example.com ¥¥¥ http://example.com aaa');
|
2023-07-23 12:36:15 +02:00
|
|
|
await resetAndHover(8, 0);
|
2022-12-19 15:49:10 +01:00
|
|
|
await evalLinkStateData('http://example.com', { start: { x: 8, y: 1 }, end: { x: 25, y: 1 } });
|
2023-07-23 12:36:15 +02:00
|
|
|
await resetAndHover(1, 1);
|
2022-12-19 15:49:10 +01:00
|
|
|
await evalLinkStateData('http://example.com', { start: { x: 34, y: 1 }, end: { x: 11, y: 2 } });
|
2022-12-05 22:49:25 +01:00
|
|
|
});
|
|
|
|
|
it('full width within url and before', async () => {
|
|
|
|
|
await writeSync(page, '¥¥¥ https://ko.wikipedia.org/wiki/위키백과:대문 aaa https://ko.wikipedia.org/wiki/위키백과:대문 ¥¥¥');
|
2023-07-23 12:36:15 +02:00
|
|
|
await resetAndHover(8, 0);
|
2022-12-19 15:49:10 +01:00
|
|
|
await evalLinkStateData('https://ko.wikipedia.org/wiki/위키백과:대문', { start: { x: 8, y: 1 }, end: { x: 11, y: 2 } });
|
2023-07-23 12:36:15 +02:00
|
|
|
await resetAndHover(1, 1);
|
2022-12-19 15:49:10 +01:00
|
|
|
await evalLinkStateData('https://ko.wikipedia.org/wiki/위키백과:대문', { start: { x: 8, y: 1 }, end: { x: 11, y: 2 } });
|
2023-07-23 12:36:15 +02:00
|
|
|
await resetAndHover(17, 1);
|
2022-12-19 15:49:10 +01:00
|
|
|
await evalLinkStateData('https://ko.wikipedia.org/wiki/위키백과:대문', { start: { x: 17, y: 2 }, end: { x: 19, y: 3 } });
|
2022-12-05 22:49:25 +01:00
|
|
|
});
|
|
|
|
|
it('name + password url after full width and combining', async () => {
|
|
|
|
|
await writeSync(page, '¥¥¥cafe\u0301 http://test:password@example.com/some_path');
|
2023-07-23 12:36:15 +02:00
|
|
|
await resetAndHover(12, 0);
|
2022-12-19 15:49:10 +01:00
|
|
|
await evalLinkStateData('http://test:password@example.com/some_path', { start: { x: 12, y: 1 }, end: { x: 13, y: 2 } });
|
2023-07-23 12:36:15 +02:00
|
|
|
await resetAndHover(5, 1);
|
2022-12-19 15:49:10 +01:00
|
|
|
await evalLinkStateData('http://test:password@example.com/some_path', { start: { x: 12, y: 1 }, end: { x: 13, y: 2 } });
|
2022-12-05 22:49:25 +01:00
|
|
|
});
|
2024-01-05 09:43:33 +01:00
|
|
|
it('url encoded params work properly', async () => {
|
|
|
|
|
await writeSync(page, '¥¥¥cafe\u0301 http://test:password@example.com/some_path?param=1%202%3');
|
|
|
|
|
await resetAndHover(12, 0);
|
|
|
|
|
await evalLinkStateData('http://test:password@example.com/some_path?param=1%202%3', { start: { x: 12, y: 1 }, end: { x: 27, y: 2 } });
|
|
|
|
|
await resetAndHover(5, 1);
|
|
|
|
|
await evalLinkStateData('http://test:password@example.com/some_path?param=1%202%3', { start: { x: 12, y: 1 }, end: { x: 27, y: 2 } });
|
|
|
|
|
});
|
2022-12-04 22:26:29 +01:00
|
|
|
});
|
2024-02-22 09:33:21 +01:00
|
|
|
|
|
|
|
|
// issue #4964
|
2024-02-22 09:40:38 +01:00
|
|
|
it('uppercase in protocol and host, default ports', async () => {
|
2024-02-22 09:38:36 +01:00
|
|
|
const data = ` HTTP://EXAMPLE.COM \\r\\n` +
|
2024-02-22 09:33:21 +01:00
|
|
|
` HTTPS://Example.com \\r\\n` +
|
|
|
|
|
` HTTP://Example.com:80 \\r\\n` +
|
2024-02-22 09:55:42 +01:00
|
|
|
` HTTP://Example.com:80/staysUpper \\r\\n` +
|
|
|
|
|
` HTTP://Ab:xY@abc.com:80/staysUpper \\r\\n`;
|
2024-02-22 09:38:36 +01:00
|
|
|
await writeSync(page, data);
|
|
|
|
|
await pollForLinkAtCell(3, 0, `HTTP://EXAMPLE.COM`);
|
|
|
|
|
await pollForLinkAtCell(3, 1, `HTTPS://Example.com`);
|
|
|
|
|
await pollForLinkAtCell(3, 2, `HTTP://Example.com:80`);
|
|
|
|
|
await pollForLinkAtCell(3, 3, `HTTP://Example.com:80/staysUpper`);
|
2024-02-22 09:55:42 +01:00
|
|
|
await pollForLinkAtCell(3, 4, `HTTP://Ab:xY@abc.com:80/staysUpper`);
|
2024-02-22 09:33:21 +01:00
|
|
|
});
|
2019-05-31 19:02:54 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
async function testHostName(hostname: string): Promise<void> {
|
2019-11-09 18:43:25 -08:00
|
|
|
const data = ` http://${hostname} \\r\\n` +
|
|
|
|
|
` http://${hostname}/a~b#c~d?e~f \\r\\n` +
|
|
|
|
|
` http://${hostname}/colon:test \\r\\n` +
|
|
|
|
|
` http://${hostname}/colon:test: \\r\\n` +
|
|
|
|
|
`"http://${hostname}/"\\r\\n` +
|
|
|
|
|
`\\'http://${hostname}/\\'\\r\\n` +
|
|
|
|
|
`http://${hostname}/subpath/+/id`;
|
|
|
|
|
await writeSync(page, data);
|
2023-07-23 12:36:15 +02:00
|
|
|
await pollForLinkAtCell(3, 0, `http://${hostname}`);
|
|
|
|
|
await pollForLinkAtCell(3, 1, `http://${hostname}/a~b#c~d?e~f`);
|
|
|
|
|
await pollForLinkAtCell(3, 2, `http://${hostname}/colon:test`);
|
2019-11-09 13:57:42 -08:00
|
|
|
await pollForLinkAtCell(3, 3, `http://${hostname}/colon:test`);
|
2023-07-23 12:36:15 +02:00
|
|
|
await pollForLinkAtCell(2, 4, `http://${hostname}/`);
|
2019-11-09 13:57:42 -08:00
|
|
|
await pollForLinkAtCell(2, 5, `http://${hostname}/`);
|
2023-07-23 12:36:15 +02:00
|
|
|
await pollForLinkAtCell(1, 6, `http://${hostname}/subpath/+/id`);
|
2019-05-31 19:02:54 -07:00
|
|
|
}
|
|
|
|
|
|
2019-11-09 13:57:42 -08:00
|
|
|
async function pollForLinkAtCell(col: number, row: number, value: string): Promise<void> {
|
2023-07-23 12:36:15 +02:00
|
|
|
await page.mouse.move(...(await cellPos(col, row)));
|
|
|
|
|
await pollFor(page, `!!Array.from(document.querySelectorAll('.xterm-rows > :nth-child(${row+1}) > span[style]')).filter(el => el.style.textDecoration == 'underline').length`, true);
|
2023-07-23 22:25:58 +02:00
|
|
|
const text = await page.evaluate(`Array.from(document.querySelectorAll('.xterm-rows > :nth-child(${row+1}) > span[style]')).filter(el => el.style.textDecoration == 'underline').map(el => el.textContent).join('');`);
|
2023-07-23 12:36:15 +02:00
|
|
|
assert.deepEqual(text, value);
|
2019-11-09 13:57:42 -08:00
|
|
|
}
|
2022-12-05 22:49:25 +01:00
|
|
|
|
|
|
|
|
async function resetAndHover(col: number, row: number): Promise<void> {
|
2023-07-23 12:36:15 +02:00
|
|
|
await page.mouse.move(0, 0);
|
|
|
|
|
await page.evaluate(`window._linkStateData = {uri:''};`);
|
|
|
|
|
await new Promise(r => setTimeout(r, 200));
|
|
|
|
|
await page.mouse.move(...(await cellPos(col, row)));
|
|
|
|
|
await pollFor(page, `!!window._linkStateData.uri.length`, true);
|
2022-12-05 22:49:25 +01:00
|
|
|
}
|
|
|
|
|
|
2022-12-19 15:49:10 +01:00
|
|
|
async function evalLinkStateData(uri: string, range: any): Promise<void> {
|
|
|
|
|
const data: ILinkStateData = await page.evaluate(`window._linkStateData`);
|
|
|
|
|
assert.equal(data.uri, uri);
|
|
|
|
|
assert.deepEqual(data.range, range);
|
2022-12-05 22:49:25 +01:00
|
|
|
}
|
2023-07-23 12:36:15 +02:00
|
|
|
|
|
|
|
|
async function cellPos(col: number, row: number): Promise<[number, number]> {
|
|
|
|
|
const coords: any = await page.evaluate(`
|
|
|
|
|
(function() {
|
|
|
|
|
const rect = window.term.element.getBoundingClientRect();
|
|
|
|
|
const dim = term._core._renderService.dimensions;
|
|
|
|
|
return {left: rect.left, top: rect.top, bottom: rect.bottom, right: rect.right, width: dim.css.cell.width, height: dim.css.cell.height};
|
|
|
|
|
})();
|
|
|
|
|
`);
|
|
|
|
|
return [col * coords.width + coords.left + 2, row * coords.height + coords.top + 2];
|
|
|
|
|
}
|