diff --git a/config.ts b/config.ts new file mode 100644 index 00000000..3585af47 --- /dev/null +++ b/config.ts @@ -0,0 +1,5 @@ +// SPDX-FileCopyrightText: none +// +// SPDX-License-Identifier: MIT + +import "jest-ts-auto-mock"; diff --git a/package.json b/package.json index a6da1c77..f4e34998 100644 --- a/package.json +++ b/package.json @@ -18,13 +18,16 @@ "eslint": "^7.32.0", "eslint-config-prettier": "^8.3.0", "husky": "^7.0.2", - "jest": "^27.1.0", + "jest": "^27.2.0", + "jest-ts-auto-mock": "^2.0.0", "lint-staged": "^11.1.2", "prettier": "2.3.2", + "ts-auto-mock": "^3.5.0", "ts-jest": "^27.0.5", + "ttypescript": "^1.5.12", "typedoc": "^0.21.6", "typedoc-plugin-rename-defaults": "^0.3.0", - "typescript": "^4.3.5" + "typescript": "^4.4.3" }, "scripts": { "clean": "bin/clean.sh", @@ -59,9 +62,16 @@ "homepage": "https://github.com/gikari/bismuth#readme", "jest": { "preset": "ts-jest", + "transform": { + ".(ts|tsx)": "ts-jest" + }, "testEnvironment": "node", "globals": { "ts-jest": { + "compiler": "ttypescript", + "setupFiles": [ + "config.ts" + ], "diagnostics": { "ignoreCodes": [ "TS151001" diff --git a/src/driver/window.ts b/src/driver/window.ts index 33919f4c..031b07ec 100644 --- a/src/driver/window.ts +++ b/src/driver/window.ts @@ -131,7 +131,11 @@ export class KWinWindow implements DriverWindow { this.debug = debug; } - public commit(geometry?: Rect, noBorder?: boolean, keepAbove?: boolean) { + public commit( + geometry?: Rect, + noBorder?: boolean, + keepAbove?: boolean + ): void { this.debug.debugObj(() => [ "KWinWindow#commit", { geometry, noBorder, keepAbove }, diff --git a/src/engine/index.test.ts b/src/engine/index.test.ts new file mode 100644 index 00000000..586eb556 --- /dev/null +++ b/src/engine/index.test.ts @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: 2021 Mikhail Zolotukhin +// +// SPDX-License-Identifier: MIT + +import { createMock } from "ts-auto-mock"; +import { On, method } from "ts-auto-mock/extension"; + +import { TilingEngine } from "."; +import Config from "../config"; +import { Controller } from "../controller"; +import { DriverSurface } from "../driver/surface"; +import { DriverWindow } from "../driver/window"; + +import Debug from "../util/debug"; +import Rect from "../util/rect"; +import TileLayout from "./layout/tile_layout"; +import LayoutStore from "./layout_store"; +import Window, { WindowState } from "./window"; +import WindowStore from "./window_store"; + +describe("arrange", () => { + it("happens on all screens", () => { + const screenMock = createMock(); + const fakeScreens = [screenMock, screenMock, screenMock, screenMock]; + + const controllerMock = createMock({ screens: fakeScreens }); + const debugMock = createMock(); + const configMock = createMock(); + const engine = new TilingEngine(controllerMock, configMock, debugMock); + + jest.spyOn(engine, "arrangeScreen").mockReturnValue(); + + engine.arrange(); + + expect(engine.arrangeScreen).toBeCalledTimes(4); + }); +}); + +describe("arrangeScreen", () => { + describe("window states are correctly changed", () => { + // Arrange + const controllerMock = createMock(); + const debugMock = createMock(); + const configMock = createMock(); + const engine = new TilingEngine(controllerMock, configMock, debugMock); + + const window1 = createMock({ + shouldFloat: false, + state: WindowState.Undecided, + }); + + const window2 = createMock({ + shouldFloat: true, + state: WindowState.Undecided, + }); + + const windowsStoreMock = createMock({ + getVisibleWindows: () => [window1, window2], + getVisibleTileables: () => [], + }); + + engine.windows = windowsStoreMock; + + const layoutStoreMock = createMock({ + getCurrentLayout: () => createMock(), + }); + engine.layouts = layoutStoreMock; + + jest + .spyOn(TilingEngine.prototype as any, "getTilingArea") + .mockReturnValue(createMock()); + + const mockSurface = createMock(); + + // Act + engine.arrangeScreen(mockSurface); + + // Assert + it("sets all undecided windows to tiled state, when they should not float", () => { + expect(window1.state).toEqual(WindowState.Tiled); + }); + it("sets all undecided windows to float state, when they should float", () => { + expect(window2.state).toEqual(WindowState.Floating); + }); + }); +}); diff --git a/src/engine/index.ts b/src/engine/index.ts index fc0a1fb8..84795422 100644 --- a/src/engine/index.ts +++ b/src/engine/index.ts @@ -21,6 +21,7 @@ import { overlap, wrapIndex } from "../util/func"; import Config from "../config"; import Debug from "../util/debug"; import qmlSetTimeout from "../util/timer"; +import { WindowsLayout } from "./layout"; export type Direction = "up" | "down" | "left" | "right"; @@ -225,7 +226,7 @@ export class TilingEngine implements Engine { /** * Arrange tiles on all screens. */ - public arrange() { + public arrange(): void { this.debug.debug(() => "arrange"); this.controller.screens.forEach((driverSurface: DriverSurface) => { @@ -234,48 +235,44 @@ export class TilingEngine implements Engine { } /** - * Arrange tiles on a screen. + * Arrange tiles on one screen + * + * @param screenSurface screen's surface, on which windows should be arranged */ - public arrangeScreen(srf: DriverSurface) { - const layout = this.layouts.getCurrentLayout(srf); + public arrangeScreen(screenSurface: DriverSurface) { + const layout = this.layouts.getCurrentLayout(screenSurface); - const workingArea = srf.workingArea; + const workingArea = screenSurface.workingArea; + const tilingArea = this.getTilingArea(workingArea, layout); - let tilingArea: Rect; - if (this.config.monocleMaximize && layout instanceof MonocleLayout) - tilingArea = workingArea; - else - tilingArea = workingArea.gap( - this.config.screenGapLeft, - this.config.screenGapRight, - this.config.screenGapTop, - this.config.screenGapBottom - ); - - const visibles = this.windows.getVisibleWindows(srf); + const visibleWindows = this.windows.getVisibleWindows(screenSurface); this.debug.debugObj(() => [ "arrangeScreen", { layout, - srf, - visibles: visibles.length, + screenSurface, + visibles: visibleWindows.length, }, ]); - visibles.forEach((window) => { - if (window.state === WindowState.Undecided) - window.state = window.shouldFloat - ? WindowState.Floating - : WindowState.Tiled; + // Set correct window state for new windows + visibleWindows.forEach((win: Window) => { + if (win.state === WindowState.Undecided) { + win.state = win.shouldFloat ? WindowState.Floating : WindowState.Tiled; + } }); - const tileables = this.windows.getVisibleTileables(srf); - if (this.config.maximizeSoleTile && tileables.length === 1) { - tileables[0].state = WindowState.Maximized; - tileables[0].geometry = workingArea; - } else if (tileables.length > 0) - layout.apply(this.controller, tileables, tilingArea); + const tileableWindows = this.windows.getVisibleTileables(screenSurface); + // Maximize sole tile if enabled or apply the current layout as expected + if (this.config.maximizeSoleTile && tileableWindows.length === 1) { + tileableWindows[0].state = WindowState.Maximized; + tileableWindows[0].geometry = workingArea; + } else if (tileableWindows.length > 0) { + layout.apply(this.controller, tileableWindows, tilingArea); + } + + // If enabled, limit the windows' width if ( this.config.limitTileWidthRatio > 0 && !(layout instanceof MonocleLayout) @@ -283,7 +280,7 @@ export class TilingEngine implements Engine { const maxWidth = Math.floor( workingArea.height * this.config.limitTileWidthRatio ); - tileables + tileableWindows .filter((tile) => tile.tiled && tile.geometry.width > maxWidth) .forEach((tile) => { const g = tile.geometry; @@ -296,8 +293,9 @@ export class TilingEngine implements Engine { }); } - visibles.forEach((window) => window.commit()); - this.debug.debugObj(() => ["arrangeScreen/finished", { srf }]); + // Commit window assigned properties + visibleWindows.forEach((win: Window) => win.commit()); + this.debug.debugObj(() => ["arrangeScreen/finished", { screenSurface }]); } /** @@ -632,4 +630,26 @@ export class TilingEngine implements Engine { public showNotification(text: string): void { this.controller.showNotification(text); } + + /** + * Returns the tiling area for the given working area and the windows layout. + * + * Tiling area is the area we are allowed to put windows in, not counting the inner gaps + * between them. I.e. working are without gaps. + * + * @param workingArea area in which we are allowed to work. @see DriverSurface#workingArea + * @param layout windows layout used + */ + private getTilingArea(workingArea: Rect, layout: WindowsLayout): Rect { + if (this.config.monocleMaximize && layout instanceof MonocleLayout) { + return workingArea; + } else { + return workingArea.gap( + this.config.screenGapLeft, + this.config.screenGapRight, + this.config.screenGapTop, + this.config.screenGapBottom + ); + } + } } diff --git a/src/engine/window.ts b/src/engine/window.ts index bec3789b..12464dbe 100644 --- a/src/engine/window.ts +++ b/src/engine/window.ts @@ -159,7 +159,7 @@ export default class Window { this.weightMap = {}; } - public commit() { + public commit(): void { const state = this.state; this.debug.debugObj(() => ["Window#commit", { state: WindowState[state] }]); switch (state) { diff --git a/test/simple.test.ts b/test/simple.test.ts deleted file mode 100644 index ed612d8a..00000000 --- a/test/simple.test.ts +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-FileCopyrightText: 2018-2019 Eon S. Jeon -// SPDX-FileCopyrightText: 2021 Mikhail Zolotukhin -// -// SPDX-License-Identifier: MIT - -describe("it just works", function () { - it("just works", function () { - expect(2 + 2).toBe(4); - }); -}); diff --git a/tsconfig.json b/tsconfig.json index 02ffddcc..2db8ec05 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,13 @@ "alwaysStrict": true, "strict": true, "moduleResolution": "node", - "module": "ES6" + "module": "ES6", + "plugins": [ + { + "transform": "ts-auto-mock/transformer", + "cacheBetweenTests": false + } + ] }, "typedocOptions": { "entryPoints": ["src"]