test: add a few basic tests with mocks

Tests directory probably will be used for integration tests.
This commit is contained in:
Mikhail Zolotukhin 2021-09-15 20:18:23 +03:00
parent b759156b3c
commit 13b1987ee0
8 changed files with 169 additions and 48 deletions

5
config.ts Normal file
View File

@ -0,0 +1,5 @@
// SPDX-FileCopyrightText: none
//
// SPDX-License-Identifier: MIT
import "jest-ts-auto-mock";

View File

@ -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": [
"<rootDir>config.ts"
],
"diagnostics": {
"ignoreCodes": [
"TS151001"

View File

@ -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 },

86
src/engine/index.test.ts Normal file
View File

@ -0,0 +1,86 @@
// SPDX-FileCopyrightText: 2021 Mikhail Zolotukhin <mail@genda.life>
//
// 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<DriverSurface>();
const fakeScreens = [screenMock, screenMock, screenMock, screenMock];
const controllerMock = createMock<Controller>({ screens: fakeScreens });
const debugMock = createMock<Debug>();
const configMock = createMock<Config>();
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<Controller>();
const debugMock = createMock<Debug>();
const configMock = createMock<Config>();
const engine = new TilingEngine(controllerMock, configMock, debugMock);
const window1 = createMock<Window>({
shouldFloat: false,
state: WindowState.Undecided,
});
const window2 = createMock<Window>({
shouldFloat: true,
state: WindowState.Undecided,
});
const windowsStoreMock = createMock<WindowStore>({
getVisibleWindows: () => [window1, window2],
getVisibleTileables: () => [],
});
engine.windows = windowsStoreMock;
const layoutStoreMock = createMock<LayoutStore>({
getCurrentLayout: () => createMock<TileLayout>(),
});
engine.layouts = layoutStoreMock;
jest
.spyOn(TilingEngine.prototype as any, "getTilingArea")
.mockReturnValue(createMock<Rect>());
const mockSurface = createMock<DriverSurface>();
// 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);
});
});
});

View File

@ -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
);
}
}
}

View File

@ -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) {

View File

@ -1,10 +0,0 @@
// SPDX-FileCopyrightText: 2018-2019 Eon S. Jeon <esjeon@hyunmu.am>
// SPDX-FileCopyrightText: 2021 Mikhail Zolotukhin <mail@genda.life>
//
// SPDX-License-Identifier: MIT
describe("it just works", function () {
it("just works", function () {
expect(2 + 2).toBe(4);
});
});

View File

@ -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"]