diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 177f581f..1ee66ddb 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -7,6 +7,8 @@ jobs: - job: Linux pool: vmImage: 'ubuntu-16.04' + variables: + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 steps: - task: NodeTool@0 inputs: @@ -16,38 +18,39 @@ jobs: inputs: versionSpec: '1.x' displayName: 'Install Yarn' - - script: yarn + - task: CacheBeta@1 + inputs: + key: yarn2 | $(Agent.OS) | yarn.lock + path: node_modules + displayName: Cache node modules + - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - - script: yarn test-unit --forbid-only + - script: yarn test-unit-coverage --forbid-only displayName: 'Unit tests' - script: yarn lint displayName: 'Lint' - - script: | - NODE_PATH=$(pwd)/out ./node_modules/.bin/nyc ./node_modules/.bin/mocha './out/*test.js' './out/**/*test.js' - ./node_modules/.bin/nyc report --reporter=cobertura - displayName: 'Coverage report' - task: PublishCodeCoverageResults@1 inputs: codeCoverageTool: Cobertura summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/*coverage.xml' displayName: 'Publish coverage' - - task: mspremier.BuildQualityChecks.QualityChecks-task.BuildQualityChecks@6 - displayName: 'Check build quality' - inputs: - checkCoverage: true - coverageType: lines - coverageThreshold: 60 - coverageFailOption: fixed - job: macOS pool: vmImage: 'xcode9-macos10.13' + variables: + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 steps: - task: NodeTool@0 inputs: versionSpec: '8.x' displayName: 'Install Node.js' - - script: yarn + - task: CacheBeta@1 + inputs: + key: yarn2 | $(Agent.OS) | yarn.lock + path: node_modules + displayName: Cache node modules + - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - script: yarn test-unit --forbid-only displayName: 'Unit tests' @@ -57,12 +60,19 @@ jobs: - job: Windows pool: vmImage: 'vs2017-win2016' + variables: + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 steps: - task: NodeTool@0 inputs: versionSpec: '8.x' displayName: 'Install Node.js' - - script: yarn + - task: CacheBeta@1 + inputs: + key: yarn2 | $(Agent.OS) | yarn.lock + path: node_modules + displayName: Cache node modules + - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - script: yarn test-unit --forbid-only displayName: 'Unit tests' @@ -77,12 +87,19 @@ jobs: inputs: versionSpec: '8.x' displayName: 'Install Node.js' + - task: CacheBeta@1 + inputs: + key: yarn_puppeteer | $(Agent.OS) | yarn.lock + path: node_modules + displayName: Cache node modules - task: YarnInstaller@3 inputs: versionSpec: '1.x' displayName: 'Install Yarn' - - script: yarn + - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' + - script: yarn add puppeteer + displayName: 'Install puppeteer' - script: | yarn start & sleep 10 @@ -97,8 +114,10 @@ jobs: inputs: versionSpec: '8.x' displayName: 'Install Node.js' - - script: yarn + - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' + - script: yarn add puppeteer + displayName: 'Install puppeteer' - script: | yarn start & sleep 10 @@ -124,7 +143,7 @@ jobs: inputs: versionSpec: '1.x' displayName: 'Install Yarn' - - script: yarn + - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - script: NPM_AUTH_TOKEN="$(NPM_AUTH_TOKEN)" node ./bin/publish.js displayName: 'Package and publish to npm' diff --git a/bin/test.js b/bin/test.js index 3b08d4a1..bd1784fb 100644 --- a/bin/test.js +++ b/bin/test.js @@ -6,6 +6,8 @@ const cp = require('child_process'); const path = require('path'); +const COVERAGE_LINES_THRESHOLD = 60; + // Add `out` to the NODE_PATH so absolute paths can be resolved. const env = { ...process.env }; env.NODE_PATH = path.resolve(__dirname, '../out'); @@ -28,8 +30,28 @@ if (process.argv.length > 2) { } } +const checkCoverage = flagArgs.indexOf('--coverage') >= 0; + +if (checkCoverage) { + flagArgs.splice(flagArgs.indexOf('--coverage'), 1); + const executable = npmBinScript('nyc'); + const args = ['--check-coverage', `--lines=${COVERAGE_LINES_THRESHOLD}`, npmBinScript('mocha'), ...testFiles, ...flagArgs]; + console.info('executable', executable); + console.info('args', args); + const run = cp.spawnSync( + executable, + args, + { + cwd: path.resolve(__dirname, '..'), + env, + stdio: 'inherit' + } + ); + process.exit(run.status); +} + const run = cp.spawnSync( - path.resolve(__dirname, '../node_modules/.bin/mocha'), + npmBinScript('mocha'), [...testFiles, ...flagArgs], { cwd: path.resolve(__dirname, '..'), @@ -38,4 +60,8 @@ const run = cp.spawnSync( } ); +function npmBinScript(script) { + return path.resolve(__dirname, `../node_modules/.bin/` + (process.platform === 'win32' ? `${script}.cmd` : script)); +} + process.exit(run.status); diff --git a/package.json b/package.json index fa29486a..7ed71741 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "posttest": "npm run lint", "test-api": "mocha \"**/*.api.js\"", "test-unit": "node ./bin/test.js", + "test-unit-coverage": "node ./bin/test.js --coverage", "build": "tsc -b ./tsconfig.all.json", "prepare": "npm run setup", "setup": "npm run build", @@ -50,7 +51,7 @@ "ts-loader": "^6.0.4", "tslint": "^5.18.0", "tslint-consistent-codestyle": "^1.13.0", - "typescript": "3.6", + "typescript": "3.7", "utf8": "^3.0.0", "webpack": "^4.35.3", "webpack-cli": "^3.1.0", diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 7b6ea1fe..74208482 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -347,6 +347,9 @@ export class InputHandler extends Disposable implements IInputHandler { } } + // Clear the dirty row service so we know which lines changed as a result of parsing + this._dirtyRowService.clearRange(); + // process big data in smaller chunks if (data.length > MAX_PARSEBUFFER_LENGTH) { for (let i = 0; i < data.length; i += MAX_PARSEBUFFER_LENGTH) { @@ -367,6 +370,8 @@ export class InputHandler extends Disposable implements IInputHandler { if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) { this._onCursorMove.fire(); } + + // Refresh any dirty rows accumulated as part of parsing this._terminal.refresh(this._dirtyRowService.start, this._dirtyRowService.end); } diff --git a/src/Terminal.ts b/src/Terminal.ts index 3e7e55ca..537ecb74 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -240,18 +240,12 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp return; } super.dispose(); - if (this._windowsMode) { - this._windowsMode.dispose(); - this._windowsMode = undefined; - } - if (this._renderService) { - this._renderService.dispose(); - } + this._windowsMode?.dispose(); + this._windowsMode = undefined; + this._renderService?.dispose(); this._customKeyEventHandler = null; this.write = () => {}; - if (this.element && this.element.parentNode) { - this.element.parentNode.removeChild(this.element); - } + this.element?.parentNode?.removeChild(this.element); } private _setup(): void { @@ -336,12 +330,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp case 'fontFamily': case 'fontSize': // When the font changes the size of the cells may change which requires a renderer clear - if (this._renderService) { - this._renderService.clear(); - } - if (this._charSizeService) { - this._charSizeService.measure(); - } + this._renderService?.clear(); + this._charSizeService?.measure(); break; case 'drawBoldTextInBrightColors': case 'letterSpacing': @@ -363,9 +353,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp break; case 'scrollback': this.buffers.resize(this.cols, this.rows); - if (this.viewport) { - this.viewport.syncScrollArea(); - } + this.viewport?.syncScrollArea(); break; case 'screenReaderMode': if (this.optionsService.options.screenReaderMode) { @@ -373,10 +361,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._accessibilityManager = new AccessibilityManager(this, this._renderService); } } else { - if (this._accessibilityManager) { - this._accessibilityManager.dispose(); - this._accessibilityManager = null; - } + this._accessibilityManager?.dispose(); + this._accessibilityManager = null; } break; case 'tabStopWidth': this.buffers.setupTabStops(); break; @@ -389,10 +375,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._windowsMode = applyWindowsMode(this); } } else { - if (this._windowsMode) { - this._windowsMode.dispose(); - this._windowsMode = undefined; - } + this._windowsMode?.dispose(); + this._windowsMode = undefined; } break; } @@ -665,15 +649,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp */ private _setTheme(theme: ITheme): void { this._theme = theme; - if (this._colorManager) { - this._colorManager.setTheme(theme); - } - if (this._renderService) { - this._renderService.setColors(this._colorManager.colors); - } - if (this.viewport) { - this.viewport.onThemeChange(this._colorManager.colors); - } + this._colorManager?.setTheme(theme); + this._renderService?.setColors(this._colorManager.colors); + this.viewport?.onThemeChange(this._colorManager.colors); } /** @@ -940,9 +918,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp * @param end The row to end at (between start and this.rows - 1). */ public refresh(start: number, end: number): void { - if (this._renderService) { - this._renderService.refreshRows(start, end); - } + this._renderService?.refreshRows(start, end); } /** @@ -951,9 +927,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp * @param end The row to end at (between start and this.rows - 1). */ private _queueLinkification(start: number, end: number): void { - if (this.linkifier) { - this.linkifier.linkifyRows(start, end); - } + this.linkifier?.linkifyRows(start, end); } /** @@ -1233,24 +1207,18 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp * Clears the current terminal selection. */ public clearSelection(): void { - if (this._selectionService) { - this._selectionService.clearSelection(); - } + this._selectionService?.clearSelection(); } /** * Selects all text within the terminal. */ public selectAll(): void { - if (this._selectionService) { - this._selectionService.selectAll(); - } + this._selectionService?.selectAll(); } public selectLines(start: number, end: number): void { - if (this._selectionService) { - this._selectionService.selectLines(start, end); - } + this._selectionService?.selectLines(start, end); } /** @@ -1457,9 +1425,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._bufferService.resize(x, y); this.buffers.setupTabStops(this.cols); - if (this._charSizeService) { - this._charSizeService.measure(); - } + this._charSizeService?.measure(); // Sync the scroll area to make sure scroll events don't fire and scroll the viewport to an // invalid location @@ -1551,9 +1517,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._bufferService.reset(); this._coreService.reset(); this._coreMouseService.reset(); - if (this._selectionService) { - this._selectionService.reset(); - } + this._selectionService?.reset(); // reattach this._customKeyEventHandler = customKeyEventHandler; @@ -1563,9 +1527,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // do a full screen refresh this.refresh(0, this.rows - 1); - if (this.viewport) { - this.viewport.syncScrollArea(); - } + this.viewport?.syncScrollArea(); } // TODO: Remove cancel function and cancelEvents option diff --git a/src/Terminal2.test.ts b/src/Terminal2.test.ts index efb2a446..f7046240 100644 --- a/src/Terminal2.test.ts +++ b/src/Terminal2.test.ts @@ -34,7 +34,6 @@ if (os.platform() === 'darwin') { // filter skipFilenames const FILES = TESTFILES.filter(value => SKIP_FILES.indexOf(value.split('/').slice(-1)[0]) === -1); - describe('Escape Sequence Files', function(): void { this.timeout(1000); @@ -44,6 +43,9 @@ describe('Escape Sequence Files', function(): void { let customHandler: IDisposable | undefined; before(() => { + if (process.platform === 'win32') { + return; + } ptyTerm = (pty as any).open({cols: COLS, rows: ROWS}); slaveEnd = ptyTerm._slave; term = new Terminal({cols: COLS, rows: ROWS}); @@ -51,12 +53,15 @@ describe('Escape Sequence Files', function(): void { }); after(() => { + if (process.platform === 'win32') { + return; + } ptyTerm._master.end(); ptyTerm._master.destroy(); }); FILES.forEach(filename => { - it(filename.split('/').slice(-1)[0], async () => { + (process.platform === 'win32' ? it.skip : it)(filename.split('/').slice(-1)[0], async () => { // reset terminal and handler if (customHandler) { customHandler.dispose(); diff --git a/src/browser/Clipboard.ts b/src/browser/Clipboard.ts index 594e4429..a7c48bfb 100644 --- a/src/browser/Clipboard.ts +++ b/src/browser/Clipboard.ts @@ -82,12 +82,12 @@ export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextA // Reset the terminal textarea's styling // Timeout needs to be long enough for click event to be handled. setTimeout(() => { - textarea.style.position = null; - textarea.style.width = null; - textarea.style.height = null; - textarea.style.left = null; - textarea.style.top = null; - textarea.style.zIndex = null; + textarea.style.position = ''; + textarea.style.width = ''; + textarea.style.height = ''; + textarea.style.left = ''; + textarea.style.top = ''; + textarea.style.zIndex = ''; }, 200); } diff --git a/src/browser/MouseZoneManager.ts b/src/browser/MouseZoneManager.ts index 7eb7c5f8..d697c133 100644 --- a/src/browser/MouseZoneManager.ts +++ b/src/browser/MouseZoneManager.ts @@ -175,11 +175,9 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager { // Find the active zone, prevent event propagation if found to prevent other // components from handling the mouse event. const zone = this._findZoneEventAt(e); - if (zone) { - if (zone.willLinkActivate(e)) { - e.preventDefault(); - e.stopImmediatePropagation(); - } + if (zone?.willLinkActivate(e)) { + e.preventDefault(); + e.stopImmediatePropagation(); } } diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index e9ad5b3a..a33bef28 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -60,9 +60,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { public dispose(): void { this._container.removeChild(this._canvas); - if (this._charAtlas) { - this._charAtlas.dispose(); - } + this._charAtlas?.dispose(); } private _initCanvas(): void { diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index 3631bbb7..4eb6550c 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -688,9 +688,7 @@ export class SelectionService implements ISelectionService { // reverseIndex) and delete in a splice is only ever used when the same // number of elements was just added. Given this is could actually be // beneficial to leave the selection as is for these cases. - if (this._trimListener) { - this._trimListener.dispose(); - } + this._trimListener.dispose(); this._trimListener = e.activeBuffer.lines.onTrim(amount => this._onTrim(amount)); } diff --git a/yarn.lock b/yarn.lock index c96c390c..cb02cf91 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5184,10 +5184,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@3.6: - version "3.6.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.3.tgz#fea942fabb20f7e1ca7164ff626f1a9f3f70b4da" - integrity sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw== +typescript@3.7: + version "3.7.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb" + integrity sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ== typescript@^3.5.1: version "3.5.1"