diff --git a/teammapper-backend/package-lock.json b/teammapper-backend/package-lock.json index 2b29950d..48384560 100644 --- a/teammapper-backend/package-lock.json +++ b/teammapper-backend/package-lock.json @@ -19,13 +19,13 @@ "@nestjs/schedule": "^4.1.1", "@nestjs/serve-static": "^4.0.2", "@nestjs/typeorm": "^10.0.2", - "@nestjs/websockets": "^10.4.1", + "@nestjs/websockets": "^10.4.12", "@types/uuid": "^10.0.0", "cache-manager": "^5.7.4", "class-validator": "^0.14.1", "dotenv": "16.4.5", "http-proxy-middleware": "3.0.3", - "npm-check-updates": "^17.1.10", + "npm-check-updates": "^17.1.11", "pg": "^8.13.1", "pq": "^0.0.3", "reflect-metadata": "^0.2.2", @@ -46,10 +46,10 @@ "@types/express-serve-static-core": "^4.19.5", "@types/jest": "29.5.14", "@types/node": "^22.8.7", - "@types/supertest": "^2.0.16", - "@typescript-eslint/eslint-plugin": "^8.13.0", + "@types/supertest": "^6.0.2", + "@typescript-eslint/eslint-plugin": "^8.16.0", "@typescript-eslint/parser": "^8.2.0", - "concurrently": "9.0.1", + "concurrently": "9.1.0", "eslint": "^8.55.0", "eslint-config-prettier": "^9.1.0", "eslint-import-resolver-typescript": "3.6.1", @@ -67,7 +67,7 @@ "ts-loader": "^9.5.1", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", - "typescript": "~5.5.4" + "typescript": "~5.7.2" }, "engines": { "node": "~22.2", @@ -1605,13 +1605,13 @@ } }, "node_modules/@nestjs/common": { - "version": "10.4.11", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.4.11.tgz", - "integrity": "sha512-7MnKYckMsT/LGlwC0PCY8XZFWD84bxVwsPsn9H2RrLSbCCwUvcRb39ZkQt4uR+zKj2fq4TsV80+5DfjMwFvVdg==", + "version": "10.4.12", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.4.12.tgz", + "integrity": "sha512-+aQw1d1cV9MtjSSCoXA2iZf+EBJANyjTC9d0j38bUAHhaPQinRlgM81F91DFYZoYcYVOY4hJd+DDYRFGoN7j7Q==", "license": "MIT", "dependencies": { "iterare": "1.2.1", - "tslib": "2.7.0", + "tslib": "2.8.1", "uid": "2.0.2" }, "funding": { @@ -1633,6 +1633,12 @@ } } }, + "node_modules/@nestjs/common/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/@nestjs/config": { "version": "3.2.3", "license": "MIT", @@ -1717,13 +1723,13 @@ } }, "node_modules/@nestjs/platform-socket.io": { - "version": "10.4.11", - "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.4.11.tgz", - "integrity": "sha512-71QOyhOv52D9XodLrcPbmLKsPfF7h+bVPk1fD5bdc8KxDZb/+0W3aWlUUUhOazRYAl2yJAOmpk+t7jE7W3wFYg==", + "version": "10.4.12", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.4.12.tgz", + "integrity": "sha512-5gtMgxyEuVppDRGqxPI05F8RTrYKBBfJmqOzogmrD4kGCKkc7POohvwiR2AsIVxXHRbcShtRGOCcs5cvkOOhlA==", "license": "MIT", "dependencies": { - "socket.io": "4.8.0", - "tslib": "2.7.0" + "socket.io": "4.8.1", + "tslib": "2.8.1" }, "funding": { "type": "opencollective", @@ -1735,6 +1741,30 @@ "rxjs": "^7.1.0" } }, + "node_modules/@nestjs/platform-socket.io/node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/@nestjs/platform-socket.io/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/@nestjs/schedule": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-4.1.1.tgz", @@ -1875,13 +1905,13 @@ } }, "node_modules/@nestjs/testing": { - "version": "10.4.11", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.4.11.tgz", - "integrity": "sha512-jpZwVrPfOvjZxdYMHasHko38XD+yuGAGJBM3qFQlZRQkZ+mEa6AuXUcMXihXWehHw7DXTeYi7u2hA+HN/wBBbw==", + "version": "10.4.12", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.4.12.tgz", + "integrity": "sha512-Q1ZpzmIqzem6Q9pBwT1qQRYE050HhvSh8U7AbdIavOHCI063GbXOy/erXEpUnE0o46mqT+y88Nn9NjTXAQt4nQ==", "dev": true, "license": "MIT", "dependencies": { - "tslib": "2.7.0" + "tslib": "2.8.1" }, "funding": { "type": "opencollective", @@ -1902,6 +1932,13 @@ } } }, + "node_modules/@nestjs/testing/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, "node_modules/@nestjs/typeorm": { "version": "10.0.2", "license": "MIT", @@ -1928,12 +1965,14 @@ } }, "node_modules/@nestjs/websockets": { - "version": "10.4.1", + "version": "10.4.12", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.4.12.tgz", + "integrity": "sha512-BPu8VgA/R0DPu87VGmGg1U6BGZaIy9vlNqaoeNUyMbyToG+cTL4WvFi3s9QBO/I88QnTlfzC/ez/oAGaJjygEQ==", "license": "MIT", "dependencies": { "iterare": "1.2.1", "object-hash": "3.0.0", - "tslib": "2.6.3" + "tslib": "2.8.1" }, "peerDependencies": { "@nestjs/common": "^10.0.0", @@ -1949,7 +1988,9 @@ } }, "node_modules/@nestjs/websockets/node_modules/tslib": { - "version": "2.6.3", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, "node_modules/@nodelib/fs.scandir": { @@ -2234,6 +2275,8 @@ }, "node_modules/@types/cookiejar": { "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", "dev": true, "license": "MIT" }, @@ -2357,6 +2400,13 @@ "version": "3.4.2", "license": "MIT" }, + "node_modules/@types/methods": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", + "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mime": { "version": "1.3.5", "dev": true, @@ -2406,20 +2456,27 @@ "license": "MIT" }, "node_modules/@types/superagent": { - "version": "4.1.24", + "version": "8.1.9", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz", + "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/cookiejar": "*", - "@types/node": "*" + "@types/cookiejar": "^2.1.5", + "@types/methods": "^1.1.4", + "@types/node": "*", + "form-data": "^4.0.0" } }, "node_modules/@types/supertest": { - "version": "2.0.16", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.2.tgz", + "integrity": "sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==", "dev": true, "license": "MIT", "dependencies": { - "@types/superagent": "*" + "@types/methods": "^1.1.4", + "@types/superagent": "^8.1.0" } }, "node_modules/@types/uuid": { @@ -2446,17 +2503,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.16.0.tgz", - "integrity": "sha512-5YTHKV8MYlyMI6BaEG7crQ9BhSc8RxzshOReKwZwRWN0+XvvTOm+L/UYLCYxFpfwYuAAqhxiq4yae0CMFwbL7Q==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.17.0.tgz", + "integrity": "sha512-HU1KAdW3Tt8zQkdvNoIijfWDMvdSweFYm4hWh+KwhPstv+sCmWb89hCIP8msFm9N1R/ooh9honpSuvqKWlYy3w==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.16.0", - "@typescript-eslint/type-utils": "8.16.0", - "@typescript-eslint/utils": "8.16.0", - "@typescript-eslint/visitor-keys": "8.16.0", + "@typescript-eslint/scope-manager": "8.17.0", + "@typescript-eslint/type-utils": "8.17.0", + "@typescript-eslint/utils": "8.17.0", + "@typescript-eslint/visitor-keys": "8.17.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -2480,14 +2537,14 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.16.0.tgz", - "integrity": "sha512-mwsZWubQvBki2t5565uxF0EYvG+FwdFb8bMtDuGQLdCCnGPrDEDvm1gtfynuKlnpzeBRqdFCkMf9jg1fnAK8sg==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz", + "integrity": "sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.16.0", - "@typescript-eslint/visitor-keys": "8.16.0" + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/visitor-keys": "8.17.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2498,9 +2555,9 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.16.0.tgz", - "integrity": "sha512-NzrHj6thBAOSE4d9bsuRNMvk+BvaQvmY4dDglgkgGC0EW/tB3Kelnp3tAKH87GEwzoxgeQn9fNGRyFJM/xd+GQ==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.17.0.tgz", + "integrity": "sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==", "dev": true, "license": "MIT", "engines": { @@ -2512,13 +2569,13 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.16.0.tgz", - "integrity": "sha512-pq19gbaMOmFE3CbL0ZB8J8BFCo2ckfHBfaIsaOZgBIF4EoISJIdLX5xRhd0FGB0LlHReNRuzoJoMGpTjq8F2CQ==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz", + "integrity": "sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/types": "8.17.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -2586,14 +2643,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.16.0.tgz", - "integrity": "sha512-IqZHGG+g1XCWX9NyqnI/0CX5LL8/18awQqmkZSl2ynn8F76j579dByc0jhfVSnSnhf7zv76mKBQv9HQFKvDCgg==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.17.0.tgz", + "integrity": "sha512-q38llWJYPd63rRnJ6wY/ZQqIzPrBCkPdpIsaCfkR3Q4t3p6sb422zougfad4TFW9+ElIFLVDzWGiGAfbb/v2qw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.16.0", - "@typescript-eslint/utils": "8.16.0", + "@typescript-eslint/typescript-estree": "8.17.0", + "@typescript-eslint/utils": "8.17.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -2614,9 +2671,9 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.16.0.tgz", - "integrity": "sha512-NzrHj6thBAOSE4d9bsuRNMvk+BvaQvmY4dDglgkgGC0EW/tB3Kelnp3tAKH87GEwzoxgeQn9fNGRyFJM/xd+GQ==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.17.0.tgz", + "integrity": "sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==", "dev": true, "license": "MIT", "engines": { @@ -2628,14 +2685,14 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.16.0.tgz", - "integrity": "sha512-E2+9IzzXMc1iaBy9zmo+UYvluE3TW7bCGWSF41hVWUE01o8nzr1rvOQYSxelxr6StUvRcTMe633eY8mXASMaNw==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz", + "integrity": "sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.16.0", - "@typescript-eslint/visitor-keys": "8.16.0", + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/visitor-keys": "8.17.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2657,13 +2714,13 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.16.0.tgz", - "integrity": "sha512-pq19gbaMOmFE3CbL0ZB8J8BFCo2ckfHBfaIsaOZgBIF4EoISJIdLX5xRhd0FGB0LlHReNRuzoJoMGpTjq8F2CQ==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz", + "integrity": "sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/types": "8.17.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -2775,16 +2832,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.16.0.tgz", - "integrity": "sha512-C1zRy/mOL8Pj157GiX4kaw7iyRLKfJXBR3L82hk5kS/GyHcOFmy4YUq/zfZti72I9wnuQtA/+xzft4wCC8PJdA==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.17.0.tgz", + "integrity": "sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.16.0", - "@typescript-eslint/types": "8.16.0", - "@typescript-eslint/typescript-estree": "8.16.0" + "@typescript-eslint/scope-manager": "8.17.0", + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/typescript-estree": "8.17.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2803,14 +2860,14 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.16.0.tgz", - "integrity": "sha512-mwsZWubQvBki2t5565uxF0EYvG+FwdFb8bMtDuGQLdCCnGPrDEDvm1gtfynuKlnpzeBRqdFCkMf9jg1fnAK8sg==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz", + "integrity": "sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.16.0", - "@typescript-eslint/visitor-keys": "8.16.0" + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/visitor-keys": "8.17.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2821,9 +2878,9 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.16.0.tgz", - "integrity": "sha512-NzrHj6thBAOSE4d9bsuRNMvk+BvaQvmY4dDglgkgGC0EW/tB3Kelnp3tAKH87GEwzoxgeQn9fNGRyFJM/xd+GQ==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.17.0.tgz", + "integrity": "sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==", "dev": true, "license": "MIT", "engines": { @@ -2835,14 +2892,14 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.16.0.tgz", - "integrity": "sha512-E2+9IzzXMc1iaBy9zmo+UYvluE3TW7bCGWSF41hVWUE01o8nzr1rvOQYSxelxr6StUvRcTMe633eY8mXASMaNw==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz", + "integrity": "sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.16.0", - "@typescript-eslint/visitor-keys": "8.16.0", + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/visitor-keys": "8.17.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2864,13 +2921,13 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.16.0.tgz", - "integrity": "sha512-pq19gbaMOmFE3CbL0ZB8J8BFCo2ckfHBfaIsaOZgBIF4EoISJIdLX5xRhd0FGB0LlHReNRuzoJoMGpTjq8F2CQ==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz", + "integrity": "sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/types": "8.17.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -4008,7 +4065,9 @@ } }, "node_modules/concurrently": { - "version": "9.0.1", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.1.0.tgz", + "integrity": "sha512-VxkzwMAn4LP7WyMnJNbHN5mKV9L2IbyDjpzemKr99sXNR3GqRNMMHdm7prV1ws9wg7ETj6WUkNOigZVsptwbgg==", "dev": true, "license": "MIT", "dependencies": { @@ -7236,9 +7295,9 @@ } }, "node_modules/libphonenumber-js": { - "version": "1.11.15", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.15.tgz", - "integrity": "sha512-M7+rtYi9l5RvMmHyjyoF3BHHUpXTYdJ0PezZGHNs0GyW1lO+K7jxlXpbdIb7a56h0nqLYdjIw+E+z0ciGaJP7g==", + "version": "1.11.16", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.16.tgz", + "integrity": "sha512-Noyazmt0yOvnG0OeRY45Cd1ur8G7Z0HWVkuCuKe+yysGNxPQwBAODBQQ40j0AIagi9ZWurfmmZWNlpg4h4W+XQ==", "license": "MIT" }, "node_modules/lines-and-columns": { @@ -9843,7 +9902,9 @@ } }, "node_modules/typescript": { - "version": "5.5.4", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/teammapper-backend/package.json b/teammapper-backend/package.json index b9302c6c..604b4ce3 100644 --- a/teammapper-backend/package.json +++ b/teammapper-backend/package.json @@ -43,13 +43,13 @@ "@nestjs/schedule": "^4.1.1", "@nestjs/serve-static": "^4.0.2", "@nestjs/typeorm": "^10.0.2", - "@nestjs/websockets": "^10.4.1", + "@nestjs/websockets": "^10.4.12", "@types/uuid": "^10.0.0", "cache-manager": "^5.7.4", "class-validator": "^0.14.1", "dotenv": "16.4.5", "http-proxy-middleware": "3.0.3", - "npm-check-updates": "^17.1.10", + "npm-check-updates": "^17.1.11", "pg": "^8.13.1", "pq": "^0.0.3", "reflect-metadata": "^0.2.2", @@ -70,10 +70,10 @@ "@types/express-serve-static-core": "^4.19.5", "@types/jest": "29.5.14", "@types/node": "^22.8.7", - "@types/supertest": "^2.0.16", - "@typescript-eslint/eslint-plugin": "^8.13.0", + "@types/supertest": "^6.0.2", + "@typescript-eslint/eslint-plugin": "^8.16.0", "@typescript-eslint/parser": "^8.2.0", - "concurrently": "9.0.1", + "concurrently": "9.1.0", "eslint": "^8.55.0", "eslint-config-prettier": "^9.1.0", "eslint-import-resolver-typescript": "3.6.1", @@ -91,7 +91,7 @@ "ts-loader": "^9.5.1", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", - "typescript": "~5.5.4" + "typescript": "~5.7.2" }, "overrides": { "path-to-regexp": "1.9.0" diff --git a/teammapper-frontend/mmp/src/map/handlers/nodes.ts b/teammapper-frontend/mmp/src/map/handlers/nodes.ts index 40fa118c..6504ad08 100644 --- a/teammapper-frontend/mmp/src/map/handlers/nodes.ts +++ b/teammapper-frontend/mmp/src/map/handlers/nodes.ts @@ -15,7 +15,11 @@ import { v4 as uuidv4 } from 'uuid' import { Event } from './events' import Log from '../../utils/log' import Utils from '../../utils/utils' +import { MapSnapshot } from './history' +const NODE_HORIZONTAL_SPACING = 200; // The x-axis spacing between parent and child nodes +const NODE_VERTICAL_SIBLING_OFFSET = 60; // The y-axis spacing between sibling nodes +const NODE_VERTICAL_SPACING = 120; // The initial vertical spacing for the first child node /** * Manage the nodes of the map. */ @@ -525,10 +529,22 @@ export default class Nodes { * Return the orientation of a node in the map (true if left). * @return {boolean} */ - public getOrientation(node: Node): boolean { - if (!node.isRoot) { - return node.coordinates.x < this.getRoot().coordinates.x + public getOrientation( + node: Node | ExportNodeProperties, + rootNode?: Node | ExportNodeProperties + ): boolean | undefined { + // isRoot exists only on Node type, so type guard is needed + if ('isRoot' in node && node.isRoot) { + return; + } + + // For Node type, use this.getRoot() if rootNode not provided + const root = rootNode ?? (('isRoot' in node) ? this.getRoot() : undefined); + if (!root) { + return; } + + return (node.coordinates?.x ?? 0) < (root.coordinates?.x ?? 0); } /** @@ -661,48 +677,113 @@ export default class Nodes { } /** - * Return the appropriate coordinates of the node. - * @param {Node} node - * @returns {Coordinates} coordinates + * Base method for calculating node coordinates. + * The reason this exists is so we can work with a JSON snapshot (as given by an import), but also allow saved, "real" nodes to calculate coordinates + * This prevents duplication, whilst passing methods that differ depending on whether or not a JSON snapshot or "real" node is calculating coordinates. + * @param node Either a node previously saved or one from a JSON snapshot + * @param params + * getParent - Parent node of given node + * getSiblings() - Method to get the siblings of the given node + * isRoot - If parent node is root + * getOrientation() - Method to get the orientation of the node + * @returns */ - private calculateCoordinates(node: Node): Coordinates { + private calculateNodeCoordinates( + node: Node | ExportNodeProperties, + params: { + nodeParent: (Node | ExportNodeProperties) | null, + getSiblings: () => (Node | ExportNodeProperties)[], + isRoot: boolean, + getOrientation: (n: Node | ExportNodeProperties) => boolean | undefined + } + ): Coordinates { + const nodeParent = params.nodeParent; + let coordinates: Coordinates = { - x: node.parent ? node.parent.coordinates.x : node.coordinates.x || 0, - y: node.parent ? node.parent.coordinates.y : node.coordinates.y || 0 - }, - siblings: Array = this.getSiblings(node) - - if (node.parent && node.parent.isRoot) { - const rightNodes: Array = [], - leftNodes: Array = [] - - for (const sibling of siblings) { - this.getOrientation(sibling) ? leftNodes.push(sibling) : rightNodes.push(sibling) - } - + x: nodeParent?.coordinates?.x ?? (node.coordinates?.x ?? 0), + y: nodeParent?.coordinates?.y ?? (node.coordinates?.y ?? 0) + }; + + let siblings = params.getSiblings(); + + if (nodeParent && params.isRoot) { + // This will go through sibling nodes and assign them to the left or to the right depending on the orientation of the sibling node + const [leftNodes, rightNodes] = siblings.reduce<[(Node | ExportNodeProperties)[], (Node | ExportNodeProperties)[]]>( + (acc, sibling) => { + params.getOrientation(sibling) + ? acc[0].push(sibling) + : acc[1].push(sibling); + return acc; + }, + [[], []] + ); + if (leftNodes.length <= rightNodes.length) { - coordinates.x -= 200 - siblings = leftNodes + coordinates.x -= NODE_HORIZONTAL_SPACING; + siblings = leftNodes; } else { - coordinates.x += 200 - siblings = rightNodes + coordinates.x += NODE_HORIZONTAL_SPACING; + siblings = rightNodes; } } else if (!node.detached) { - if (node.parent && this.getOrientation(node.parent)) { - coordinates.x -= 200 + if (nodeParent && params.getOrientation(nodeParent)) { + coordinates.x -= NODE_HORIZONTAL_SPACING; } else { - coordinates.x += 200 + coordinates.x += NODE_HORIZONTAL_SPACING; } } - + if (siblings.length > 0) { const lowerNode = this.getLowerNode(siblings) - coordinates.y = lowerNode.coordinates.y + 60 + coordinates.y = (lowerNode?.coordinates?.y ?? 0) + NODE_VERTICAL_SIBLING_OFFSET; } else if (!node.detached) { - coordinates.y -= 120 + coordinates.y -= NODE_VERTICAL_SPACING; } + + return coordinates; + } - return coordinates + /** + * Existing method to calculate the coordinates of "real", saved nodes in the database. + * This method will pass on existing methods such as this.getSiblings() to calculateNodeCoordinates, so existing implementations don't break + * @param node + * @returns + */ + private calculateCoordinates(node: Node): Coordinates { + return this.calculateNodeCoordinates( + node, + { + nodeParent: node.parent, + getSiblings: () => this.getSiblings(node), + isRoot: node.parent?.isRoot ?? false, + getOrientation: (n: Node) => this.getOrientation(n) + } + ); + } + + public applyCoordinatesToMapSnapshot = (mapSnapshot: MapSnapshot): MapSnapshot => { + const rootNode = mapSnapshot.find(x => x.isRoot); + + return mapSnapshot.map(node => { + if (!node.coordinates) { + /** + * Since we're working with a JSON snapshot here, none of the nodes actually exist. + * This makes existing methods such as this.getSiblings() useless, because they only work with existing nodes. + * So here, we pass on methods that work directly with the JSON. + */ + node.coordinates = this.calculateNodeCoordinates( + node, + { + nodeParent: node.parent ? mapSnapshot.find(x => x.id === node.parent) : null, + getSiblings: () => node.parent ? mapSnapshot.filter(x => x.parent === node.parent && x.id !== node.id) : [], + isRoot: !!node.parent && mapSnapshot.find(x => x.id === node.parent)?.isRoot, + getOrientation: (n: ExportNodeProperties) => this.getOrientation(n, rootNode) + } + ); + } + + return node + }); } /** @@ -710,19 +791,17 @@ export default class Nodes { * @param {Node[]} nodes * @returns {Node} lowerNode */ - private getLowerNode(nodes: Node[] = Array.from(this.nodes.values())): Node { - if (nodes.length > 0) { - let tmp = nodes[0].coordinates.y, lowerNode = nodes[0] - - for (const node of nodes) { - if (node.coordinates.y > tmp) { - tmp = node.coordinates.y - lowerNode = node - } - } - - return lowerNode + private getLowerNode(nodes: (Node | ExportNodeProperties)[]): Node | ExportNodeProperties | undefined { + if (nodes.length === 0) { + return; } + + return nodes.reduce((lowest, current) => { + const lowestY = lowest.coordinates?.y ?? 0; + const currentY = current.coordinates?.y ?? 0; + + return currentY > lowestY ? current : lowest; + }, nodes[0]); } /** @@ -1081,8 +1160,8 @@ export default class Nodes { * @param {boolean} direction */ private moveSelectionOnBranch(direction: boolean) { - if ((this.getOrientation(this.selectedNode) === false && direction) || - (this.getOrientation(this.selectedNode) === true && !direction)) { + if ((!this.getOrientation(this.selectedNode) && direction) || + (this.getOrientation(this.selectedNode) && !direction)) { this.selectNode(this.selectedNode.parent.id) } else { let children = this.getChildren(this.selectedNode) diff --git a/teammapper-frontend/mmp/src/map/map.ts b/teammapper-frontend/mmp/src/map/map.ts index 24113348..e9a133dc 100644 --- a/teammapper-frontend/mmp/src/map/map.ts +++ b/teammapper-frontend/mmp/src/map/map.ts @@ -3,11 +3,12 @@ import Events from './handlers/events' import Zoom from './handlers/zoom' import Draw from './handlers/draw' import Options, {OptionParameters} from './options' -import History from './handlers/history' +import History, { MapSnapshot } from './handlers/history' import Drag from './handlers/drag' import Nodes from './handlers/nodes' import Export from './handlers/export' import CopyPaste from './handlers/copy-paste' +import { UserNodeProperties } from './types' /** * Initialize all handlers and return a mmp object. @@ -91,6 +92,7 @@ export default class MmpMap { center: this.zoom.center, copyNode: this.copyPaste.copy, cutNode: this.copyPaste.cut, + applyCoordinatesToMapSnapshot: this.nodes.applyCoordinatesToMapSnapshot, deselectNode: this.nodes.deselectNode, getSelectedNode: this.nodes.getSelectedNode, editNode: this.nodes.editNode, @@ -129,6 +131,7 @@ export interface MmpInstance { center: Function copyNode: Function cutNode: Function + applyCoordinatesToMapSnapshot: Function deselectNode: Function getSelectedNode: Function editNode: Function diff --git a/teammapper-frontend/package-lock.json b/teammapper-frontend/package-lock.json index d70b7b8a..5136c43f 100644 --- a/teammapper-frontend/package-lock.json +++ b/teammapper-frontend/package-lock.json @@ -25,7 +25,7 @@ "@fortawesome/angular-fontawesome": "^0.15.0", "@fortawesome/fontawesome-svg-core": "^1.2.27", "@fortawesome/free-brands-svg-icons": "^6.6.0", - "@fortawesome/free-solid-svg-icons": "^6.5.2", + "@fortawesome/free-solid-svg-icons": "^6.7.1", "@material-design-icons/font": "^0.13.0", "@ngx-translate/core": "^16.0.3", "@ngx-translate/http-loader": "^7.0.0", @@ -72,7 +72,7 @@ "eslint-plugin-prettier": "^4.2.1", "jest": "^29.7.0", "jest-canvas-mock": "^2.5.2", - "jest-preset-angular": "^14.2.4", + "jest-preset-angular": "^14.4.1", "minimist": "^1.2.5", "prettier": "2.8.7", "ts-node": "~10.9.2", @@ -4519,17 +4519,21 @@ } }, "node_modules/@fortawesome/free-solid-svg-icons": { - "version": "6.6.0", + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.1.tgz", + "integrity": "sha512-BTKc0b0mgjWZ2UDKVgmwaE0qt0cZs6ITcDgjrti5f/ki7aF5zs+N91V6hitGo3TItCFtnKg6cUVGdTmBFICFRg==", "license": "(CC-BY-4.0 AND MIT)", "dependencies": { - "@fortawesome/fontawesome-common-types": "6.6.0" + "@fortawesome/fontawesome-common-types": "6.7.1" }, "engines": { "node": ">=6" } }, "node_modules/@fortawesome/free-solid-svg-icons/node_modules/@fortawesome/fontawesome-common-types": { - "version": "6.6.0", + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.1.tgz", + "integrity": "sha512-gbDz3TwRrIPT3i0cDfujhshnXO9z03IT1UKRIVi/VEjpNHtSBIP2o5XSm+e816FzzCFEzAxPw09Z13n20PaQJQ==", "license": "MIT", "engines": { "node": ">=6" @@ -13755,9 +13759,9 @@ } }, "node_modules/jest-preset-angular": { - "version": "14.4.0", - "resolved": "https://registry.npmjs.org/jest-preset-angular/-/jest-preset-angular-14.4.0.tgz", - "integrity": "sha512-oGQN8ME1IbpqoOlgM/ebokIkXTkE2kUvSdvsO7WPj8HMjYvyg2YGOmn1hyxTttzw1cGo/JEVvAZTdCyMVLrMFQ==", + "version": "14.4.1", + "resolved": "https://registry.npmjs.org/jest-preset-angular/-/jest-preset-angular-14.4.1.tgz", + "integrity": "sha512-6QBP9SN+VVilghc5hjWzJ4ZBrBB4Djl2fO5uyjJhIWEq/r9255fAyDNHfoigdUbx3l4MRVwwyiTMXRsFAZE4XQ==", "dev": true, "license": "MIT", "dependencies": { @@ -13775,7 +13779,6 @@ "esbuild": ">=0.15.13" }, "peerDependencies": { - "@angular-devkit/build-angular": ">=15.0.0 <20.0.0", "@angular/compiler-cli": ">=15.0.0 <20.0.0", "@angular/core": ">=15.0.0 <20.0.0", "@angular/platform-browser-dynamic": ">=15.0.0 <20.0.0", diff --git a/teammapper-frontend/package.json b/teammapper-frontend/package.json index df98c4d4..16f1dca5 100644 --- a/teammapper-frontend/package.json +++ b/teammapper-frontend/package.json @@ -53,7 +53,7 @@ "@fortawesome/angular-fontawesome": "^0.15.0", "@fortawesome/fontawesome-svg-core": "^1.2.27", "@fortawesome/free-brands-svg-icons": "^6.6.0", - "@fortawesome/free-solid-svg-icons": "^6.5.2", + "@fortawesome/free-solid-svg-icons": "^6.7.1", "@material-design-icons/font": "^0.13.0", "@ngx-translate/core": "^16.0.3", "@ngx-translate/http-loader": "^7.0.0", @@ -100,7 +100,7 @@ "eslint-plugin-prettier": "^4.2.1", "jest": "^29.7.0", "jest-canvas-mock": "^2.5.2", - "jest-preset-angular": "^14.2.4", + "jest-preset-angular": "^14.4.1", "minimist": "^1.2.5", "prettier": "2.8.7", "ts-node": "~10.9.2", diff --git a/teammapper-frontend/src/app/core/services/map-sync/map-sync.service.ts b/teammapper-frontend/src/app/core/services/map-sync/map-sync.service.ts index 38678f5a..58804218 100644 --- a/teammapper-frontend/src/app/core/services/map-sync/map-sync.service.ts +++ b/teammapper-frontend/src/app/core/services/map-sync/map-sync.service.ts @@ -466,7 +466,7 @@ export class MapSyncService implements OnDestroy { }; } - return undefined; + return; }; const { added, updated, deleted } = result.diff; diff --git a/teammapper-frontend/src/app/core/services/mmp/__mocks__/mmp.service.ts b/teammapper-frontend/src/app/core/services/mmp/__mocks__/mmp.service.ts index b3983610..1d75c63b 100644 --- a/teammapper-frontend/src/app/core/services/mmp/__mocks__/mmp.service.ts +++ b/teammapper-frontend/src/app/core/services/mmp/__mocks__/mmp.service.ts @@ -1,6 +1,7 @@ export const mockMmpService = { new: jest.fn(), addNodeImage: jest.fn(), + create: jest.fn(), map: { on: jest.fn(), remove: jest.fn(), diff --git a/teammapper-frontend/src/app/core/services/mmp/mmp.service.spec.ts b/teammapper-frontend/src/app/core/services/mmp/mmp.service.spec.ts new file mode 100644 index 00000000..6f826c41 --- /dev/null +++ b/teammapper-frontend/src/app/core/services/mmp/mmp.service.spec.ts @@ -0,0 +1,263 @@ +import { TestBed } from '@angular/core/testing'; +import { MmpService } from './mmp.service'; +import { SettingsService } from '../settings/settings.service'; +import { ToastrService } from 'ngx-toastr'; +import { UtilsService } from '../utils/utils.service'; +import * as mmp from '@mmp/index'; +import { Subject } from 'rxjs'; +import { OptionParameters } from '@mmp/map/types'; + +jest.mock('dompurify', () => ({ + default: { + sanitize: jest.fn().mockImplementation(str => str), + }, +})); + +jest.mock('@mmp/index', () => ({ + create: jest.fn(), + NodePropertyMapping: {}, +})); + +const downloadFileSpy = jest + .spyOn(UtilsService, 'downloadFile') + .mockImplementation(jest.fn()); + +describe('MmpService', () => { + let service: MmpService; + let settingsService: Partial>; + let utilsService: Partial>; + let toastrService: Partial>; + let editModeSubject: Subject; + + const mockMap = { + instance: { + unsubscribeAll: jest.fn(), + remove: jest.fn(), + new: jest.fn(), + zoomIn: jest.fn(), + zoomOut: jest.fn(), + updateOptions: jest.fn(), + exportAsJSON: jest.fn(), + exportAsImage: jest.fn(), + history: jest.fn(), + save: jest.fn(), + center: jest.fn(), + on: jest.fn(), + addNodes: jest.fn(), + addNode: jest.fn(), + selectNode: jest.fn(), + exportRootProperties: jest.fn(), + exportNodeProperties: jest.fn(), + existNode: jest.fn(), + highlightNode: jest.fn(), + editNode: jest.fn(), + getSelectedNode: jest.fn(), + deselectNode: jest.fn(), + updateNode: jest.fn(), + removeNode: jest.fn(), + copyNode: jest.fn(), + cutNode: jest.fn(), + pasteNode: jest.fn(), + toggleBranchVisibility: jest.fn(), + nodeChildren: jest.fn(), + exportSelectedNode: jest.fn(), + undo: jest.fn(), + redo: jest.fn(), + }, + options: { + update: jest.fn(), + }, + }; + + beforeEach(() => { + (mmp.create as jest.Mock).mockReturnValue(mockMap); + editModeSubject = new Subject(); + + settingsService = { + getEditModeObservable: jest.fn().mockReturnValue(editModeSubject), + getCachedSettings: jest.fn().mockReturnValue({ + mapOptions: { + autoBranchColors: false, + }, + }), + getDefaultSettings: jest.fn().mockResolvedValue({ + mapOptions: { + fontMinSize: 12, + fontMaxSize: 24, + fontIncrement: 2, + }, + }), + }; + + utilsService = { + translate: jest.fn().mockResolvedValue('translated-text'), + }; + + toastrService = { + success: jest.fn(), + error: jest.fn(), + }; + + TestBed.configureTestingModule({ + providers: [ + MmpService, + { provide: SettingsService, useValue: settingsService }, + { provide: UtilsService, useValue: utilsService }, + { provide: ToastrService, useValue: toastrService }, + ], + }); + + service = TestBed.inject(MmpService); + }); + + afterEach(() => { + jest.clearAllMocks(); + downloadFileSpy.mockClear(); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + describe('create', () => { + it('should create a new mind map', async () => { + const id = 'test-id'; + const element = document.createElement('div'); + const options: OptionParameters = { drag: true }; + + await service.create(id, element, options); + + expect(mmp.create).toHaveBeenCalledWith(id, element, options); + }); + + it('should initialize additional options with defaults', async () => { + const id = 'test-id'; + const element = document.createElement('div'); + + await service.create(id, element); + + const additionalOptions = service.getAdditionalMapOptions(); + expect(additionalOptions).toEqual({ + fontMinSize: 12, + fontMaxSize: 24, + fontIncrement: 2, + }); + }); + }); + + describe('remove', () => { + it('should remove the current map', async () => { + await service.create('test-id', document.createElement('div')); + service.remove(); + + expect(mockMap.instance.unsubscribeAll).toHaveBeenCalled(); + expect(mockMap.instance.remove).toHaveBeenCalled(); + }); + + it('should do nothing if no map exists', () => { + service.remove(); + expect(mockMap.instance.unsubscribeAll).not.toHaveBeenCalled(); + }); + }); + + describe('node operations', () => { + beforeEach(async () => { + await service.create('test-id', document.createElement('div')); + }); + + describe('addNode', () => { + it('should add a node with default properties', () => { + service.addNode(); + expect(mockMap.instance.addNode).toHaveBeenCalledWith( + { name: '' }, + true, + true, + undefined, + undefined + ); + }); + + it('should add a node with custom properties', () => { + const props = { name: 'Test Node', id: '123' }; + service.addNode(props); + expect(mockMap.instance.addNode).toHaveBeenCalledWith( + props, + true, + true, + undefined, + '123' + ); + }); + + it('should not add node if selected node is detached', () => { + mockMap.instance.selectNode.mockReturnValue({ detached: true }); + service.addNode(); + expect(mockMap.instance.addNode).not.toHaveBeenCalled(); + }); + }); + + describe('selectNode', () => { + it('should select node by id', () => { + const nodeId = 'test-node'; + service.selectNode(nodeId); + expect(mockMap.instance.selectNode).toHaveBeenCalledWith(nodeId); + }); + + it('should select node by direction', () => { + service.selectNode('left'); + expect(mockMap.instance.selectNode).toHaveBeenCalledWith('left'); + }); + }); + + describe('copyNode', () => { + it('should copy node successfully', async () => { + await service.copyNode('test-node'); + expect(mockMap.instance.copyNode).toHaveBeenCalledWith('test-node'); + expect(toastrService.success).toHaveBeenCalledWith('translated-text'); + }); + + it('should handle root node copy error', async () => { + mockMap.instance.copyNode.mockImplementation(() => { + throw new Error('The root node can not be copied'); + }); + + await service.copyNode('root-node'); + expect(toastrService.error).toHaveBeenCalledWith('translated-text'); + }); + }); + }); + + describe('export operations', () => { + beforeEach(async () => { + await service.create('test-id', document.createElement('div')); + }); + + describe('exportMap', () => { + beforeEach(() => { + mockMap.instance.exportRootProperties.mockReturnValue({ + name: 'Test Map', + }); + }); + + it('should export to JSON', async () => { + mockMap.instance.exportAsJSON.mockReturnValue({ some: 'data' }); + + const result = await service.exportMap('json'); + + expect(result.success).toBe(true); + expect(downloadFileSpy).toHaveBeenCalled(); + }); + + it('should export to PNG', async () => { + mockMap.instance.exportAsImage.mockImplementation(callback => { + callback('data:image/png;base64,test'); + }); + + const result = await service.exportMap('png'); + + expect(result.success).toBe(true); + expect(downloadFileSpy).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/teammapper-frontend/src/app/core/services/mmp/mmp.service.ts b/teammapper-frontend/src/app/core/services/mmp/mmp.service.ts index afbbfc5e..ba82923e 100644 --- a/teammapper-frontend/src/app/core/services/mmp/mmp.service.ts +++ b/teammapper-frontend/src/app/core/services/mmp/mmp.service.ts @@ -87,7 +87,9 @@ export class MmpService implements OnDestroy { * Clear or load an existing mind mmp. */ public new(map?: MapSnapshot, notifyWithEvent = true) { - this.currentMap.instance.new(map, notifyWithEvent); + const mapWithCoordinates = + this.currentMap.instance.applyCoordinatesToMapSnapshot(map); + this.currentMap.instance.new(mapWithCoordinates, notifyWithEvent); } /**