diff --git a/eslint.config.js b/eslint.config.js index b5f747860..1de7f5897 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -3,6 +3,7 @@ import globals from "globals"; import path from "node:path"; import pluginJs from "@eslint/js"; import stylistic from "@stylistic/eslint-plugin"; +import jest from "eslint-plugin-jest"; import tseslint from "typescript-eslint"; import { fileURLToPath } from "node:url"; import { includeIgnoreFile } from "@eslint/compat"; @@ -112,7 +113,7 @@ export default [ "object-curly-newline": ["error", { multiline: true, consistent: true }], "object-curly-spacing": ["error", "always"], "object-property-newline": ["error", { allowAllPropertiesOnSameLine: true }], - // "no-undef": "error", // TODO: Enable this rule, https://github.com/openfrontio/OpenFrontIO/issues/1786 + "no-undef": "error", "no-unused-vars": "off", // @typescript-eslint/no-unused-vars "quote-props": ["error", "consistent-as-needed"], // 'sort-imports': 'error', // TODO: Enable this rule, https://github.com/openfrontio/OpenFrontIO/issues/1787 @@ -142,6 +143,19 @@ export default [ "sort-keys": "off", }, }, + { + languageOptions: { + globals: { + ...globals.jest, + }, + }, + files: [ + "**/*.test.{js,ts,jsx,tsx}", + "tests/**/*.{js,ts,jsx,tsx}", + ], + plugins: ["jest"], + ...jest.configs['flat/style'], + }, { files: [ "src/client/**/*.{js,ts,jsx,tsx}", diff --git a/package-lock.json b/package-lock.json index 2612dafad..5b9b6d6f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,7 @@ "@eslint/compat": "^1.2.7", "@eslint/js": "^9.21.0", "@stylistic/eslint-plugin": "^5.2.3", + "@swc/core": "^1.13.3", "@swc/jest": "^0.2.39", "@total-typescript/ts-reset": "^0.6.1", "@types/benchmark": "^2.1.5", @@ -74,6 +75,7 @@ "eslint": "^9.21.0", "eslint-config-prettier": "^10.1.1", "eslint-formatter-gha": "^1.5.2", + "eslint-plugin-jest": "^29.0.1", "eslint-webpack-plugin": "^5.0.0", "file-loader": "^6.2.0", "globals": "^16.0.0", @@ -3600,13 +3602,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.2.tgz", - "integrity": "sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.15.0", + "@eslint/core": "^0.15.2", "levn": "^0.4.1" }, "engines": { @@ -3614,9 +3616,9 @@ } }, "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.0.tgz", - "integrity": "sha512-b7ePw78tEWWkpgZCDYkbqDOP8dmM6qe+AOC6iuJqlq1R/0ahMAeH3qynpnqKFGkMltrp44ohV4ubGyvLX28tzw==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -6415,7 +6417,6 @@ "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.23" @@ -6460,7 +6461,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=10" } @@ -6477,7 +6477,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=10" } @@ -6494,7 +6493,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=10" } @@ -6511,7 +6509,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=10" } @@ -6528,7 +6525,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=10" } @@ -6545,7 +6541,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=10" } @@ -6562,7 +6557,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=10" } @@ -6579,7 +6573,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=10" } @@ -6596,7 +6589,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=10" } @@ -6613,7 +6605,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=10" } @@ -6649,7 +6640,6 @@ "integrity": "sha512-u1iIVZV9Q0jxY+yM2vw/hZGDNudsN85bBpTqzAQ9rzkxW9D+e3aEM4Han+ow518gSewkXgjmEK0BD79ZcNVgPw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@swc/counter": "^0.1.3" } @@ -11168,6 +11158,32 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/eslint-plugin-jest": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-29.0.1.tgz", + "integrity": "sha512-EE44T0OSMCeXhDrrdsbKAhprobKkPtJTbQz5yEktysNpHeDZTAL1SfDTNKmcFfJkY6yrQLtTKZALrD3j/Gpmiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.0.0" + }, + "engines": { + "node": "^20.12.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "jest": "*" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, "node_modules/eslint-scope": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", diff --git a/package.json b/package.json index 0d1603dea..be092a2b6 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@eslint/compat": "^1.2.7", "@eslint/js": "^9.21.0", "@stylistic/eslint-plugin": "^5.2.3", + "@swc/core": "^1.13.3", "@swc/jest": "^0.2.39", "@total-typescript/ts-reset": "^0.6.1", "@types/benchmark": "^2.1.5", @@ -61,6 +62,7 @@ "eslint": "^9.21.0", "eslint-config-prettier": "^10.1.1", "eslint-formatter-gha": "^1.5.2", + "eslint-plugin-jest": "^29.0.1", "eslint-webpack-plugin": "^5.0.0", "file-loader": "^6.2.0", "globals": "^16.0.0", diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index b96cd8257..907dfd4f6 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -193,7 +193,7 @@ export class ClientGameRunner { private lastMousePosition: { x: number; y: number } | null = null; private lastMessageTime = 0; - private connectionCheckInterval: NodeJS.Timeout | null = null; + private connectionCheckInterval: ReturnType | null = null; constructor( private lobby: LobbyConfig, diff --git a/src/client/HostLobbyModal.ts b/src/client/HostLobbyModal.ts index e8027a4c6..2d125eeb3 100644 --- a/src/client/HostLobbyModal.ts +++ b/src/client/HostLobbyModal.ts @@ -55,7 +55,7 @@ export class HostLobbyModal extends LitElement { @state() private lobbyCreatorClientID = ""; @state() private lobbyIdVisible = true; - private playersInterval: NodeJS.Timeout | null = null; + private playersInterval: ReturnType | null = null; // Add a new timer for debouncing bot changes private botsUpdateTimer: number | null = null; private userSettings: UserSettings = new UserSettings(); diff --git a/src/client/InputHandler.ts b/src/client/InputHandler.ts index 5f0ab44e6..eaaf4fec6 100644 --- a/src/client/InputHandler.ts +++ b/src/client/InputHandler.ts @@ -129,7 +129,7 @@ export class InputHandler { private alternateView = false; - private moveInterval: NodeJS.Timeout | null = null; + private moveInterval: ReturnType | null = null; private activeKeys = new Set(); private keybinds: Record = {}; diff --git a/src/client/JoinPrivateLobbyModal.ts b/src/client/JoinPrivateLobbyModal.ts index d126d6ace..10dfbec0b 100644 --- a/src/client/JoinPrivateLobbyModal.ts +++ b/src/client/JoinPrivateLobbyModal.ts @@ -23,7 +23,7 @@ export class JoinPrivateLobbyModal extends LitElement { @state() private hasJoined = false; @state() private players: string[] = []; - private playersInterval: NodeJS.Timeout | null = null; + private playersInterval: ReturnType | null = null; connectedCallback() { super.connectedCallback(); diff --git a/src/client/LocalServer.ts b/src/client/LocalServer.ts index 5f169c8ae..cf9f91645 100644 --- a/src/client/LocalServer.ts +++ b/src/client/LocalServer.ts @@ -35,7 +35,7 @@ export class LocalServer { private turnsExecuted = 0; private turnStartTime = 0; - private turnCheckInterval: NodeJS.Timeout; + private turnCheckInterval: ReturnType; constructor( private lobbyConfig: LobbyConfig, diff --git a/src/client/graphics/TransformHandler.ts b/src/client/graphics/TransformHandler.ts index 252ee8042..05569a647 100644 --- a/src/client/graphics/TransformHandler.ts +++ b/src/client/graphics/TransformHandler.ts @@ -20,7 +20,7 @@ export class TransformHandler { private lastGoToCallTime: number | null = null; private target: Cell | null; - private intervalID: NodeJS.Timeout | null = null; + private intervalID: ReturnType | null = null; private changed = false; constructor( diff --git a/tests/MissileSilo.test.ts b/tests/MissileSilo.test.ts index c3a1f5ab1..1459a2543 100644 --- a/tests/MissileSilo.test.ts +++ b/tests/MissileSilo.test.ts @@ -91,7 +91,7 @@ describe("MissileSilo", () => { }); test("missilesilo should have increased level after upgrade", async () => { - expect(attacker.units(UnitType.MissileSilo)[0].level()).toEqual(1); + expect(attacker.units(UnitType.MissileSilo)[0].level()).toBe(1); const upgradeStructureExecution = new UpgradeStructureExecution( attacker, @@ -100,6 +100,6 @@ describe("MissileSilo", () => { game.addExecution(upgradeStructureExecution); executeTicks(game, 2); - expect(attacker.units(UnitType.MissileSilo)[0].level()).toEqual(2); + expect(attacker.units(UnitType.MissileSilo)[0].level()).toBe(2); }); }); diff --git a/tests/PlayerImpl.test.ts b/tests/PlayerImpl.test.ts index 7f47a1cd2..463f20350 100644 --- a/tests/PlayerImpl.test.ts +++ b/tests/PlayerImpl.test.ts @@ -96,7 +96,7 @@ describe("PlayerImpl", () => { const ports = player.tradingPorts(playerPort); - expect(ports.length).toBe(3); + expect(ports).toHaveLength(3); }); test("Can't send alliance requests when dead", () => { diff --git a/tests/TeamAssignment.test.ts b/tests/TeamAssignment.test.ts index 5d9f5bcab..e4002df29 100644 --- a/tests/TeamAssignment.test.ts +++ b/tests/TeamAssignment.test.ts @@ -83,7 +83,7 @@ describe("assignTeams", () => { expect(result.get(players[1])).toEqual(ColoredTeams.Red); expect(result.get(players[2])).toEqual(ColoredTeams.Red); - expect(result.get(players[3])).toEqual("kicked"); + expect(result.get(players[3])).toBe("kicked"); expect(result.get(players[4])).toEqual(ColoredTeams.Blue); expect(result.get(players[5])).toEqual(ColoredTeams.Blue); @@ -151,7 +151,7 @@ describe("assignTeams", () => { expect(result.get(players[0])).toEqual(ColoredTeams.Red); expect(result.get(players[1])).toEqual(ColoredTeams.Red); - expect(result.get(players[2])).toEqual("kicked"); + expect(result.get(players[2])).toBe("kicked"); expect(result.get(players[3])).toEqual(ColoredTeams.Blue); expect(result.get(players[4])).toEqual(ColoredTeams.Blue); expect(result.get(players[5])).toEqual(ColoredTeams.Yellow); diff --git a/tests/UnitGrid.test.ts b/tests/UnitGrid.test.ts index bd6629910..b9bb8a11f 100644 --- a/tests/UnitGrid.test.ts +++ b/tests/UnitGrid.test.ts @@ -90,7 +90,7 @@ describe("Unit Grid range tests", () => { range, units, // remove readonly ); - expect(result.length).toBe(expectedResult); + expect(result).toHaveLength(expectedResult); }, ); diff --git a/tests/core/executions/SAMLauncherExecution.test.ts b/tests/core/executions/SAMLauncherExecution.test.ts index 0e7fc0d98..c4f5ad557 100644 --- a/tests/core/executions/SAMLauncherExecution.test.ts +++ b/tests/core/executions/SAMLauncherExecution.test.ts @@ -230,7 +230,7 @@ describe("SAM", () => { test("SAM should have increased level after upgrade", async () => { defender.buildUnit(UnitType.SAMLauncher, game.ref(1, 1), {}); - expect(defender.units(UnitType.SAMLauncher)[0].level()).toEqual(1); + expect(defender.units(UnitType.SAMLauncher)[0].level()).toBe(1); const upgradeStructureExecution = new UpgradeStructureExecution( defender, @@ -239,6 +239,6 @@ describe("SAM", () => { game.addExecution(upgradeStructureExecution); executeTicks(game, 2); - expect(defender.units(UnitType.SAMLauncher)[0].level()).toEqual(2); + expect(defender.units(UnitType.SAMLauncher)[0].level()).toBe(2); }); }); diff --git a/tests/core/game/RailNetwork.test.ts b/tests/core/game/RailNetwork.test.ts index 1ebe38235..f900552ef 100644 --- a/tests/core/game/RailNetwork.test.ts +++ b/tests/core/game/RailNetwork.test.ts @@ -42,7 +42,7 @@ describe("StationManagerImpl", () => { const station = createMockStation(1); manager.addStation(station); manager.removeStation(station); - expect(manager.findStation(station.unit)).toBe(null); + expect(manager.findStation(station.unit)).toBeNull(); }); });