diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..2152a31 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,12 @@ +const {createDefaultPreset} = require("ts-jest") + +const tsJestTransformCfg = createDefaultPreset().transform; + +/** @type {import ("jest").Config} **/ +module.exports = { + preset: "ts-jest", + testEnvironment: "node", + transform: { + ...tsJestTransformCfg, + }, +}; diff --git a/modules/ecs6-class/line.ts b/modules/ecs6-class/line.ts index e2d6086..424be36 100644 --- a/modules/ecs6-class/line.ts +++ b/modules/ecs6-class/line.ts @@ -33,11 +33,11 @@ export default class Line { getPointByX(x: number) { - if (this.slope && this.n) { - let y = this.slope * x + this.n - return new Point({ x, y }) - } + if (this.slope !== undefined && this.n !== undefined) { + let y = this.slope * x + this.n; + return new Point({ x, y }); } +} getPointByY(y: number) { if (this.slope && this.n) { diff --git a/modules/geometry-calculation.ts b/modules/geometry-calculation.ts index 22ab78b..032433d 100644 --- a/modules/geometry-calculation.ts +++ b/modules/geometry-calculation.ts @@ -3,7 +3,7 @@ import Point from './ecs6-class/point'; export const calculateDistance = (point1: Point, point2: Point): number => { let distanceX = (point2.x - point1.x) ** 2; - let distanceY = (point2.y - point2.y) ** 2; + let distanceY = (point2.y - point1.y) ** 2; const distance = Math.sqrt(distanceX + distanceY); return distance; } @@ -12,16 +12,19 @@ export const calculateJunctionPoint = (line1: Line, line2: Line): Boolean | Poin if (line1.slope === line2.slope) { if (line1.n === line2.n) { return true - } - else { + } else { return false } - } - else { - if (line1.n !== undefined && line1.slope !== undefined && line2.n !== undefined && line2.slope !== undefined) { + } else { + if ( + line1.n !== undefined && line1.slope !== undefined && + line2.n !== undefined && line2.slope !== undefined + ) { const x = (line1.n - line2.n) / (line2.slope - line1.slope) const junctionPoint = line1.getPointByX(x); return junctionPoint + } else { + throw new Error('Slope or n is undefined for one of the lines'); } } } diff --git a/modules/tests/geometry-calculation.test.ts b/modules/tests/geometry-calculation.test.ts new file mode 100644 index 0000000..13f0207 --- /dev/null +++ b/modules/tests/geometry-calculation.test.ts @@ -0,0 +1,92 @@ +import Point from '../ecs6-class/point'; +import Line from '../ecs6-class/line'; +import { calculateDistance, calculateJunctionPoint, isPointOnLine } from '../geometry-calculation'; + +describe('geometry-calculation module', () => { + test('calculateDistance returns correct distance', () => { + const p1 = new Point({ x: 0, y: 0 }); + const p2 = new Point({ x: 3, y: 4 }); + expect(calculateDistance(p1, p2)).toBe(5); // 3-4-5 triangle + }); + + test('calculateJunctionPoint returns true for identical lines', () => { + const p1 = new Point({ x: 0, y: 0 }); + const p2 = new Point({ x: 2, y: 2 }); + const line1 = new Line({ point1: p1, point2: p2 }); + const line2 = new Line({ point1: p1, point2: p2 }); + line1.slope = (p2.y - p1.y) / (p2.x - p1.x); + line1.n = p1.y - line1.slope * p1.x; + line2.slope = (p2.y - p1.y) / (p2.x - p1.x); + line2.n = p1.y - line2.slope * p1.x; + expect(calculateJunctionPoint(line1, line2)).toBe(true); + }); + + test('calculateJunctionPoint returns false for parallel lines', () => { + const p1 = new Point({ x: 0, y: 0 }); + const p2 = new Point({ x: 2, y: 2 }); + const p3 = new Point({ x: 0, y: 1 }); + const p4 = new Point({ x: 2, y: 3 }); + const line1 = new Line({ point1: p1, point2: p2 }); + const line2 = new Line({ point1: p3, point2: p4 }); + line1.slope = (p2.y - p1.y) / (p2.x - p1.x); + line1.n = p1.y - line1.slope * p1.x; + line2.slope = (p4.y - p3.y) / (p4.x - p3.x); + line2.n = p3.y - line2.slope * p3.x; + expect(calculateJunctionPoint(line1, line2)).toBe(false); + }); + + test('calculateJunctionPoint returns intersection point for crossing lines', () => { + const p1 = new Point({ x: 0, y: 0 }); + const p2 = new Point({ x: 2, y: 2 }); + const p3 = new Point({ x: 0, y: 2 }); + const p4 = new Point({ x: 2, y: 0 }); + const line1 = new Line({ point1: p1, point2: p2 }); + const line2 = new Line({ point1: p3, point2: p4 }); + line1.slope = (p2.y - p1.y) / (p2.x - p1.x); + line1.n = p1.y - line1.slope * p1.x; + line2.slope = (p4.y - p3.y) / (p4.x - p3.x); + line2.n = p3.y - line2.slope * p3.x; + const intersection = calculateJunctionPoint(line1, line2); + if (intersection instanceof Point) { + expect(intersection.x).toBeCloseTo(1); + expect(intersection.y).toBeCloseTo(1); + } else { + throw new Error('Intersection is not a Point'); + } + }); + + test('isPointOnLine returns true for point on line', () => { + const p1 = new Point({ x: 0, y: 0 }); + const p2 = new Point({ x: 2, y: 2 }); + const line = new Line({ point1: p1, point2: p2 }); + line.slope = (p2.y - p1.y) / (p2.x - p1.x); + line.n = p1.y - line.slope * p1.x; + const pointOnLine = new Point({ x: 1, y: 1 }); + expect(isPointOnLine(line, pointOnLine)).toBe(true); + }); + + test('isPointOnLine returns false for point not on line', () => { + const p1 = new Point({ x: 0, y: 0 }); + const p2 = new Point({ x: 2, y: 2 }); + const line = new Line({ point1: p1, point2: p2 }); + line.slope = (p2.y - p1.y) / (p2.x - p1.x); + line.n = p1.y - line.slope * p1.x; + const pointNotOnLine = new Point({ x: 1, y: 2 }); + expect(isPointOnLine(line, pointNotOnLine)).toBe(false); + }); + + test('calculateJunctionPoint throws error for undefined slope or n', () => { + const p1 = new Point({ x: 0, y: 0 }); + const p2 = new Point({ x: 2, y: 2 }); + const line1 = new Line({ point1: p1, point2: p2 }); + line1.slope = (p2.y - p1.y) / (p2.x - p1.x); + line1.n = p1.y - line1.slope * p1.x; + const line2 = new Line({ point1: p1, point2: p1 }); // קו עם אותו נקודה, שיפוע לא מוגדר + line2.slope = undefined; // או line2.n = undefined; + + expect(() => calculateJunctionPoint(line1, line2)).toThrow(Error); +}); + + + +}); \ No newline at end of file diff --git a/modules/tests/line.test.ts b/modules/tests/line.test.ts new file mode 100644 index 0000000..8432910 --- /dev/null +++ b/modules/tests/line.test.ts @@ -0,0 +1,64 @@ +import Point from '../ecs6-class/point'; +import Line from '../ecs6-class/line'; + +describe('Line Class', () => { + test('calculateSlope calculates correct slope', () => { + const p1 = new Point({ x: 0, y: 0 }); + const p2 = new Point({ x: 2, y: 4 }); + const line = new Line({ point1: p1, point2: p2 }); + line.calculateSlope(); + expect(line.slope).toBe((0 - 4) / (0 - 2)); // Should be 2 + }); + + test('calculateNOfLineFunction calculates correct n', () => { + const p1 = new Point({ x: 1, y: 3 }); + const p2 = new Point({ x: 2, y: 5 }); + const line = new Line({ point1: p1, point2: p2 }); + const slope = (p2.y - p1.y) / (p2.x - p1.x); + line.slope = slope; + line.calculateNOfLineFunction(); + expect(line.n).toBe(p1.y - line.slope! * p1.x); + }); + + test('getPointByX returns correct point', () => { + const p1 = new Point({ x: 1, y: 3 }); + const p2 = new Point({ x: 2, y: 5 }); + const line = new Line({ point1: p1, point2: p2 }); + line.slope = (p2.y - p1.y) / (p2.x - p1.x); + line.n = p1.y - line.slope * p1.x; + const point = line.getPointByX(10); + expect(point?.x).toBe(10); + expect(point?.y).toBeCloseTo(line.slope! * 10 + line.n!); + }); + + test('getPointByY returns correct point', () => { + const p1 = new Point({ x: 1, y: 3 }); + const p2 = new Point({ x: 2, y: 5 }); + const line = new Line({ point1: p1, point2: p2 }); + line.slope = (p2.y - p1.y) / (p2.x - p1.x); + line.n = p1.y - line.slope * p1.x; + const point = line.getPointByY(7); + expect(point?.y).toBe(7); + expect(point?.x).toBeCloseTo((7 - line.n!) / line.slope!); + }); + + test('getPointOnXAsis returns point on X axis', () => { + const p1 = new Point({ x: 1, y: 3 }); + const p2 = new Point({ x: 2, y: 5 }); + const line = new Line({ point1: p1, point2: p2 }); + line.slope = (p2.y - p1.y) / (p2.x - p1.x); + line.n = p1.y - line.slope * p1.x; + const point = line.getPointOnXAsis(); + expect(point?.y).toBe(0); + }); + + test('getPointOnYAsis returns point on Y axis', () => { + const p1 = new Point({ x: 1, y: 3 }); + const p2 = new Point({ x: 2, y: 5 }); + const line = new Line({ point1: p1, point2: p2 }); + line.slope = (p2.y - p1.y) / (p2.x - p1.x); + line.n = p1.y - line.slope * p1.x; + const point = line.getPointOnYAsis(); + expect(point?.x).toBe(0); + }); +}); \ No newline at end of file diff --git a/modules/tests/point.test.ts b/modules/tests/point.test.ts new file mode 100644 index 0000000..d048c10 --- /dev/null +++ b/modules/tests/point.test.ts @@ -0,0 +1,31 @@ +import Point from '../ecs6-class/point'; + +describe('Point Class', () => { + test('Default values of x and y should be 0', () => { + const point = new Point(); + expect(point.x).toBe(0); + expect(point.y).toBe(0); + }); + + test('Should initialize x and y with provided values', () => { + const point = new Point({ x: 5, y: 10 }); + expect(point.x).toBe(5); + expect(point.y).toBe(10); + }); + + test('moveVertical should correctly update y value', () => { + const point = new Point({ x: 0, y: 0 }); + point.moveVertical(10); + expect(point.y).toBe(10); + point.moveVertical(-5); + expect(point.y).toBe(5); + }); + + test('moveHorizontal should correctly update x value', () => { + const point = new Point({ x: 0, y: 0 }); + point.moveHorizontal(15); + expect(point.x).toBe(15); + point.moveHorizontal(-10); + expect(point.x).toBe(5); + }); +}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 5bc5ea8..095d84d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,9 @@ "license": "ISC", "devDependencies": { "@types/jest": "^30.0.0", + "create-jest": "^30.0.4", "jest": "^30.0.4", + "ts-jest": "^29.4.0", "ts-node": "^10.9.2", "typescript": "^5.8.3" } @@ -1662,6 +1664,13 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, "node_modules/babel-jest": { "version": "30.0.4", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.0.4.tgz", @@ -1823,6 +1832,19 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -2061,6 +2083,28 @@ "dev": true, "license": "MIT" }, + "node_modules/create-jest": { + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-30.0.4.tgz", + "integrity": "sha512-5uYiU+C+s3Wt1I7dfMd6OKAIrVh/WbhyK0BHIT2/2aiUnEvDaLwz7PkTt5qReZ3yiH5n2pa01dfiUqd1dZErMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.0.1", + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-config": "30.0.4", + "jest-util": "30.0.2", + "prompts": "^2.4.2" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -2153,6 +2197,22 @@ "dev": true, "license": "MIT" }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.181", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.181.tgz", @@ -2300,6 +2360,29 @@ "bser": "2.1.1" } }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -2681,6 +2764,49 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jake/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/jake/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/jest": { "version": "30.0.4", "resolved": "https://registry.npmjs.org/jest/-/jest-30.0.4.tgz", @@ -3341,6 +3467,16 @@ "node": ">=6" } }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -3371,6 +3507,13 @@ "node": ">=8" } }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -3783,6 +3926,20 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/pure-rand": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", @@ -3886,6 +4043,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -4221,6 +4385,85 @@ "node": ">=8.0" } }, + "node_modules/ts-jest": { + "version": "29.4.0", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.0.tgz", + "integrity": "sha512-d423TJMnJGu80/eSgfQ5w/R+0zFJvdtTxwtF9KzFFunOpSeD+79lHJQIiAhluJoyGRbvj9NZJsl9WjCUo0ND7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "ejs": "^3.1.10", + "fast-json-stable-stringify": "^2.1.0", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.2", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", diff --git a/package.json b/package.json index f9371ee..6f4ac78 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "", "main": "index.js", "scripts": { - "test": "jest" + "test": "jest", + "test:coverage": "jest --coverage" }, "repository": { "type": "git", @@ -21,7 +22,9 @@ "homepage": "https://github.com/gemtechd/build-tests-ts#readme", "devDependencies": { "@types/jest": "^30.0.0", + "create-jest": "^30.0.4", "jest": "^30.0.4", + "ts-jest": "^29.4.0", "ts-node": "^10.9.2", "typescript": "^5.8.3" }