diff --git a/eslint.config.mjs b/eslint.config.mjs index ca765a0..dcb3fad 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,14 +1,24 @@ // @ts-check -import eslint from '@eslint/js'; -import tseslint from 'typescript-eslint'; +import eslint from '@eslint/js' +import tseslint from 'typescript-eslint' +import jest from 'eslint-plugin-jest' export default tseslint.config( eslint.configs.recommended, ...tseslint.configs.recommended, + { + files: ['test/**'], + ...jest.configs['flat/recommended'] + }, { ignores: [ 'lib/' ] + }, + { + rules: { + 'semi': ['error', 'never'] + } } -); +) diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..ad6c371 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,8 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} **/ +// eslint-disable-next-line no-undef +module.exports = { + testEnvironment: "node", + transform: { + "^.+.tsx?$": ["ts-jest", {}], + }, +} diff --git a/package.json b/package.json index 93e122d..9265bc3 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "build-demo": "pnpm --prefix demo build", "deploy-demo": "pnpm build-demo && gh-pages -d demo/build", "lint": "eslint", - "test": "jest --passWithNoTests" + "test": "jest" }, "keywords": [ "body", @@ -43,11 +43,14 @@ "@eslint/js": "^9.9.0", "@rollup/plugin-typescript": "^11.1.6", "@types/eslint__js": "^8.42.3", + "@types/jest": "^29.5.12", "@types/node": "^22.5.0", "eslint": "^9.9.0", + "eslint-plugin-jest": "^28.8.0", "gh-pages": "^6.1.1", "jest": "^29.7.0", "rollup": "^4.21.0", + "ts-jest": "^29.2.4", "ts-node": "^10.9.2", "tslib": "^2.6.3", "typescript": "^5.5.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7035b58..3fb4fbb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,12 +17,18 @@ importers: '@types/eslint__js': specifier: ^8.42.3 version: 8.42.3 + '@types/jest': + specifier: ^29.5.12 + version: 29.5.12 '@types/node': specifier: ^22.5.0 version: 22.5.0 eslint: specifier: ^9.9.0 version: 9.9.0 + eslint-plugin-jest: + specifier: ^28.8.0 + version: 28.8.0(@typescript-eslint/eslint-plugin@8.2.0(@typescript-eslint/parser@8.2.0(eslint@9.9.0)(typescript@5.5.4))(eslint@9.9.0)(typescript@5.5.4))(eslint@9.9.0)(jest@29.7.0(@types/node@22.5.0)(ts-node@10.9.2(@types/node@22.5.0)(typescript@5.5.4)))(typescript@5.5.4) gh-pages: specifier: ^6.1.1 version: 6.1.1 @@ -32,6 +38,9 @@ importers: rollup: specifier: ^4.21.0 version: 4.21.0 + ts-jest: + specifier: ^29.2.4 + version: 29.2.4(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@22.5.0)(ts-node@10.9.2(@types/node@22.5.0)(typescript@5.5.4)))(typescript@5.5.4) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@22.5.0)(typescript@5.5.4) @@ -527,6 +536,9 @@ packages: '@types/istanbul-reports@3.0.4': resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + '@types/jest@29.5.12': + resolution: {integrity: sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -768,6 +780,10 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + bs-logger@0.2.6: + resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} + engines: {node: '>= 6'} + bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} @@ -904,6 +920,11 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} + ejs@3.1.10: + resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} + engines: {node: '>=0.10.0'} + hasBin: true + electron-to-chromium@1.5.13: resolution: {integrity: sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==} @@ -943,6 +964,19 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + eslint-plugin-jest@28.8.0: + resolution: {integrity: sha512-Tubj1hooFxCl52G4qQu0edzV/+EZzPUeN8p2NnW5uu4fbDs+Yo7+qDVDc4/oG3FbCqEBmu/OC3LSsyiU22oghw==} + engines: {node: ^16.10.0 || ^18.12.0 || >=20.0.0} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^6.0.0 || ^7.0.0 || ^8.0.0 + eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 + jest: '*' + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + jest: + optional: true + eslint-scope@5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} @@ -1040,6 +1074,9 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} + filelist@1.0.4: + resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} + filename-reserved-regex@2.0.0: resolution: {integrity: sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==} engines: {node: '>=4'} @@ -1249,6 +1286,11 @@ packages: resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} engines: {node: '>=8'} + jake@10.9.2: + resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==} + engines: {node: '>=10'} + hasBin: true + jest-changed-files@29.7.0: resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -1448,6 +1490,9 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -1494,6 +1539,10 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -1825,6 +1874,30 @@ packages: peerDependencies: typescript: '>=4.2.0' + ts-jest@29.2.4: + resolution: {integrity: sha512-3d6tgDyhCI29HlpwIq87sNuI+3Q6GLTTCeYRHCs7vDz+/3GCMwEtV9jezLyl4ZtnBgx00I7hm8PCP8cTksMGrw==} + engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/transform': ^29.0.0 + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.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 + ts-node@10.9.2: resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} hasBin: true @@ -2544,6 +2617,11 @@ snapshots: dependencies: '@types/istanbul-lib-report': 3.0.3 + '@types/jest@29.5.12': + dependencies: + expect: 29.7.0 + pretty-format: 29.7.0 + '@types/json-schema@7.0.15': {} '@types/node@22.5.0': @@ -2860,6 +2938,10 @@ snapshots: node-releases: 2.0.18 update-browserslist-db: 1.1.0(browserslist@4.23.3) + bs-logger@0.2.6: + dependencies: + fast-json-stable-stringify: 2.1.0 + bser@2.1.1: dependencies: node-int64: 0.4.0 @@ -2968,6 +3050,10 @@ snapshots: dependencies: path-type: 4.0.0 + ejs@3.1.10: + dependencies: + jake: 10.9.2 + electron-to-chromium@1.5.13: {} email-addresses@5.0.0: {} @@ -2995,6 +3081,17 @@ snapshots: escape-string-regexp@4.0.0: {} + eslint-plugin-jest@28.8.0(@typescript-eslint/eslint-plugin@8.2.0(@typescript-eslint/parser@8.2.0(eslint@9.9.0)(typescript@5.5.4))(eslint@9.9.0)(typescript@5.5.4))(eslint@9.9.0)(jest@29.7.0(@types/node@22.5.0)(ts-node@10.9.2(@types/node@22.5.0)(typescript@5.5.4)))(typescript@5.5.4): + dependencies: + '@typescript-eslint/utils': 8.2.0(eslint@9.9.0)(typescript@5.5.4) + eslint: 9.9.0 + optionalDependencies: + '@typescript-eslint/eslint-plugin': 8.2.0(@typescript-eslint/parser@8.2.0(eslint@9.9.0)(typescript@5.5.4))(eslint@9.9.0)(typescript@5.5.4) + jest: 29.7.0(@types/node@22.5.0)(ts-node@10.9.2(@types/node@22.5.0)(typescript@5.5.4)) + transitivePeerDependencies: + - supports-color + - typescript + eslint-scope@5.1.1: dependencies: esrecurse: 4.3.0 @@ -3122,6 +3219,10 @@ snapshots: dependencies: flat-cache: 4.0.1 + filelist@1.0.4: + dependencies: + minimatch: 5.1.6 + filename-reserved-regex@2.0.0: {} filenamify@4.3.0: @@ -3330,6 +3431,13 @@ snapshots: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 + jake@10.9.2: + dependencies: + async: 3.2.6 + chalk: 4.1.2 + filelist: 1.0.4 + minimatch: 3.1.2 + jest-changed-files@29.7.0: dependencies: execa: 5.1.1 @@ -3699,6 +3807,8 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash.memoize@4.1.2: {} + lodash.merge@4.6.2: {} lru-cache@5.1.1: @@ -3740,6 +3850,10 @@ snapshots: dependencies: brace-expansion: 1.1.11 + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.1 + minimatch@9.0.5: dependencies: brace-expansion: 2.0.1 @@ -4034,6 +4148,25 @@ snapshots: dependencies: typescript: 5.5.4 + ts-jest@29.2.4(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@22.5.0)(ts-node@10.9.2(@types/node@22.5.0)(typescript@5.5.4)))(typescript@5.5.4): + dependencies: + bs-logger: 0.2.6 + ejs: 3.1.10 + fast-json-stable-stringify: 2.1.0 + jest: 29.7.0(@types/node@22.5.0)(ts-node@10.9.2(@types/node@22.5.0)(typescript@5.5.4)) + jest-util: 29.7.0 + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.6.3 + typescript: 5.5.4 + yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.25.2 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.25.2) + ts-node@10.9.2(@types/node@22.5.0)(typescript@5.5.4): dependencies: '@cspotcode/source-map-support': 0.8.1 diff --git a/rollup.config.mjs b/rollup.config.mjs index 1399575..c57f5aa 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -1,7 +1,7 @@ import fs from 'fs' import typescript from '@rollup/plugin-typescript' -const packageJson = JSON.parse(fs.readFileSync('./package.json')); +const packageJson = JSON.parse(fs.readFileSync('./package.json')) export default { input: 'src/index.ts', diff --git a/src/Body.spec.ts b/src/Body.spec.ts new file mode 100644 index 0000000..03e3aa1 --- /dev/null +++ b/src/Body.spec.ts @@ -0,0 +1,17 @@ +import Body from "./Body" +import Vector from "./Vector" + +describe('Body', () => { + describe('moveBodyThroughTime', () => { + it('should move body through delta time', () => { + const body = new Body({ + position: new Vector(0, 0, 0), + velocity: new Vector(1, 2, -3) + }) + + body.moveBodyThroughTime(1) + + expect(body.velocity).toEqual(new Vector(1, 2, -3)) + }) + }) +}) diff --git a/src/Body.ts b/src/Body.ts index 1ae3391..2961e86 100644 --- a/src/Body.ts +++ b/src/Body.ts @@ -1,4 +1,4 @@ -import Vector from './Vector'; +import Vector from './Vector' export default class Body { mass: number @@ -37,7 +37,7 @@ export default class Body { } setExternalForces(externalForces: Vector[]) { - this.externalForces = externalForces; + this.externalForces = externalForces this.netExternalForce = this.externalForces.reduce((total, force) => { total.x += force.x @@ -59,7 +59,7 @@ export default class Body { const deltaPosition = this.velocity.scaleBy(deltaTime) this.position = this.position.sum(deltaPosition) if (this.pastPositions.length >= this.tailLength) { - this.pastPositions.pop(); + this.pastPositions.pop() } this.pastPositions.unshift(this.position) } diff --git a/src/Simulation.ts b/src/Simulation.ts index 246c812..dc8f810 100644 --- a/src/Simulation.ts +++ b/src/Simulation.ts @@ -10,7 +10,7 @@ export default class Simulation { renderInterval: number universe: Universe - eventEmitter: EventEmitter; + eventEmitter: EventEmitter simulationInterval: NodeJS.Timeout constructor({ @@ -31,7 +31,7 @@ export default class Simulation { deltaTime?: number renderInterval?: number }) { - this.bodies = bodies; + this.bodies = bodies this.gravity = gravity ?? 6.674e-11 this.collisions = collisions ?? false this.deltaTime = deltaTime ?? 0.5 @@ -48,6 +48,6 @@ export default class Simulation { this.simulationInterval = setInterval(() => { this.universe.moveBodiesThroughTime() this.eventEmitter.emit('deltaTime') - }, renderInterval); + }, renderInterval) } } diff --git a/src/StableUniverses.ts b/src/StableUniverses.ts index b8edcd0..4266690 100644 --- a/src/StableUniverses.ts +++ b/src/StableUniverses.ts @@ -1,8 +1,8 @@ import Body from './Body' -import Universe from './Universe'; -import Vector from './Vector'; +import Universe from './Universe' +import Vector from './Vector' -const equalChaseCircularSpeed = 0.008125; +const equalChaseCircularSpeed = 0.008125 export const EqualChaseCircular = new Universe({ bodies: [ new Body({ @@ -24,9 +24,9 @@ export const EqualChaseCircular = new Universe({ color: '#008ebd' }) ] -}); +}) -const equalChaseInOutSpeed = 0.006; +const equalChaseInOutSpeed = 0.006 export const EqualChaseInOut = new Universe({ bodies: [ new Body({ @@ -48,10 +48,10 @@ export const EqualChaseInOut = new Universe({ color: '#008ebd' }) ] -}); +}) -const getRandom = () => -1 + Math.random() * 2; -const maxVelocityForRandom = 0.02; +const getRandom = () => -1 + Math.random() * 2 +const maxVelocityForRandom = 0.02 export const Random = new Universe({ bodies: [ new Body({ @@ -76,7 +76,7 @@ export const Random = new Universe({ color: '#777' }) ] -}); +}) export const FigureEight = new Universe({ bodies: [ @@ -100,4 +100,4 @@ export const FigureEight = new Universe({ }) ], gravity: 1, collisions: false, deltaTime: 0.005 -}); +}) diff --git a/src/Vector.ts b/src/Vector.ts index cb05c15..02c0054 100644 --- a/src/Vector.ts +++ b/src/Vector.ts @@ -70,7 +70,7 @@ export default class Vector { this.x * factor, this.y * factor, this.z * factor - ); + ) } scaleTo(magnitude: number) {