diff --git a/.github/workflows/cibuild.yml b/.github/workflows/cibuild.yml index 9af7546278f..b48a34e7081 100644 --- a/.github/workflows/cibuild.yml +++ b/.github/workflows/cibuild.yml @@ -118,7 +118,7 @@ jobs: # on windows there are frequent failures caused by page files being too small # https://github.com/actions/virtual-environments/issues/785 - name: Configure Windows Pagefile - uses: al-cheb/configure-pagefile-action@v1.3 + uses: al-cheb/configure-pagefile-action@v1.4 - name: Run tests run: mvn --batch-mode test -P prettierSkip @@ -130,7 +130,7 @@ jobs: LOCAL_BRANCH: local-pages REMOTE_BRANCH: main TOKEN: ${{ secrets.CHANGELOG_TOKEN }} - MASTER_BRANCH_VERSION: 2.4.0 + MASTER_BRANCH_VERSION: 2.5.0 steps: diff --git a/README.md b/README.md index 4486818745b..953aa02b731 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ GTFS and OpenStreetMap). It applies real-time updates and alerts with immediate clients, finding itineraries that account for disruptions and service changes. Note that this branch contains **OpenTripPlanner 2**, the second major version of OTP, which has -been under development since 2018. The latest version of OTP is v2.4.0, released in September 2023. +been under development since 2018. The latest version of OTP is v2.5.0, released in March 2024. If you do not want to use this version, please switch to the final 1.x release tag `v1.5.0` or the `dev-1.x` branch. diff --git a/client-next/package-lock.json b/client-next/package-lock.json index 7f01598ad30..944da10aa11 100644 --- a/client-next/package-lock.json +++ b/client-next/package-lock.json @@ -9,39 +9,39 @@ "version": "0.0.0", "dependencies": { "@googlemaps/polyline-codec": "1.0.28", - "bootstrap": "5.3.1", + "bootstrap": "5.3.3", "graphql": "16.8.1", "graphql-request": "6.1.0", - "maplibre-gl": "4.0.1", + "maplibre-gl": "4.1.0", "react": "18.2.0", - "react-bootstrap": "2.8.0", + "react-bootstrap": "2.10.1", "react-dom": "18.2.0", - "react-map-gl": "7.1.5" + "react-map-gl": "7.1.7" }, "devDependencies": { - "@graphql-codegen/cli": "5.0.0", - "@graphql-codegen/client-preset": "4.1.0", - "@graphql-codegen/introspection": "4.0.0", - "@parcel/watcher": "2.3.0", - "@testing-library/react": "14.1.2", - "@types/react": "18.2.21", - "@types/react-dom": "18.2.7", - "@typescript-eslint/eslint-plugin": "7.1.0", - "@typescript-eslint/parser": "7.1.0", - "@vitejs/plugin-react": "4.0.4", - "@vitest/coverage-v8": "1.1.3", - "eslint": "8.56.0", - "eslint-config-prettier": "9.0.0", - "eslint-plugin-import": "2.28.1", - "eslint-plugin-jsx-a11y": "6.7.1", - "eslint-plugin-react": "7.33.2", + "@graphql-codegen/cli": "5.0.2", + "@graphql-codegen/client-preset": "4.2.4", + "@graphql-codegen/introspection": "4.0.3", + "@parcel/watcher": "2.4.1", + "@testing-library/react": "14.2.1", + "@types/react": "18.2.65", + "@types/react-dom": "18.2.21", + "@typescript-eslint/eslint-plugin": "7.2.0", + "@typescript-eslint/parser": "7.2.0", + "@vitejs/plugin-react": "4.2.1", + "@vitest/coverage-v8": "1.3.1", + "eslint": "8.57.0", + "eslint-config-prettier": "9.1.0", + "eslint-plugin-import": "2.29.1", + "eslint-plugin-jsx-a11y": "6.8.0", + "eslint-plugin-react": "7.34.0", "eslint-plugin-react-hooks": "4.6.0", - "eslint-plugin-react-refresh": "0.4.3", + "eslint-plugin-react-refresh": "0.4.5", "jsdom": "24.0.0", - "prettier": "3.0.3", - "typescript": "5.2.2", + "prettier": "3.2.5", + "typescript": "5.4.2", "vite": "4.5.2", - "vitest": "1.1.3" + "vitest": "1.3.1" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -380,9 +380,9 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.23.10", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.10.tgz", - "integrity": "sha512-2XpP2XhkXzgxecPNEEK8Vz8Asj9aRxt08oKOqtiZoqV2UGZ5T+EkyP9sXQ9nwMxBIG34a7jmasVqoMop7VdPUw==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.0.tgz", + "integrity": "sha512-QAH+vfvts51BCsNZ2PhY6HAggnlS6omLLFTsIpeqZk/MmJ6cW7tgz5yRv0fMJThcr6FmbMrENh1RgrWPTYA76g==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", @@ -1656,9 +1656,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", - "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1683,16 +1683,17 @@ } }, "node_modules/@graphql-codegen/cli": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@graphql-codegen/cli/-/cli-5.0.0.tgz", - "integrity": "sha512-A7J7+be/a6e+/ul2KI5sfJlpoqeqwX8EzktaKCeduyVKgOLA6W5t+NUGf6QumBDXU8PEOqXk3o3F+RAwCWOiqA==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@graphql-codegen/cli/-/cli-5.0.2.tgz", + "integrity": "sha512-MBIaFqDiLKuO4ojN6xxG9/xL9wmfD3ZjZ7RsPjwQnSHBCUXnEkdKvX+JVpx87Pq29Ycn8wTJUguXnTZ7Di0Mlw==", "dev": true, "dependencies": { "@babel/generator": "^7.18.13", "@babel/template": "^7.18.10", "@babel/types": "^7.18.13", - "@graphql-codegen/core": "^4.0.0", - "@graphql-codegen/plugin-helpers": "^5.0.1", + "@graphql-codegen/client-preset": "^4.2.2", + "@graphql-codegen/core": "^4.0.2", + "@graphql-codegen/plugin-helpers": "^5.0.3", "@graphql-tools/apollo-engine-loader": "^8.0.0", "@graphql-tools/code-file-loader": "^8.0.0", "@graphql-tools/git-loader": "^8.0.0", @@ -1740,35 +1741,29 @@ } }, "node_modules/@graphql-codegen/client-preset": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@graphql-codegen/client-preset/-/client-preset-4.1.0.tgz", - "integrity": "sha512-/3Ymb/fjxIF1+HGmaI1YwSZbWsrZAWMSQjh3dU425eBjctjsVQ6gzGRr+l/gE5F1mtmCf+vlbTAT03heAc/QIw==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@graphql-codegen/client-preset/-/client-preset-4.2.4.tgz", + "integrity": "sha512-k1c8v2YxJhhITGQGxViG9asLAoop9m7X9duU7Zztqjc98ooxsUzXICfvAWsH3mLAUibXAx4Ax6BPzKsTtQmBPg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.20.2", "@babel/template": "^7.20.7", - "@graphql-codegen/add": "^5.0.0", - "@graphql-codegen/gql-tag-operations": "4.0.1", - "@graphql-codegen/plugin-helpers": "^5.0.1", - "@graphql-codegen/typed-document-node": "^5.0.1", - "@graphql-codegen/typescript": "^4.0.1", - "@graphql-codegen/typescript-operations": "^4.0.1", - "@graphql-codegen/visitor-plugin-common": "^4.0.1", + "@graphql-codegen/add": "^5.0.2", + "@graphql-codegen/gql-tag-operations": "4.0.6", + "@graphql-codegen/plugin-helpers": "^5.0.3", + "@graphql-codegen/typed-document-node": "^5.0.6", + "@graphql-codegen/typescript": "^4.0.6", + "@graphql-codegen/typescript-operations": "^4.2.0", + "@graphql-codegen/visitor-plugin-common": "^5.1.0", "@graphql-tools/documents": "^1.0.0", "@graphql-tools/utils": "^10.0.0", "@graphql-typed-document-node/core": "3.2.0", - "tslib": "~2.5.0" + "tslib": "~2.6.0" }, "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-codegen/client-preset/node_modules/tslib": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", - "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==", - "dev": true - }, "node_modules/@graphql-codegen/core": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@graphql-codegen/core/-/core-4.0.2.tgz", @@ -1785,68 +1780,35 @@ } }, "node_modules/@graphql-codegen/gql-tag-operations": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@graphql-codegen/gql-tag-operations/-/gql-tag-operations-4.0.1.tgz", - "integrity": "sha512-qF6wIbBzW8BNT+wiVsBxrYOs2oYcsxQ7mRvCpfEI3HnNZMAST/uX76W8MqFEJvj4mw7NIDv7xYJAcAZIWM5LWw==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@graphql-codegen/gql-tag-operations/-/gql-tag-operations-4.0.6.tgz", + "integrity": "sha512-y6iXEDpDNjwNxJw3WZqX1/Znj0QHW7+y8O+t2V8qvbTT+3kb2lr9ntc8By7vCr6ctw9tXI4XKaJgpTstJDOwFA==", "dev": true, "dependencies": { - "@graphql-codegen/plugin-helpers": "^5.0.0", - "@graphql-codegen/visitor-plugin-common": "4.0.1", - "@graphql-tools/utils": "^10.0.0", - "auto-bind": "~4.0.0", - "tslib": "~2.5.0" - }, - "peerDependencies": { - "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" - } - }, - "node_modules/@graphql-codegen/gql-tag-operations/node_modules/@graphql-codegen/visitor-plugin-common": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-4.0.1.tgz", - "integrity": "sha512-Bi/1z0nHg4QMsAqAJhds+ForyLtk7A3HQOlkrZNm3xEkY7lcBzPtiOTLBtvziwopBsXUxqeSwVjOOFPLS5Yw1Q==", - "dev": true, - "dependencies": { - "@graphql-codegen/plugin-helpers": "^5.0.0", - "@graphql-tools/optimize": "^2.0.0", - "@graphql-tools/relay-operation-optimizer": "^7.0.0", + "@graphql-codegen/plugin-helpers": "^5.0.3", + "@graphql-codegen/visitor-plugin-common": "5.1.0", "@graphql-tools/utils": "^10.0.0", "auto-bind": "~4.0.0", - "change-case-all": "1.0.15", - "dependency-graph": "^0.11.0", - "graphql-tag": "^2.11.0", - "parse-filepath": "^1.0.2", - "tslib": "~2.5.0" + "tslib": "~2.6.0" }, "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-codegen/gql-tag-operations/node_modules/tslib": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", - "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==", - "dev": true - }, "node_modules/@graphql-codegen/introspection": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@graphql-codegen/introspection/-/introspection-4.0.0.tgz", - "integrity": "sha512-t9g3AkK99dfHblMWtG4ynUM9+A7JrWq5110zSpNV2wlSnv0+bRKagDW8gozwgXfR5i1IIG8QDjJZ6VgXQVqCZw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@graphql-codegen/introspection/-/introspection-4.0.3.tgz", + "integrity": "sha512-4cHRG15Zu4MXMF4wTQmywNf4+fkDYv5lTbzraVfliDnB8rJKcaurQpRBi11KVuQUe24YTq/Cfk4uwewfNikWoA==", "dev": true, "dependencies": { - "@graphql-codegen/plugin-helpers": "^5.0.0", - "@graphql-codegen/visitor-plugin-common": "^4.0.0", - "tslib": "~2.5.0" + "@graphql-codegen/plugin-helpers": "^5.0.3", + "@graphql-codegen/visitor-plugin-common": "^5.0.0", + "tslib": "~2.6.0" }, "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-codegen/introspection/node_modules/tslib": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", - "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==", - "dev": true - }, "node_modules/@graphql-codegen/plugin-helpers": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-5.0.3.tgz", @@ -1879,13 +1841,13 @@ } }, "node_modules/@graphql-codegen/typed-document-node": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@graphql-codegen/typed-document-node/-/typed-document-node-5.0.4.tgz", - "integrity": "sha512-t66Z6erQ4Dh1j6f9pRZmc8uYtHoUI3A49tLmJAlg9/3IV0kCmwrWKJut/G8SeOefDLG8cXBTVtI/YuZOe1Te+w==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typed-document-node/-/typed-document-node-5.0.6.tgz", + "integrity": "sha512-US0J95hOE2/W/h42w4oiY+DFKG7IetEN1mQMgXXeat1w6FAR5PlIz4JrRrEkiVfVetZ1g7K78SOwBD8/IJnDiA==", "dev": true, "dependencies": { "@graphql-codegen/plugin-helpers": "^5.0.3", - "@graphql-codegen/visitor-plugin-common": "4.1.2", + "@graphql-codegen/visitor-plugin-common": "5.1.0", "auto-bind": "~4.0.0", "change-case-all": "1.0.15", "tslib": "~2.6.0" @@ -1895,14 +1857,14 @@ } }, "node_modules/@graphql-codegen/typescript": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-4.0.4.tgz", - "integrity": "sha512-x79CKLfP9UQCX+/I78qxQlMs2Mmq3pF1lKafZo7lAno0f/fvJ+qWUduzdgjRNz+YL+5blGeWcC0pWEDxniO7hw==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-4.0.6.tgz", + "integrity": "sha512-IBG4N+Blv7KAL27bseruIoLTjORFCT3r+QYyMC3g11uY3/9TPpaUyjSdF70yBe5GIQ6dAgDU+ENUC1v7EPi0rw==", "dev": true, "dependencies": { "@graphql-codegen/plugin-helpers": "^5.0.3", "@graphql-codegen/schema-ast": "^4.0.2", - "@graphql-codegen/visitor-plugin-common": "4.1.2", + "@graphql-codegen/visitor-plugin-common": "5.1.0", "auto-bind": "~4.0.0", "tslib": "~2.6.0" }, @@ -1911,14 +1873,14 @@ } }, "node_modules/@graphql-codegen/typescript-operations": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-operations/-/typescript-operations-4.1.2.tgz", - "integrity": "sha512-CtCWK+gW7hS+Ely3lohr8CL1HVLswQzMcaUk3k1sxdWCWKTNq7abMsWa31rTVwRCJ+WNEkM/7S8sIBTpEG683A==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-operations/-/typescript-operations-4.2.0.tgz", + "integrity": "sha512-lmuwYb03XC7LNRS8oo9M4/vlOrq/wOKmTLBHlltK2YJ1BO/4K/Q9Jdv/jDmJpNydHVR1fmeF4wAfsIp1f9JibA==", "dev": true, "dependencies": { "@graphql-codegen/plugin-helpers": "^5.0.3", - "@graphql-codegen/typescript": "^4.0.4", - "@graphql-codegen/visitor-plugin-common": "4.1.2", + "@graphql-codegen/typescript": "^4.0.6", + "@graphql-codegen/visitor-plugin-common": "5.1.0", "auto-bind": "~4.0.0", "tslib": "~2.6.0" }, @@ -1927,9 +1889,9 @@ } }, "node_modules/@graphql-codegen/visitor-plugin-common": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-4.1.2.tgz", - "integrity": "sha512-yk7iEAL1kYZ2Gi/pvVjdsZhul5WsYEM4Zcgh2Ev15VicMdJmPHsMhNUsZWyVJV0CaQCYpNOFlGD/11Ea3pn4GA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-5.1.0.tgz", + "integrity": "sha512-eamQxtA9bjJqI2lU5eYoA1GbdMIRT2X8m8vhWYsVQVWD3qM7sx/IqJU0kx0J3Vd4/CSd36BzL6RKwksibytDIg==", "dev": true, "dependencies": { "@graphql-codegen/plugin-helpers": "^5.0.3", @@ -2491,13 +2453,13 @@ "dev": true }, "node_modules/@graphql-tools/relay-operation-optimizer": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-7.0.0.tgz", - "integrity": "sha512-UNlJi5y3JylhVWU4MBpL0Hun4Q7IoJwv9xYtmAz+CgRa066szzY7dcuPfxrA7cIGgG/Q6TVsKsYaiF4OHPs1Fw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-7.0.1.tgz", + "integrity": "sha512-y0ZrQ/iyqWZlsS/xrJfSir3TbVYJTYmMOu4TaSz6F4FRDTQ3ie43BlKkhf04rC28pnUOS4BO9pDcAo1D30l5+A==", "dev": true, "dependencies": { "@ardatan/relay-compiler": "12.0.0", - "@graphql-tools/utils": "^10.0.0", + "@graphql-tools/utils": "^10.0.13", "tslib": "^2.4.0" }, "engines": { @@ -2854,11 +2816,10 @@ } }, "node_modules/@parcel/watcher": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.3.0.tgz", - "integrity": "sha512-pW7QaFiL11O0BphO+bq3MgqeX/INAk9jgBldVDYjlQPO4VddoZnF22TcF9onMhnLVHuNqBJeRf+Fj7eezi/+rQ==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.4.1.tgz", + "integrity": "sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA==", "dev": true, - "hasInstallScript": true, "dependencies": { "detect-libc": "^1.0.3", "is-glob": "^4.0.3", @@ -2873,24 +2834,24 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.3.0", - "@parcel/watcher-darwin-arm64": "2.3.0", - "@parcel/watcher-darwin-x64": "2.3.0", - "@parcel/watcher-freebsd-x64": "2.3.0", - "@parcel/watcher-linux-arm-glibc": "2.3.0", - "@parcel/watcher-linux-arm64-glibc": "2.3.0", - "@parcel/watcher-linux-arm64-musl": "2.3.0", - "@parcel/watcher-linux-x64-glibc": "2.3.0", - "@parcel/watcher-linux-x64-musl": "2.3.0", - "@parcel/watcher-win32-arm64": "2.3.0", - "@parcel/watcher-win32-ia32": "2.3.0", - "@parcel/watcher-win32-x64": "2.3.0" + "@parcel/watcher-android-arm64": "2.4.1", + "@parcel/watcher-darwin-arm64": "2.4.1", + "@parcel/watcher-darwin-x64": "2.4.1", + "@parcel/watcher-freebsd-x64": "2.4.1", + "@parcel/watcher-linux-arm-glibc": "2.4.1", + "@parcel/watcher-linux-arm64-glibc": "2.4.1", + "@parcel/watcher-linux-arm64-musl": "2.4.1", + "@parcel/watcher-linux-x64-glibc": "2.4.1", + "@parcel/watcher-linux-x64-musl": "2.4.1", + "@parcel/watcher-win32-arm64": "2.4.1", + "@parcel/watcher-win32-ia32": "2.4.1", + "@parcel/watcher-win32-x64": "2.4.1" } }, "node_modules/@parcel/watcher-android-arm64": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.3.0.tgz", - "integrity": "sha512-f4o9eA3dgk0XRT3XhB0UWpWpLnKgrh1IwNJKJ7UJek7eTYccQ8LR7XUWFKqw6aEq5KUNlCcGvSzKqSX/vtWVVA==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.4.1.tgz", + "integrity": "sha512-LOi/WTbbh3aTn2RYddrO8pnapixAziFl6SMxHM69r3tvdSm94JtCenaKgk1GRg5FJ5wpMCpHeW+7yqPlvZv7kg==", "cpu": [ "arm64" ], @@ -2908,9 +2869,9 @@ } }, "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.3.0.tgz", - "integrity": "sha512-mKY+oijI4ahBMc/GygVGvEdOq0L4DxhYgwQqYAz/7yPzuGi79oXrZG52WdpGA1wLBPrYb0T8uBaGFo7I6rvSKw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.4.1.tgz", + "integrity": "sha512-ln41eihm5YXIY043vBrrHfn94SIBlqOWmoROhsMVTSXGh0QahKGy77tfEywQ7v3NywyxBBkGIfrWRHm0hsKtzA==", "cpu": [ "arm64" ], @@ -2928,9 +2889,9 @@ } }, "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.3.0.tgz", - "integrity": "sha512-20oBj8LcEOnLE3mgpy6zuOq8AplPu9NcSSSfyVKgfOhNAc4eF4ob3ldj0xWjGGbOF7Dcy1Tvm6ytvgdjlfUeow==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.4.1.tgz", + "integrity": "sha512-yrw81BRLjjtHyDu7J61oPuSoeYWR3lDElcPGJyOvIXmor6DEo7/G2u1o7I38cwlcoBHQFULqF6nesIX3tsEXMg==", "cpu": [ "x64" ], @@ -2948,9 +2909,9 @@ } }, "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.3.0.tgz", - "integrity": "sha512-7LftKlaHunueAEiojhCn+Ef2CTXWsLgTl4hq0pkhkTBFI3ssj2bJXmH2L67mKpiAD5dz66JYk4zS66qzdnIOgw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.4.1.tgz", + "integrity": "sha512-TJa3Pex/gX3CWIx/Co8k+ykNdDCLx+TuZj3f3h7eOjgpdKM+Mnix37RYsYU4LHhiYJz3DK5nFCCra81p6g050w==", "cpu": [ "x64" ], @@ -2968,9 +2929,9 @@ } }, "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.3.0.tgz", - "integrity": "sha512-1apPw5cD2xBv1XIHPUlq0cO6iAaEUQ3BcY0ysSyD9Kuyw4MoWm1DV+W9mneWI+1g6OeP6dhikiFE6BlU+AToTQ==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.4.1.tgz", + "integrity": "sha512-4rVYDlsMEYfa537BRXxJ5UF4ddNwnr2/1O4MHM5PjI9cvV2qymvhwZSFgXqbS8YoTk5i/JR0L0JDs69BUn45YA==", "cpu": [ "arm" ], @@ -2988,9 +2949,9 @@ } }, "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.3.0.tgz", - "integrity": "sha512-mQ0gBSQEiq1k/MMkgcSB0Ic47UORZBmWoAWlMrTW6nbAGoLZP+h7AtUM7H3oDu34TBFFvjy4JCGP43JlylkTQA==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.4.1.tgz", + "integrity": "sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==", "cpu": [ "arm64" ], @@ -3008,9 +2969,9 @@ } }, "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.3.0.tgz", - "integrity": "sha512-LXZAExpepJew0Gp8ZkJ+xDZaTQjLHv48h0p0Vw2VMFQ8A+RKrAvpFuPVCVwKJCr5SE+zvaG+Etg56qXvTDIedw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.4.1.tgz", + "integrity": "sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==", "cpu": [ "arm64" ], @@ -3028,9 +2989,9 @@ } }, "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.3.0.tgz", - "integrity": "sha512-P7Wo91lKSeSgMTtG7CnBS6WrA5otr1K7shhSjKHNePVmfBHDoAOHYRXgUmhiNfbcGk0uMCHVcdbfxtuiZCHVow==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.4.1.tgz", + "integrity": "sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==", "cpu": [ "x64" ], @@ -3048,9 +3009,9 @@ } }, "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.3.0.tgz", - "integrity": "sha512-+kiRE1JIq8QdxzwoYY+wzBs9YbJ34guBweTK8nlzLKimn5EQ2b2FSC+tAOpq302BuIMjyuUGvBiUhEcLIGMQ5g==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.4.1.tgz", + "integrity": "sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==", "cpu": [ "x64" ], @@ -3068,9 +3029,9 @@ } }, "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.3.0.tgz", - "integrity": "sha512-35gXCnaz1AqIXpG42evcoP2+sNL62gZTMZne3IackM+6QlfMcJLy3DrjuL6Iks7Czpd3j4xRBzez3ADCj1l7Aw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.4.1.tgz", + "integrity": "sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg==", "cpu": [ "arm64" ], @@ -3088,9 +3049,9 @@ } }, "node_modules/@parcel/watcher-win32-ia32": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.3.0.tgz", - "integrity": "sha512-FJS/IBQHhRpZ6PiCjFt1UAcPr0YmCLHRbTc00IBTrelEjlmmgIVLeOx4MSXzx2HFEy5Jo5YdhGpxCuqCyDJ5ow==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.4.1.tgz", + "integrity": "sha512-maNRit5QQV2kgHFSYwftmPBxiuK5u4DXjbXx7q6eKjq5dsLXZ4FJiVvlcw35QXzk0KrUecJmuVFbj4uV9oYrcw==", "cpu": [ "ia32" ], @@ -3108,9 +3069,9 @@ } }, "node_modules/@parcel/watcher-win32-x64": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.3.0.tgz", - "integrity": "sha512-dLx+0XRdMnVI62kU3wbXvbIRhLck4aE28bIGKbRGS7BJNt54IIj9+c/Dkqb+7DJEbHUZAX1bwaoM8PqVlHJmCA==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.4.1.tgz", + "integrity": "sha512-+DvS92F9ezicfswqrvIRM2njcYJbd5mb9CUgtrHCHmvn7pPPa+nMDRu1o1bYYz/l5IB2NVGNJWiH7h1E58IF2A==", "cpu": [ "x64" ], @@ -3437,9 +3398,9 @@ } }, "node_modules/@testing-library/react": { - "version": "14.1.2", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.1.2.tgz", - "integrity": "sha512-z4p7DVBTPjKM5qDZ0t5ZjzkpSNb+fZy1u6bzO7kk8oeGagpPCAtgh4cx1syrfp7a+QWkM021jGqjJaxJJnXAZg==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.2.1.tgz", + "integrity": "sha512-sGdjws32ai5TLerhvzThYFbpnF9XtL65Cjf+gB0Dhr29BGqK+mAeN7SURSdu+eqgET4ANcWoC7FQpkaiGvBr+A==", "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", @@ -3460,6 +3421,47 @@ "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -3552,9 +3554,9 @@ "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" }, "node_modules/@types/react": { - "version": "18.2.21", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.21.tgz", - "integrity": "sha512-neFKG/sBAwGxHgXiIxnbm3/AAVQ/cMRS93hvBpg8xYRbeQSPVABp9U2bRnPf0iI4+Ucdv3plSxKK+3CW2ENJxA==", + "version": "18.2.65", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.65.tgz", + "integrity": "sha512-98TsY0aW4jqx/3RqsUXwMDZSWR1Z4CUlJNue8ueS2/wcxZOsz4xmW1X8ieaWVRHcmmQM3R8xVA4XWB3dJnWwDQ==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -3562,9 +3564,9 @@ } }, "node_modules/@types/react-dom": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz", - "integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==", + "version": "18.2.21", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.21.tgz", + "integrity": "sha512-gnvBA/21SA4xxqNXEwNiVcP0xSGHh/gi1VhWv9Bl46a0ItbTT5nFY+G9VSQpaG/8N/qdJpJ+vftQ4zflTtnjLw==", "dev": true, "dependencies": { "@types/react": "*" @@ -3612,16 +3614,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.1.0.tgz", - "integrity": "sha512-j6vT/kCulhG5wBmGtstKeiVr1rdXE4nk+DT1k6trYkwlrvW9eOF5ZbgKnd/YR6PcM4uTEXa0h6Fcvf6X7Dxl0w==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.2.0.tgz", + "integrity": "sha512-mdekAHOqS9UjlmyF/LSs6AIEvfceV749GFxoBAjwAv0nkevfKHWQFDMcBZWUiIC5ft6ePWivXoS36aKQ0Cy3sw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.1.0", - "@typescript-eslint/type-utils": "7.1.0", - "@typescript-eslint/utils": "7.1.0", - "@typescript-eslint/visitor-keys": "7.1.0", + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/type-utils": "7.2.0", + "@typescript-eslint/utils": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -3680,15 +3682,15 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.1.0.tgz", - "integrity": "sha512-V1EknKUubZ1gWFjiOZhDSNToOjs63/9O0puCgGS8aDOgpZY326fzFu15QAUjwaXzRZjf/qdsdBrckYdv9YxB8w==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz", + "integrity": "sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.1.0", - "@typescript-eslint/types": "7.1.0", - "@typescript-eslint/typescript-estree": "7.1.0", - "@typescript-eslint/visitor-keys": "7.1.0", + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/typescript-estree": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", "debug": "^4.3.4" }, "engines": { @@ -3708,13 +3710,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.1.0.tgz", - "integrity": "sha512-6TmN4OJiohHfoOdGZ3huuLhpiUgOGTpgXNUPJgeZOZR3DnIpdSgtt83RS35OYNNXxM4TScVlpVKC9jyQSETR1A==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.2.0.tgz", + "integrity": "sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.0", - "@typescript-eslint/visitor-keys": "7.1.0" + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -3725,13 +3727,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.1.0.tgz", - "integrity": "sha512-UZIhv8G+5b5skkcuhgvxYWHjk7FW7/JP5lPASMEUoliAPwIH/rxoUSQPia2cuOj9AmDZmwUl1usKm85t5VUMew==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.2.0.tgz", + "integrity": "sha512-xHi51adBHo9O9330J8GQYQwrKBqbIPJGZZVQTHHmy200hvkLZFWJIFtAG/7IYTWUyun6DE6w5InDReePJYJlJA==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.1.0", - "@typescript-eslint/utils": "7.1.0", + "@typescript-eslint/typescript-estree": "7.2.0", + "@typescript-eslint/utils": "7.2.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -3752,9 +3754,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.1.0.tgz", - "integrity": "sha512-qTWjWieJ1tRJkxgZYXx6WUYtWlBc48YRxgY2JN1aGeVpkhmnopq+SUC8UEVGNXIvWH7XyuTjwALfG6bFEgCkQA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.2.0.tgz", + "integrity": "sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -3765,13 +3767,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.1.0.tgz", - "integrity": "sha512-k7MyrbD6E463CBbSpcOnwa8oXRdHzH1WiVzOipK3L5KSML92ZKgUBrTlehdi7PEIMT8k0bQixHUGXggPAlKnOQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz", + "integrity": "sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.0", - "@typescript-eslint/visitor-keys": "7.1.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -3850,17 +3852,17 @@ "dev": true }, "node_modules/@typescript-eslint/utils": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.1.0.tgz", - "integrity": "sha512-WUFba6PZC5OCGEmbweGpnNJytJiLG7ZvDBJJoUcX4qZYf1mGZ97mO2Mps6O2efxJcJdRNpqweCistDbZMwIVHw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.2.0.tgz", + "integrity": "sha512-YfHpnMAGb1Eekpm3XRK8hcMwGLGsnT6L+7b2XyRv6ouDuJU1tZir1GS2i0+VXRatMwSI1/UfcyPe53ADkU+IuA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "7.1.0", - "@typescript-eslint/types": "7.1.0", - "@typescript-eslint/typescript-estree": "7.1.0", + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/typescript-estree": "7.2.0", "semver": "^7.5.4" }, "engines": { @@ -3908,12 +3910,12 @@ "dev": true }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.1.0.tgz", - "integrity": "sha512-FhUqNWluiGNzlvnDZiXad4mZRhtghdoKW6e98GoEOYSu5cND+E39rG5KwJMUzeENwm1ztYBRqof8wMLP+wNPIA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz", + "integrity": "sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.0", + "@typescript-eslint/types": "7.2.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -3931,27 +3933,28 @@ "dev": true }, "node_modules/@vitejs/plugin-react": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.0.4.tgz", - "integrity": "sha512-7wU921ABnNYkETiMaZy7XqpueMnpu5VxvVps13MjmCo+utBdD79sZzrApHawHtVX66cCJQQTXFcjH0y9dSUK8g==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz", + "integrity": "sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==", "dev": true, "dependencies": { - "@babel/core": "^7.22.9", - "@babel/plugin-transform-react-jsx-self": "^7.22.5", - "@babel/plugin-transform-react-jsx-source": "^7.22.5", + "@babel/core": "^7.23.5", + "@babel/plugin-transform-react-jsx-self": "^7.23.3", + "@babel/plugin-transform-react-jsx-source": "^7.23.3", + "@types/babel__core": "^7.20.5", "react-refresh": "^0.14.0" }, "engines": { "node": "^14.18.0 || >=16.0.0" }, "peerDependencies": { - "vite": "^4.2.0" + "vite": "^4.2.0 || ^5.0.0" } }, "node_modules/@vitest/coverage-v8": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.1.3.tgz", - "integrity": "sha512-Uput7t3eIcbSTOTQBzGtS+0kah96bX+szW9qQrLeGe3UmgL2Akn8POnyC2lH7XsnREZOds9aCUTxgXf+4HX5RA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.3.1.tgz", + "integrity": "sha512-UuBnkSJUNE9rdHjDCPyJ4fYuMkoMtnghes1XohYa4At0MS3OQSAo97FrbwSLRshYsXThMZy1+ybD/byK5llyIg==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.1", @@ -3962,7 +3965,7 @@ "istanbul-lib-source-maps": "^4.0.1", "istanbul-reports": "^3.1.6", "magic-string": "^0.30.5", - "magicast": "^0.3.2", + "magicast": "^0.3.3", "picocolors": "^1.0.0", "std-env": "^3.5.0", "test-exclude": "^6.0.0", @@ -3972,17 +3975,17 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "^1.0.0" + "vitest": "1.3.1" } }, "node_modules/@vitest/expect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.1.3.tgz", - "integrity": "sha512-MnJqsKc1Ko04lksF9XoRJza0bGGwTtqfbyrsYv5on4rcEkdo+QgUdITenBQBUltKzdxW7K3rWh+nXRULwsdaVg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.3.1.tgz", + "integrity": "sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw==", "dev": true, "dependencies": { - "@vitest/spy": "1.1.3", - "@vitest/utils": "1.1.3", + "@vitest/spy": "1.3.1", + "@vitest/utils": "1.3.1", "chai": "^4.3.10" }, "funding": { @@ -3990,12 +3993,12 @@ } }, "node_modules/@vitest/runner": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.1.3.tgz", - "integrity": "sha512-Va2XbWMnhSdDEh/OFxyUltgQuuDRxnarK1hW5QNN4URpQrqq6jtt8cfww/pQQ4i0LjoYxh/3bYWvDFlR9tU73g==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.3.1.tgz", + "integrity": "sha512-5FzF9c3jG/z5bgCnjr8j9LNq/9OxV2uEBAITOXfoe3rdZJTdO7jzThth7FXv/6b+kdY65tpRQB7WaKhNZwX+Kg==", "dev": true, "dependencies": { - "@vitest/utils": "1.1.3", + "@vitest/utils": "1.3.1", "p-limit": "^5.0.0", "pathe": "^1.1.1" }, @@ -4031,9 +4034,9 @@ } }, "node_modules/@vitest/snapshot": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.1.3.tgz", - "integrity": "sha512-U0r8pRXsLAdxSVAyGNcqOU2H3Z4Y2dAAGGelL50O0QRMdi1WWeYHdrH/QWpN1e8juWfVKsb8B+pyJwTC+4Gy9w==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.3.1.tgz", + "integrity": "sha512-EF++BZbt6RZmOlE3SuTPu/NfwBF6q4ABS37HHXzs2LUVPBLx2QoY/K0fKpRChSo8eLiuxcbCVfqKgx/dplCDuQ==", "dev": true, "dependencies": { "magic-string": "^0.30.5", @@ -4077,9 +4080,9 @@ "dev": true }, "node_modules/@vitest/spy": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.1.3.tgz", - "integrity": "sha512-Ec0qWyGS5LhATFQtldvChPTAHv08yHIOZfiNcjwRQbFPHpkih0md9KAbs7TfeIfL7OFKoe7B/6ukBTqByubXkQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.3.1.tgz", + "integrity": "sha512-xAcW+S099ylC9VLU7eZfdT9myV67Nor9w9zhf0mGCYJSO+zM2839tOeROTdikOi/8Qeusffvxb/MyBSOja1Uig==", "dev": true, "dependencies": { "tinyspy": "^2.2.0" @@ -4089,9 +4092,9 @@ } }, "node_modules/@vitest/utils": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.1.3.tgz", - "integrity": "sha512-Dyt3UMcdElTll2H75vhxfpZu03uFpXRCHxWnzcrFjZxT1kTbq8ALUYIeBgGolo1gldVdI0YSlQRacsqxTwNqwg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.3.1.tgz", + "integrity": "sha512-d3Waie/299qqRyHTm2DjADeTaNdNSVsnwHPWrs20JMpjh6eiVq7ggggweO8rc4arhf6rRkWuHKwvxGvejUXZZQ==", "dev": true, "dependencies": { "diff-sequences": "^29.6.3", @@ -4363,6 +4366,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array.prototype.findlast": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.4.tgz", + "integrity": "sha512-BMtLxpV+8BD+6ZPFIWmnUBpQoy+A+ujcg4rhp2iwCRJYA7PEh2MS4NL3lz8EiDlLrJPp2hg9qWihr5pd//jcGw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array.prototype.findlastindex": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.4.tgz", @@ -4418,6 +4440,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array.prototype.toreversed": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz", + "integrity": "sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + } + }, "node_modules/array.prototype.tosorted": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz", @@ -4491,9 +4525,9 @@ } }, "node_modules/ast-types-flow": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", - "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", "dev": true }, "node_modules/astral-regex": { @@ -4544,15 +4578,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/axe-core": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.8.4.tgz", - "integrity": "sha512-CZLSKisu/bhJ2awW4kJndluz2HLZYIHh5Uy1+ZwDRkJi69811xgIXXfdU9HSLX0Th+ILrHj8qfL/5wzamsFtQg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/axobject-query": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", @@ -4644,9 +4669,9 @@ } }, "node_modules/bootstrap": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.1.tgz", - "integrity": "sha512-jzwza3Yagduci2x0rr9MeFSORjcHpt0lRZukZPZQJT1Dth5qzV7XcgGqYzi39KGAVYR8QEDVoO0ubFKOxzMG+g==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", + "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", "funding": [ { "type": "github", @@ -5752,16 +5777,16 @@ } }, "node_modules/eslint": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", - "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.56.0", - "@humanwhocodes/config-array": "^0.11.13", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -5807,9 +5832,9 @@ } }, "node_modules/eslint-config-prettier": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", - "integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" @@ -5865,28 +5890,28 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.28.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.28.1.tgz", - "integrity": "sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.findlastindex": "^1.2.2", - "array.prototype.flat": "^1.3.1", - "array.prototype.flatmap": "^1.3.1", + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", "debug": "^3.2.7", "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.7", + "eslint-import-resolver-node": "^0.3.9", "eslint-module-utils": "^2.8.0", - "has": "^1.0.3", - "is-core-module": "^2.13.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", - "object.fromentries": "^2.0.6", - "object.groupby": "^1.0.0", - "object.values": "^1.1.6", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", "semver": "^6.3.1", - "tsconfig-paths": "^3.14.2" + "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" @@ -5917,27 +5942,27 @@ } }, "node_modules/eslint-plugin-jsx-a11y": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz", - "integrity": "sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.20.7", - "aria-query": "^5.1.3", - "array-includes": "^3.1.6", - "array.prototype.flatmap": "^1.3.1", - "ast-types-flow": "^0.0.7", - "axe-core": "^4.6.2", - "axobject-query": "^3.1.1", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.8.0.tgz", + "integrity": "sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.23.2", + "aria-query": "^5.3.0", + "array-includes": "^3.1.7", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "=4.7.0", + "axobject-query": "^3.2.1", "damerau-levenshtein": "^1.0.8", "emoji-regex": "^9.2.2", - "has": "^1.0.3", - "jsx-ast-utils": "^3.3.3", - "language-tags": "=1.0.5", + "es-iterator-helpers": "^1.0.15", + "hasown": "^2.0.0", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", "minimatch": "^3.1.2", - "object.entries": "^1.1.6", - "object.fromentries": "^2.0.6", - "semver": "^6.3.0" + "object.entries": "^1.1.7", + "object.fromentries": "^2.0.7" }, "engines": { "node": ">=4.0" @@ -5946,28 +5971,48 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" } }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/axe-core": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.0.tgz", + "integrity": "sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/eslint-plugin-react": { - "version": "7.33.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz", - "integrity": "sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==", + "version": "7.34.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.0.tgz", + "integrity": "sha512-MeVXdReleBTdkz/bvcQMSnCXGi+c9kvy51IpinjnJgutl3YTHWsDdke7Z1ufZpGfDG8xduBDKyjtB9JH1eBKIQ==", "dev": true, "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flatmap": "^1.3.1", - "array.prototype.tosorted": "^1.1.1", + "array-includes": "^3.1.7", + "array.prototype.findlast": "^1.2.4", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.toreversed": "^1.1.2", + "array.prototype.tosorted": "^1.1.3", "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.0.12", + "es-iterator-helpers": "^1.0.17", "estraverse": "^5.3.0", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", - "object.entries": "^1.1.6", - "object.fromentries": "^2.0.6", - "object.hasown": "^1.1.2", - "object.values": "^1.1.6", + "object.entries": "^1.1.7", + "object.fromentries": "^2.0.7", + "object.hasown": "^1.1.3", + "object.values": "^1.1.7", "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.4", + "resolve": "^2.0.0-next.5", "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.8" + "string.prototype.matchall": "^4.0.10" }, "engines": { "node": ">=4" @@ -5989,9 +6034,9 @@ } }, "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.3.tgz", - "integrity": "sha512-Hh0wv8bUNY877+sI0BlCUlsS0TYYQqvzEwJsJJPM2WF4RnTStSnSR3zdJYa2nPOJgg3UghXi54lVyMSmpCalzA==", + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.5.tgz", + "integrity": "sha512-D53FYKJa+fDmZMtriODxvhwrO+IOqrxoEo21gMA0sjHdU6dPVH4OhyFip9ypl8HOF5RV5KdTo+rBQLvnY2cO8w==", "dev": true, "peerDependencies": { "eslint": ">=7" @@ -6832,15 +6877,6 @@ "graphql": ">=0.11 <=16" } }, - "node_modules/has": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", - "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", - "dev": true, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -7936,12 +7972,15 @@ "dev": true }, "node_modules/language-tags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", - "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", "dev": true, "dependencies": { - "language-subtag-registry": "~0.3.2" + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" } }, "node_modules/levn": { @@ -8244,9 +8283,9 @@ } }, "node_modules/maplibre-gl": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-4.0.1.tgz", - "integrity": "sha512-UF+wI2utIciFXNg6+gYaMe7IGa9fMLzAZM3vdlGilqyWYmuibjcN40yGVgkz2r28//aOLphvtli3TbDEjEqHww==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-4.1.0.tgz", + "integrity": "sha512-4RQFJSroo/JAJml6Rj2FFIZOfnjsqPp0O9kSp8aVXQUY0HGXNupltzPKbBZeucqi7ynRQHFeu+onTM3hY0Makw==", "dependencies": { "@mapbox/geojson-rewind": "^0.5.2", "@mapbox/jsonlint-lines-primitives": "^2.0.2", @@ -9083,9 +9122,9 @@ } }, "node_modules/prettier": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", - "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -9242,14 +9281,14 @@ } }, "node_modules/react-bootstrap": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.8.0.tgz", - "integrity": "sha512-e/aNtxl0Z2ozrIaR82jr6Zz7ss9GSoaXpQaxmvtDUsTZIq/XalkduR/ZXP6vbQHz2T4syvjA+4FbtwELxxmpww==", + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.1.tgz", + "integrity": "sha512-J3OpRZIvCTQK+Tg/jOkRUvpYLHMdGeU9KqFUBQrV0d/Qr/3nsINpiOJyZMWnM5SJ3ctZdhPA6eCIKpEJR3Ellg==", "dependencies": { - "@babel/runtime": "^7.21.0", + "@babel/runtime": "^7.22.5", "@restart/hooks": "^0.4.9", - "@restart/ui": "^1.6.3", - "@types/react-transition-group": "^4.4.5", + "@restart/ui": "^1.6.6", + "@types/react-transition-group": "^4.4.6", "classnames": "^2.3.2", "dom-helpers": "^5.2.1", "invariant": "^2.2.4", @@ -9294,9 +9333,9 @@ "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, "node_modules/react-map-gl": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/react-map-gl/-/react-map-gl-7.1.5.tgz", - "integrity": "sha512-YS2u2cSLlZVGjfa394f0snO6f6ZwZVTKqQwjbq/jj0w7fHU01Mn+Xqvm/Qr7nZChoT3OG7kh8JluDcXeBrDG/g==", + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/react-map-gl/-/react-map-gl-7.1.7.tgz", + "integrity": "sha512-mwjc0obkBJOXCcoXQr3VoLqmqwo9vS4bXfbGsdxXzEgVCv/PM0v+1QggL7W0d/ccIy+VCjbXNlGij+PENz6VNg==", "dependencies": { "@maplibre/maplibre-gl-style-spec": "^19.2.1", "@types/mapbox-gl": ">=1.0.0" @@ -10132,17 +10171,23 @@ } }, "node_modules/strip-literal": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", - "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.0.0.tgz", + "integrity": "sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==", "dev": true, "dependencies": { - "acorn": "^8.10.0" + "js-tokens": "^8.0.2" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-8.0.3.tgz", + "integrity": "sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==", + "dev": true + }, "node_modules/supercluster": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz", @@ -10478,9 +10523,9 @@ } }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", + "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -10778,9 +10823,9 @@ } }, "node_modules/vite-node": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.1.3.tgz", - "integrity": "sha512-BLSO72YAkIUuNrOx+8uznYICJfTEbvBAmWClY3hpath5+h1mbPS5OMn42lrTxXuyCazVyZoDkSRnju78GiVCqA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.3.1.tgz", + "integrity": "sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==", "dev": true, "dependencies": { "cac": "^6.7.14", @@ -11277,18 +11322,17 @@ } }, "node_modules/vitest": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.1.3.tgz", - "integrity": "sha512-2l8om1NOkiA90/Y207PsEvJLYygddsOyr81wLQ20Ra8IlLKbyQncWsGZjnbkyG2KwwuTXLQjEPOJuxGMG8qJBQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.3.1.tgz", + "integrity": "sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==", "dev": true, "dependencies": { - "@vitest/expect": "1.1.3", - "@vitest/runner": "1.1.3", - "@vitest/snapshot": "1.1.3", - "@vitest/spy": "1.1.3", - "@vitest/utils": "1.1.3", - "acorn-walk": "^8.3.1", - "cac": "^6.7.14", + "@vitest/expect": "1.3.1", + "@vitest/runner": "1.3.1", + "@vitest/snapshot": "1.3.1", + "@vitest/spy": "1.3.1", + "@vitest/utils": "1.3.1", + "acorn-walk": "^8.3.2", "chai": "^4.3.10", "debug": "^4.3.4", "execa": "^8.0.1", @@ -11297,11 +11341,11 @@ "pathe": "^1.1.1", "picocolors": "^1.0.0", "std-env": "^3.5.0", - "strip-literal": "^1.3.0", + "strip-literal": "^2.0.0", "tinybench": "^2.5.1", - "tinypool": "^0.8.1", + "tinypool": "^0.8.2", "vite": "^5.0.0", - "vite-node": "1.1.3", + "vite-node": "1.3.1", "why-is-node-running": "^2.2.2" }, "bin": { @@ -11316,8 +11360,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "^1.0.0", - "@vitest/ui": "^1.0.0", + "@vitest/browser": "1.3.1", + "@vitest/ui": "1.3.1", "happy-dom": "*", "jsdom": "*" }, @@ -11694,6 +11738,175 @@ "node": ">=12" } }, + "node_modules/vitest/node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.1.tgz", + "integrity": "sha512-iU2Sya8hNn1LhsYyf0N+L4Gf9Qc+9eBTJJJsaOGUp+7x4n2M9dxTt8UvhJl3oeftSjblSlpCfvjA/IfP3g5VjQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/vitest/node_modules/@rollup/rollup-android-arm64": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.1.tgz", + "integrity": "sha512-wlzcWiH2Ir7rdMELxFE5vuM7D6TsOcJ2Yw0c3vaBR3VOsJFVTx9xvwnAvhgU5Ii8Gd6+I11qNHwndDscIm0HXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/vitest/node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.1.tgz", + "integrity": "sha512-YRXa1+aZIFN5BaImK+84B3uNK8C6+ynKLPgvn29X9s0LTVCByp54TB7tdSMHDR7GTV39bz1lOmlLDuedgTwwHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/vitest/node_modules/@rollup/rollup-darwin-x64": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.1.tgz", + "integrity": "sha512-opjWJ4MevxeA8FhlngQWPBOvVWYNPFkq6/25rGgG+KOy0r8clYwL1CFd+PGwRqqMFVQ4/Qd3sQu5t7ucP7C/Uw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/vitest/node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.1.tgz", + "integrity": "sha512-uBkwaI+gBUlIe+EfbNnY5xNyXuhZbDSx2nzzW8tRMjUmpScd6lCQYKY2V9BATHtv5Ef2OBq6SChEP8h+/cxifQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/vitest/node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.1.tgz", + "integrity": "sha512-0bK9aG1kIg0Su7OcFTlexkVeNZ5IzEsnz1ept87a0TUgZ6HplSgkJAnFpEVRW7GRcikT4GlPV0pbtVedOaXHQQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/vitest/node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.1.tgz", + "integrity": "sha512-qB6AFRXuP8bdkBI4D7UPUbE7OQf7u5OL+R94JE42Z2Qjmyj74FtDdLGeriRyBDhm4rQSvqAGCGC01b8Fu2LthQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/vitest/node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.1.tgz", + "integrity": "sha512-sHig3LaGlpNgDj5o8uPEoGs98RII8HpNIqFtAI8/pYABO8i0nb1QzT0JDoXF/pxzqO+FkxvwkHZo9k0NJYDedg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/vitest/node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.1.tgz", + "integrity": "sha512-nD3YcUv6jBJbBNFvSbp0IV66+ba/1teuBcu+fBBPZ33sidxitc6ErhON3JNavaH8HlswhWMC3s5rgZpM4MtPqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/vitest/node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.1.tgz", + "integrity": "sha512-7/XVZqgBby2qp/cO0TQ8uJK+9xnSdJ9ct6gSDdEr4MfABrjTyrW6Bau7HQ73a2a5tPB7hno49A0y1jhWGDN9OQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/vitest/node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.1.tgz", + "integrity": "sha512-CYc64bnICG42UPL7TrhIwsJW4QcKkIt9gGlj21gq3VV0LL6XNb1yAdHVp1pIi9gkts9gGcT3OfUYHjGP7ETAiw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/vitest/node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.1.tgz", + "integrity": "sha512-LN+vnlZ9g0qlHGlS920GR4zFCqAwbv2lULrR29yGaWP9u7wF5L7GqWu9Ah6/kFZPXPUkpdZwd//TNR+9XC9hvA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/vitest/node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.1.tgz", + "integrity": "sha512-n+vkrSyphvmU0qkQ6QBNXCGr2mKjhP08mPRM/Xp5Ck2FV4NrHU+y6axzDeixUrCBHVUS51TZhjqrKBBsHLKb2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/vitest/node_modules/esbuild": { "version": "0.19.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", @@ -11733,9 +11946,9 @@ } }, "node_modules/vitest/node_modules/rollup": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", - "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.1.tgz", + "integrity": "sha512-ggqQKvx/PsB0FaWXhIvVkSWh7a/PCLQAsMjBc+nA2M8Rv2/HG0X6zvixAB7KyZBRtifBUhy5k8voQX/mRnABPg==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -11748,26 +11961,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.10.0", - "@rollup/rollup-android-arm64": "4.10.0", - "@rollup/rollup-darwin-arm64": "4.10.0", - "@rollup/rollup-darwin-x64": "4.10.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", - "@rollup/rollup-linux-arm64-gnu": "4.10.0", - "@rollup/rollup-linux-arm64-musl": "4.10.0", - "@rollup/rollup-linux-riscv64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-musl": "4.10.0", - "@rollup/rollup-win32-arm64-msvc": "4.10.0", - "@rollup/rollup-win32-ia32-msvc": "4.10.0", - "@rollup/rollup-win32-x64-msvc": "4.10.0", + "@rollup/rollup-android-arm-eabi": "4.12.1", + "@rollup/rollup-android-arm64": "4.12.1", + "@rollup/rollup-darwin-arm64": "4.12.1", + "@rollup/rollup-darwin-x64": "4.12.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.1", + "@rollup/rollup-linux-arm64-gnu": "4.12.1", + "@rollup/rollup-linux-arm64-musl": "4.12.1", + "@rollup/rollup-linux-riscv64-gnu": "4.12.1", + "@rollup/rollup-linux-x64-gnu": "4.12.1", + "@rollup/rollup-linux-x64-musl": "4.12.1", + "@rollup/rollup-win32-arm64-msvc": "4.12.1", + "@rollup/rollup-win32-ia32-msvc": "4.12.1", + "@rollup/rollup-win32-x64-msvc": "4.12.1", "fsevents": "~2.3.2" } }, "node_modules/vitest/node_modules/vite": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", - "integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.6.tgz", + "integrity": "sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==", "dev": true, "dependencies": { "esbuild": "^0.19.3", diff --git a/client-next/package.json b/client-next/package.json index ad3f1fa10a5..df70e5baf28 100644 --- a/client-next/package.json +++ b/client-next/package.json @@ -18,38 +18,38 @@ }, "dependencies": { "@googlemaps/polyline-codec": "1.0.28", - "bootstrap": "5.3.1", + "bootstrap": "5.3.3", "graphql": "16.8.1", "graphql-request": "6.1.0", - "maplibre-gl": "4.0.1", + "maplibre-gl": "4.1.0", "react": "18.2.0", - "react-bootstrap": "2.8.0", + "react-bootstrap": "2.10.1", "react-dom": "18.2.0", - "react-map-gl": "7.1.5" + "react-map-gl": "7.1.7" }, "devDependencies": { - "@graphql-codegen/cli": "5.0.0", - "@graphql-codegen/client-preset": "4.1.0", - "@graphql-codegen/introspection": "4.0.0", - "@parcel/watcher": "2.3.0", - "@testing-library/react": "14.1.2", - "@types/react": "18.2.21", - "@types/react-dom": "18.2.7", - "@typescript-eslint/eslint-plugin": "7.1.0", - "@typescript-eslint/parser": "7.1.0", - "@vitejs/plugin-react": "4.0.4", - "@vitest/coverage-v8": "1.1.3", - "eslint": "8.56.0", - "eslint-config-prettier": "9.0.0", - "eslint-plugin-import": "2.28.1", - "eslint-plugin-jsx-a11y": "6.7.1", - "eslint-plugin-react": "7.33.2", + "@graphql-codegen/cli": "5.0.2", + "@graphql-codegen/client-preset": "4.2.4", + "@graphql-codegen/introspection": "4.0.3", + "@parcel/watcher": "2.4.1", + "@testing-library/react": "14.2.1", + "@types/react": "18.2.65", + "@types/react-dom": "18.2.21", + "@typescript-eslint/eslint-plugin": "7.2.0", + "@typescript-eslint/parser": "7.2.0", + "@vitejs/plugin-react": "4.2.1", + "@vitest/coverage-v8": "1.3.1", + "eslint": "8.57.0", + "eslint-config-prettier": "9.1.0", + "eslint-plugin-import": "2.29.1", + "eslint-plugin-jsx-a11y": "6.8.0", + "eslint-plugin-react": "7.34.0", "eslint-plugin-react-hooks": "4.6.0", - "eslint-plugin-react-refresh": "0.4.3", + "eslint-plugin-react-refresh": "0.4.5", "jsdom": "24.0.0", - "prettier": "3.0.3", - "typescript": "5.2.2", + "prettier": "3.2.5", + "typescript": "5.4.2", "vite": "4.5.2", - "vitest": "1.1.3" + "vitest": "1.3.1" } } diff --git a/doc-templates/Configuration.md b/doc-templates/Configuration.md index 6344e270570..e06adc46dc5 100644 --- a/doc-templates/Configuration.md +++ b/doc-templates/Configuration.md @@ -146,7 +146,7 @@ text inserted is valid JSON (starts with `{` and ends with `}`). Variable substitution is performed on configuration file after the include file directive; Hence variable substitution is also performed on the text in the injected file. -Here is an example including variable substitution, assuming version 2.4.0 of OTP: +Here is an example including variable substitution, assuming version 2.5.0 of OTP: ```JSON // build-config.json @@ -170,7 +170,7 @@ The result will look like this: { "transitFeeds": [ { - "source": "netex-v2.4.0.obj" + "source": "netex-v2.5.0.obj" } ] } diff --git a/docs/Basic-Tutorial.md b/docs/Basic-Tutorial.md index 3d99d96f4da..a7a69b8a3bb 100644 --- a/docs/Basic-Tutorial.md +++ b/docs/Basic-Tutorial.md @@ -18,9 +18,9 @@ JAR containing all other libraries needed for OTP to work, and is available from repository. You will be able to go to [the OTP directory at Maven Central](https://repo1.maven.org/maven2/org/opentripplanner/otp/), navigate to -the [directory of releases](https://repo1.maven.org/maven2/org/opentripplanner/otp/2.4.0/), +the [directory of releases](https://repo1.maven.org/maven2/org/opentripplanner/otp/2.5.0/), and download -the [file with `shaded.jar` suffix](https://repo1.maven.org/maven2/org/opentripplanner/otp/2.4.0/otp-2.4.0-shaded.jar) +the [file with `shaded.jar` suffix](https://repo1.maven.org/maven2/org/opentripplanner/otp/2.5.0/otp-2.5.0-shaded.jar) . You may also want to get your own copy of the OTP source code @@ -129,7 +129,7 @@ below and in other tutorials. The simplest way to use OTP is to build a graph in a single step and start a server immediately, without saving it to disk. The command to do so is: - $ java -Xmx2G -jar otp-2.4.0-shaded.jar --build --serve /home/username/otp + $ java -Xmx2G -jar otp-2.5.0-shaded.jar --build --serve /home/username/otp where `/home/username/otp` should be the directory where you put your configuration and input files. @@ -154,13 +154,13 @@ build a graph from street and transit data then save it to a file using the `--b command line parameters together. If for example your current working directory (`.`) contains the input files and the OTP JAR file, you can use this command: - $ java -Xmx2G -jar otp-2.4.0-shaded.jar --build --save . + $ java -Xmx2G -jar otp-2.5.0-shaded.jar --build --save . This will produce a file called `graph.obj` in the same directory as the inputs. The server can then be started later using the `--load` parameter, and will read this file instead of building the graph from scratch: - $ java -Xmx2G -jar otp-2.4.0-shaded.jar --load . + $ java -Xmx2G -jar otp-2.5.0-shaded.jar --load . Another reason to perform these two phases separately is that the building process loads the entire GTFS and OSM data sets into memory, so can require significantly more memory than just running a @@ -177,16 +177,16 @@ graph once, and then layer transit data on top of the streets to make the final Again assuming the input files and OTP JAR file are in the current working directory, you can build a street graph with OSM and elevation data only (ignoring transit input files) with this command: - $ java -Xmx2G -jar otp-2.4.0-shaded.jar --buildStreet . + $ java -Xmx2G -jar otp-2.5.0-shaded.jar --buildStreet . Then, to build a graph layering transit data on top of the saved street graph (built using the previous command): - $ java -Xmx2G -jar otp-2.4.0-shaded.jar --loadStreet --save . + $ java -Xmx2G -jar otp-2.5.0-shaded.jar --loadStreet --save . Finally, the server can be started using the `--load` parameter: - $ java -Xmx2G -jar otp-2.4.0-shaded.jar --load . + $ java -Xmx2G -jar otp-2.5.0-shaded.jar --load . ## Command Line Switches diff --git a/docs/Changelog.md b/docs/Changelog.md index 9e2e515c463..cfe8cde5133 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -3,25 +3,40 @@ The changelog lists most feature changes between each release. The list is automatically created based on merged pull requests. Search GitHub issues and pull requests for smaller issues. -## 2.5.0 (under development) +## 2.6.0-SNAPSHOT (under development) + +- ISO-8601 date time for GTFS API itinerary responses [#5660](https://github.com/opentripplanner/OpenTripPlanner/pull/5660) +[](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) + +## 2.5.0 (2024-03-13) + +### Notable Changes + +- Make GTFS GraphQL API an official API [#5339](https://github.com/opentripplanner/OpenTripPlanner/pull/5339) +- Make Transmodel GraphQl API an official API [#5573](https://github.com/opentripplanner/OpenTripPlanner/pull/5573), [#5637](https://github.com/opentripplanner/OpenTripPlanner/pull/5637) +- Deprecate REST API [#5580](https://github.com/opentripplanner/OpenTripPlanner/pull/5580) +- Transit group priority [#4999](https://github.com/opentripplanner/OpenTripPlanner/pull/4999), [#5583](https://github.com/opentripplanner/OpenTripPlanner/pull/5583), [#5638](https://github.com/opentripplanner/OpenTripPlanner/pull/5638) +- Transmodel GraphQL API for pass-through searches [#5320](https://github.com/opentripplanner/OpenTripPlanner/pull/5320) +- Migrate to Java 21 [#5421](https://github.com/opentripplanner/OpenTripPlanner/pull/5421) +- New debug client [#5334](https://github.com/opentripplanner/OpenTripPlanner/pull/5334) +- Update to latest GTFS Flex spec draft [#5564](https://github.com/opentripplanner/OpenTripPlanner/pull/5564), [#5655](https://github.com/opentripplanner/OpenTripPlanner/pull/5655) +- Restructure walk/bicycle/car preferences in router-config.json [#5582](https://github.com/opentripplanner/OpenTripPlanner/pull/5582) + +### Detailed changes by Pull Request - Gracefully handle nullable fields in TransitAlert [#5349](https://github.com/opentripplanner/OpenTripPlanner/pull/5349) - Remove transit with higher cost than best on-street itinerary filter [#5222](https://github.com/opentripplanner/OpenTripPlanner/pull/5222) -- Remove banDiscouragedCycling and banDiscouragedWalking [#5341](https://github.com/opentripplanner/OpenTripPlanner/pull/5341) +- Remove `banDiscouragedCycling` and `banDiscouragedWalking` [#5341](https://github.com/opentripplanner/OpenTripPlanner/pull/5341) - Fix rental scooter access [#5361](https://github.com/opentripplanner/OpenTripPlanner/pull/5361) - De-duplicate stops returned by `stopsByRadius` [#5366](https://github.com/opentripplanner/OpenTripPlanner/pull/5366) -- Fix value mapping for bikesAllowed in GTFS GraphQL API [#5368](https://github.com/opentripplanner/OpenTripPlanner/pull/5368) +- Fix value mapping for `bikesAllowed` in GTFS GraphQL API [#5368](https://github.com/opentripplanner/OpenTripPlanner/pull/5368) - Apply correct traversal permissions to barrier vertex [#5369](https://github.com/opentripplanner/OpenTripPlanner/pull/5369) -- Move GTFS GraphQL API out of the sandbox [#5339](https://github.com/opentripplanner/OpenTripPlanner/pull/5339) -- Transmodel GraphQL API for pass-through searches [#5320](https://github.com/opentripplanner/OpenTripPlanner/pull/5320) - Fix check for OSM relation members not being present in the extract [#5379](https://github.com/opentripplanner/OpenTripPlanner/pull/5379) - Add a configurable limit for the search window [#5293](https://github.com/opentripplanner/OpenTripPlanner/pull/5293) -- Fix fare calculation for combined interlined legs [#5408](https://github.com/opentripplanner/OpenTripPlanner/pull/5408) - Fix board slack list mapping in Transmodel API [#5420](https://github.com/opentripplanner/OpenTripPlanner/pull/5420) - Fix flexible quay querying in Transmodel API [#5417](https://github.com/opentripplanner/OpenTripPlanner/pull/5417) - Add validation for missing calls in SIRI update [#5403](https://github.com/opentripplanner/OpenTripPlanner/pull/5403) - Import Occupancy Status from GTFS-RT Vehicle Positions [#5372](https://github.com/opentripplanner/OpenTripPlanner/pull/5372) -- Add Roadmap epic template [#5413](https://github.com/opentripplanner/OpenTripPlanner/pull/5413) - Allow multiple zones in an unscheduled flex trip [#5376](https://github.com/opentripplanner/OpenTripPlanner/pull/5376) - Filter out null, empty and blank elements when mapping feed-scoped ids [#5428](https://github.com/opentripplanner/OpenTripPlanner/pull/5428) - Validate stop id in Transit leg reference [#5440](https://github.com/opentripplanner/OpenTripPlanner/pull/5440) @@ -30,76 +45,48 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Add stricter validation for flex areas [#5457](https://github.com/opentripplanner/OpenTripPlanner/pull/5457) - Remove HTTPS handling and its documentation [#5439](https://github.com/opentripplanner/OpenTripPlanner/pull/5439) - Add support for DSJ in transit leg reference [#5455](https://github.com/opentripplanner/OpenTripPlanner/pull/5455) -- Ignore negative travel-times in Raptor [#5443](https://github.com/opentripplanner/OpenTripPlanner/pull/5443) - Fix sort order bug in optimized transfers [#5446](https://github.com/opentripplanner/OpenTripPlanner/pull/5446) -- Siri file loader [#5460](https://github.com/opentripplanner/OpenTripPlanner/pull/5460) +- SIRI file loader [#5460](https://github.com/opentripplanner/OpenTripPlanner/pull/5460) - Calculate CO₂ emissions of itineraries [#5278](https://github.com/opentripplanner/OpenTripPlanner/pull/5278) -- Update NeTEx Java Model 2.0.15 [#5466](https://github.com/opentripplanner/OpenTripPlanner/pull/5466) -- Migrate to Java 21 [#5421](https://github.com/opentripplanner/OpenTripPlanner/pull/5421) -- Add Roadmap setup docs [#5468](https://github.com/opentripplanner/OpenTripPlanner/pull/5468) - Interpolate increasing stop times for GTFS-RT cancelled trips [#5348](https://github.com/opentripplanner/OpenTripPlanner/pull/5348) - Remove itineraries outside the search window in arriveBy search [#5433](https://github.com/opentripplanner/OpenTripPlanner/pull/5433) -- Add back walk-reluctance in Transmodel API [#5471](https://github.com/opentripplanner/OpenTripPlanner/pull/5471) -- Make `feedId` required for real-time updaters [#5502](https://github.com/opentripplanner/OpenTripPlanner/pull/5502) -- Fix serialization of `AtomicInteger` [#5508](https://github.com/opentripplanner/OpenTripPlanner/pull/5508) +- Add back walk reluctance in Transmodel API [#5471](https://github.com/opentripplanner/OpenTripPlanner/pull/5471) - Improve linking of fixed stops used by flex trips [#5503](https://github.com/opentripplanner/OpenTripPlanner/pull/5503) - Keep min transfer filter is not local to group-by-filters [#5436](https://github.com/opentripplanner/OpenTripPlanner/pull/5436) - Add paging deduplication when cropping [#5458](https://github.com/opentripplanner/OpenTripPlanner/pull/5458) -- Consolidate equivalent stops from several feeds [#5429](https://github.com/opentripplanner/OpenTripPlanner/pull/5429) - Check transport mode when mapping GroupStops [#5518](https://github.com/opentripplanner/OpenTripPlanner/pull/5518) -- Cleanup trip times - Part A [#5437](https://github.com/opentripplanner/OpenTripPlanner/pull/5437) - Transfer cost limit [#5516](https://github.com/opentripplanner/OpenTripPlanner/pull/5516) - Fix missed trip when arrive-by search-window is off by one minute [#5520](https://github.com/opentripplanner/OpenTripPlanner/pull/5520) -- Transit group priority - Part 1 [#4999](https://github.com/opentripplanner/OpenTripPlanner/pull/4999) - Remove `matchBusRoutesToStreets` [#5523](https://github.com/opentripplanner/OpenTripPlanner/pull/5523) -- Rename realtime to real-time in docs [#5535](https://github.com/opentripplanner/OpenTripPlanner/pull/5535) - Add same submode in alternative legs filter [#5548](https://github.com/opentripplanner/OpenTripPlanner/pull/5548) - Fix issue where stop points are sometimes added twice to index [#5552](https://github.com/opentripplanner/OpenTripPlanner/pull/5552) - Improve shutdown logic [#5514](https://github.com/opentripplanner/OpenTripPlanner/pull/5514) -- Create TripOnServiceDate for new siri realtime servicejourneys [#5542](https://github.com/opentripplanner/OpenTripPlanner/pull/5542) -- New debug client [#5334](https://github.com/opentripplanner/OpenTripPlanner/pull/5334) +- Create TripOnServiceDate for new SIRI real-time servicejourneys [#5542](https://github.com/opentripplanner/OpenTripPlanner/pull/5542) - Improve paging - avoid duplicates and missed itineraries when paging [#5551](https://github.com/opentripplanner/OpenTripPlanner/pull/5551) -- Create own parking preferences for bike and car in the internal model [#5521](https://github.com/opentripplanner/OpenTripPlanner/pull/5521) -- Make Transmodel GraphQl API an official OTP API [#5573](https://github.com/opentripplanner/OpenTripPlanner/pull/5573) - Add option to include stations in `nearest` search [#5390](https://github.com/opentripplanner/OpenTripPlanner/pull/5390) -- GTFS Flex spec update: separate columns for `location_id`, `location_group_id` [#5564](https://github.com/opentripplanner/OpenTripPlanner/pull/5564) -- Report NO_TRANSIT_CONNECTION when search-window is set. [#5570](https://github.com/opentripplanner/OpenTripPlanner/pull/5570) -- Transit priority - part 3 [#5583](https://github.com/opentripplanner/OpenTripPlanner/pull/5583) +- Report NO_TRANSIT_CONNECTION when search-window is set [#5570](https://github.com/opentripplanner/OpenTripPlanner/pull/5570) - Fix preference cost comparisons [#5586](https://github.com/opentripplanner/OpenTripPlanner/pull/5586) -- Report and throw away trip-times which fail sanity check [#5587](https://github.com/opentripplanner/OpenTripPlanner/pull/5587) - Consider escalator edges in island pruning [#5591](https://github.com/opentripplanner/OpenTripPlanner/pull/5591) -- Create own rental preferences for bike and car in the internal model [#5562](https://github.com/opentripplanner/OpenTripPlanner/pull/5562) - Adding situation-version to TransmodelGraphQL API [#5592](https://github.com/opentripplanner/OpenTripPlanner/pull/5592) -- Move REST API into sandbox [#5580](https://github.com/opentripplanner/OpenTripPlanner/pull/5580) - Fix high walk reluctance leading to zero egress results for rental searches [#5605](https://github.com/opentripplanner/OpenTripPlanner/pull/5605) - Remove GTFS-RT websocket updater [#5604](https://github.com/opentripplanner/OpenTripPlanner/pull/5604) -- Add stop layer to new Debug UI [#5602](https://github.com/opentripplanner/OpenTripPlanner/pull/5602) - Use fallback timezone if no transit data is loaded [#4652](https://github.com/opentripplanner/OpenTripPlanner/pull/4652) - Add new path for GTFS GraphQL API, remove batch feature [#5581](https://github.com/opentripplanner/OpenTripPlanner/pull/5581) -- Restructure walk/bicycle/car preferences in router-config.json [#5582](https://github.com/opentripplanner/OpenTripPlanner/pull/5582) -- Revert REST API spelling change of real-time [#5629](https://github.com/opentripplanner/OpenTripPlanner/pull/5629) - Remove `FareComponent` [#5613](https://github.com/opentripplanner/OpenTripPlanner/pull/5613) -- Add AreaStop layer to new debug frontend [#5636](https://github.com/opentripplanner/OpenTripPlanner/pull/5636) -- Allow configuration of vector tiles base path [#5627](https://github.com/opentripplanner/OpenTripPlanner/pull/5627) -- Change Transmodel API path to `/otp/transmodel/v3` [#5637](https://github.com/opentripplanner/OpenTripPlanner/pull/5637) - Add flexibleArea to GroupStop Quays [#5625](https://github.com/opentripplanner/OpenTripPlanner/pull/5625) -- Pass-through should override transit-group-priority [#5638](https://github.com/opentripplanner/OpenTripPlanner/pull/5638) -- Introduce `generalizedCostPlusPenalty` to make cost comparsion fairer [#5483](https://github.com/opentripplanner/OpenTripPlanner/pull/5483) +- Introduce `generalizedCostPlusPenalty` to make cost comparison fairer [#5483](https://github.com/opentripplanner/OpenTripPlanner/pull/5483) - Separate walk time from non-transit time [#5648](https://github.com/opentripplanner/OpenTripPlanner/pull/5648) - Remove "fare" [#5645](https://github.com/opentripplanner/OpenTripPlanner/pull/5645) -- Refactor GroupStopBuilder addLocation method [#5651](https://github.com/opentripplanner/OpenTripPlanner/pull/5651) - Remove `VehicleToStopHeuristics` [#5381](https://github.com/opentripplanner/OpenTripPlanner/pull/5381) - Set defaults of the modes WALK, even if one and not the others are set [#5675](https://github.com/opentripplanner/OpenTripPlanner/pull/5675) - Reduce flex default access/egress penalty [#5674](https://github.com/opentripplanner/OpenTripPlanner/pull/5674) -- Add scooter preferences [#5632](https://github.com/opentripplanner/OpenTripPlanner/pull/5632) -- Add GroupStop layer to new debug frontend [#5666](https://github.com/opentripplanner/OpenTripPlanner/pull/5666) -- Update to newest version of GTFS Flex location groups [#5655](https://github.com/opentripplanner/OpenTripPlanner/pull/5655) +- Add scooter preferences [#5632](https://github.com/opentripplanner/OpenTripPlanner/pull/5632) - Use NeTEx authority short name if name is not present [#5698](https://github.com/opentripplanner/OpenTripPlanner/pull/5698) - Add Hamburg OSM mapper [#5701](https://github.com/opentripplanner/OpenTripPlanner/pull/5701) - Remove configurable car speed and determine it in graph build [#5657](https://github.com/opentripplanner/OpenTripPlanner/pull/5657) - Avoid cumulative real-time updates [#5705](https://github.com/opentripplanner/OpenTripPlanner/pull/5705) - Fix time penalty [#5715](https://github.com/opentripplanner/OpenTripPlanner/pull/5715) -[](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) +- Fix world envelope builder when crossing Greenwich meridian [#5731](https://github.com/opentripplanner/OpenTripPlanner/pull/5731) ## 2.4.0 (2023-09-13) diff --git a/docs/Configuration.md b/docs/Configuration.md index 93ca1fa6c1e..ed58f13fa6e 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -173,7 +173,7 @@ text inserted is valid JSON (starts with `{` and ends with `}`). Variable substitution is performed on configuration file after the include file directive; Hence variable substitution is also performed on the text in the injected file. -Here is an example including variable substitution, assuming version 2.4.0 of OTP: +Here is an example including variable substitution, assuming version 2.5.0 of OTP: ```JSON // build-config.json @@ -197,7 +197,7 @@ The result will look like this: { "transitFeeds": [ { - "source": "netex-v2.4.0.obj" + "source": "netex-v2.5.0.obj" } ] } diff --git a/docs/Getting-OTP.md b/docs/Getting-OTP.md index 1186209854b..a71bc02b5d8 100644 --- a/docs/Getting-OTP.md +++ b/docs/Getting-OTP.md @@ -9,8 +9,8 @@ the [release pages on GitHub](https://github.com/opentripplanner/OpenTripPlanner or [the OTP directory at Maven Central](https://repo1.maven.org/maven2/org/opentripplanner/otp/), navigate to the highest version number, and download the file whose name ends with `shaded.jar`. -Note that version numbers like `v2.1.0-rc1` or `v2.4.0-SNAPSHOT` refer to development builds _ -before_ the release version `v2.4.0`. The existence of a build `vX.Y.Z-SNAPSHOT` does not mean +Note that version numbers like `v2.1.0-rc1` or `v2.5.0-SNAPSHOT` refer to development builds _ +before_ the release version `v2.5.0`. The existence of a build `vX.Y.Z-SNAPSHOT` does not mean that `vX.Y.Z` has been released yet. We use the [Github Actions CI system](https://github.com/opentripplanner/OpenTripPlanner/actions) to @@ -87,7 +87,7 @@ For example, you could do the following: ```bash cd OpenTripPlanner -git checkout v2.4.0 +git checkout v2.5.0 git clean -df mvn clean package -DskipTests ``` @@ -110,8 +110,8 @@ file) to the Maven repository, from which it can be automatically included in ot This repository is machine-readable (by Maven or other build systems) and also provides human readable directory listings via HTTP. You can fetch an OTP JAR from this repository by constructing -the proper URL for the release you want. For example, release 2.4.0 will be found -at `https://repo1.maven.org/maven2/org/opentripplanner/otp/2.4.0/otp-2.4.0-shaded.jar`. +the proper URL for the release you want. For example, release 2.5.0 will be found +at `https://repo1.maven.org/maven2/org/opentripplanner/otp/2.5.0/otp-2.5.0-shaded.jar`. To make use of OTP in another Maven project, you must specify it as a dependency in that project's `pom.xml`: @@ -120,6 +120,6 @@ project's `pom.xml`: org.opentripplanner otp - 2.4.0 + 2.5.0 ``` diff --git a/docs/ReleaseChecklist.md b/docs/ReleaseChecklist.md index 7702a60e51c..0f7c0667762 100644 --- a/docs/ReleaseChecklist.md +++ b/docs/ReleaseChecklist.md @@ -69,12 +69,6 @@ manually is more tedious, but keeps eyes on each step and is less prone to failu * Apply the changes recorded in https://github.com/opentripplanner/OpenTripPlanner/tree/signed-deploy-to-central * While still on the tag commit, run `mvn deploy -Prelease`. -* Set up next development iteration - * Add a new section header to `docs/Changelog.md` like `x.y+1.0-SNAPSHOT (in progress)` - * Edit minor version in `pom.xml` to `x.y+1.0-SNAPSHOT` - * `git add pom.xml docs/Changelog.md` - * `git commit -m "Prepare next development iteration x.y+1.0-SNAPSHOT"` - * `git push` * Check that Maven artifact appears on Maven Central * [Directory listing of OTP releases on Maven Central](https://repo1.maven.org/maven2/org/opentripplanner/otp/) * It may take a while (half an hour) for releases to show up in the central repo after Travis @@ -83,7 +77,13 @@ manually is more tedious, but keeps eyes on each step and is less prone to failu * `git checkout dev-2.x` * `git merge master` * `git push` -* Email the OTP dev and users mailing lists +* Set up next development iteration + * Add a new section header to `docs/Changelog.md` like `x.y+1.0-SNAPSHOT (in progress)` + * Edit minor version in `pom.xml` to `x.y+1.0-SNAPSHOT` + * `git add pom.xml docs/Changelog.md` + * `git commit -m "Prepare next development iteration x.y+1.0-SNAPSHOT"` + * `git push` +* Send a message in Gitter and email the OTP users mailing lists * Mention the new version number. * Provide links to the new developer documentation. * Provide links to the artifacts directory on Maven Central. diff --git a/docs/apis/GraphQL-Tutorial.md b/docs/apis/GraphQL-Tutorial.md index 8df6d6f38ee..92645f9b245 100644 --- a/docs/apis/GraphQL-Tutorial.md +++ b/docs/apis/GraphQL-Tutorial.md @@ -88,25 +88,33 @@ Most people want to get routing results out of OTP, so lets see the query for th }, ]) { itineraries { - startTime - endTime + start + end legs { mode - startTime - endTime from { name lat lon - departureTime - arrivalTime + departure { + scheduledTime + estimated { + time + delay + } + } } to { name lat lon - departureTime - arrivalTime + arrival { + scheduledTime + estimated { + time + delay + } + } } route { gtfsId diff --git a/docs/index.md b/docs/index.md index b70c99e4d52..f1ddc791212 100644 --- a/docs/index.md +++ b/docs/index.md @@ -26,7 +26,8 @@ the selector in the upper left of the published documentation. **Releases** -- [Latest](http://docs.opentripplanner.org/en/latest) - Version 2.4.0 (the git master branch) +- [Latest](http://docs.opentripplanner.org/en/latest) - Version 2.5 (the git master branch) +- [v2.4.0](http://docs.opentripplanner.org/en/v2.4.0) - Version 2.4 - [v2.3.0](http://docs.opentripplanner.org/en/v2.3.0) - Version 2.3 - [v2.2.0](http://docs.opentripplanner.org/en/v2.2.0) - Version 2.2 - [v2.1.0](http://docs.opentripplanner.org/en/v2.1.0) - Version 2.1 diff --git a/magidoc.mjs b/magidoc.mjs index a02976f4bcc..a57b17a4308 100644 --- a/magidoc.mjs +++ b/magidoc.mjs @@ -35,7 +35,8 @@ To learn how to deactivate it, read the appTitle: 'OTP GTFS GraphQL API', queryGenerationFactories: { 'Polyline': '<>', - 'GeoJson': '<>' + 'GeoJson': '<>', + 'OffsetDateTime': '2024-02-05T18:04:23+01:00' }, } }, diff --git a/pom.xml b/pom.xml index fc619a66b42..132eaf7db20 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.5.0-SNAPSHOT + 2.6.0-SNAPSHOT jar @@ -60,17 +60,17 @@ 30.2 2.51 - 2.16.1 + 2.16.2 3.1.5 5.10.2 - 1.12.2 + 1.12.3 5.5.3 1.5.3 9.9.1 2.0.12 2.0.15 1.26 - 4.0.4 + 4.0.5 UTF-8 opentripplanner/OpenTripPlanner @@ -328,7 +328,7 @@ but we need the Maven project version as well, so we perform substitution. --> io.github.git-commit-id git-commit-id-maven-plugin - 8.0.0 + 8.0.1 @@ -552,7 +552,7 @@ com.google.cloud libraries-bom - 26.31.0 + 26.34.0 pom import @@ -937,14 +937,14 @@ ch.poole OpeningHoursParser - 0.28.1 + 0.28.2 org.apache.commons commons-compress - 1.26.0 + 1.26.1 test diff --git a/renovate.json5 b/renovate.json5 index e2a94318313..8ecbbeb565e 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -25,7 +25,8 @@ "com.microsoft.azure:azure-servicebus", "com.azure.resourcemanager:azure-resourcemanager-servicebus", "com.azure:azure-core", - "com.azure:azure-messaging-servicebus" + "com.azure:azure-messaging-servicebus", + "com.azure:azure-identity" ], "enabled": false }, @@ -44,7 +45,8 @@ // gbfs-java-model patch releases are automatic dependency upgrades so we automerge { "matchPackageNames": [ - "org.entur.gbfs:gbfs-java-model" + "org.entur.gbfs:gbfs-java-model", + "ch.qos.logback:logback-classic" ], "matchUpdateTypes": ["patch"], "schedule": "on the 18th day of the month", @@ -104,7 +106,6 @@ "org.apache.maven.plugins:maven-surefire-plugin", "org.jacoco:jacoco-maven-plugin", // coverage plugin "org.apache.commons:commons-compress", // only used by tests - "ch.qos.logback:logback-classic", // maven plugins "org.codehaus.mojo:build-helper-maven-plugin", "org.apache.maven.plugins:maven-gpg-plugin", diff --git a/src/client/debug-client-preview/index.html b/src/client/debug-client-preview/index.html index a7b15bef13b..84d1084320f 100644 --- a/src/client/debug-client-preview/index.html +++ b/src/client/debug-client-preview/index.html @@ -5,8 +5,8 @@ OTP Debug Client - - + +
diff --git a/src/ext-test/java/org/opentripplanner/ext/fares/impl/CombinedInterlinedLegsFareServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/fares/impl/CombinedInterlinedLegsFareServiceTest.java index 3f237cf509f..407cd0f11e9 100644 --- a/src/ext-test/java/org/opentripplanner/ext/fares/impl/CombinedInterlinedLegsFareServiceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/fares/impl/CombinedInterlinedLegsFareServiceTest.java @@ -16,12 +16,12 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.ext.fares.impl.CombinedInterlinedLegsFareService.CombinationMode; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Place; import org.opentripplanner.model.plan.PlanTestConstants; import org.opentripplanner.routing.core.FareType; -import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.basic.Money; import org.opentripplanner.transit.model.network.Route; @@ -44,17 +44,19 @@ class CombinedInterlinedLegsFareServiceTest implements PlanTestConstants { static Money tenDollars = Money.usDollars(10); static Money twentyDollars = Money.usDollars(20); - static Stream testCases = Stream.of( - Arguments.of(ALWAYS, interlinedWithSameRoute, tenDollars, "same routes"), - Arguments.of(ALWAYS, interlinedWithDifferentRoute, tenDollars, "different routes"), - Arguments.of(SAME_ROUTE, interlinedWithSameRoute, tenDollars, "same routes"), - Arguments.of(SAME_ROUTE, interlinedWithDifferentRoute, twentyDollars, "different routes") - ); + static Stream testCases() { + return Stream.of( + Arguments.of(ALWAYS, interlinedWithSameRoute, tenDollars, "same routes"), + Arguments.of(ALWAYS, interlinedWithDifferentRoute, tenDollars, "different routes"), + Arguments.of(SAME_ROUTE, interlinedWithSameRoute, tenDollars, "same routes"), + Arguments.of(SAME_ROUTE, interlinedWithDifferentRoute, twentyDollars, "different routes") + ); + } @ParameterizedTest( name = "Itinerary with {3} and combination mode {0} should lead to a fare of {2}" ) - @VariableSource("testCases") + @MethodSource("testCases") void modes(CombinationMode mode, Itinerary itinerary, Money totalPrice, String hint) { var service = new CombinedInterlinedLegsFareService(mode); service.addFareRules( diff --git a/src/ext-test/java/org/opentripplanner/ext/fares/model/FareProductTest.java b/src/ext-test/java/org/opentripplanner/ext/fares/model/FareProductTest.java index 9edcecb7567..089bac64c11 100644 --- a/src/ext-test/java/org/opentripplanner/ext/fares/model/FareProductTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/fares/model/FareProductTest.java @@ -9,10 +9,10 @@ import javax.annotation.Nonnull; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.model.fare.FareMedium; import org.opentripplanner.model.fare.FareProduct; import org.opentripplanner.model.fare.RiderCategory; -import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model.basic.Money; import org.opentripplanner.transit.model.framework.FeedScopedId; @@ -28,27 +28,29 @@ class FareProductTest { static FareMedium MEDIUM = new FareMedium(new FeedScopedId("1", "app"), "App"); - static Stream testCases = Stream.of( - Arguments.of(fareProduct(null, null, null), ZDT, "b18a083d-ee82-3c83-af07-2b8bb11bff9e"), - Arguments.of( - fareProduct(null, null, null), - ZDT.plusHours(1), - "2a60adcf-3e56-338a-ab7d-8407a3bc529b" - ), - Arguments.of( - fareProduct(Duration.ofHours(2), CATEGORY, null), - ZDT, - "ca4a45b5-b837-34d8-b987-4e06fa5a3317" - ), - Arguments.of( - fareProduct(Duration.ofHours(2), CATEGORY, MEDIUM), - ZDT, - "b59e7eef-c118-37b1-8f53-bf2a97c5dae9" - ) - ); + static Stream testCases() { + return Stream.of( + Arguments.of(fareProduct(null, null, null), ZDT, "b18a083d-ee82-3c83-af07-2b8bb11bff9e"), + Arguments.of( + fareProduct(null, null, null), + ZDT.plusHours(1), + "2a60adcf-3e56-338a-ab7d-8407a3bc529b" + ), + Arguments.of( + fareProduct(Duration.ofHours(2), CATEGORY, null), + ZDT, + "ca4a45b5-b837-34d8-b987-4e06fa5a3317" + ), + Arguments.of( + fareProduct(Duration.ofHours(2), CATEGORY, MEDIUM), + ZDT, + "b59e7eef-c118-37b1-8f53-bf2a97c5dae9" + ) + ); + } @ParameterizedTest - @VariableSource("testCases") + @MethodSource("testCases") void instanceId(FareProduct fareProduct, ZonedDateTime startTime, String expectedInstanceId) { var instanceId = fareProduct.uniqueInstanceId(startTime); diff --git a/src/ext-test/java/org/opentripplanner/ext/ridehailing/RideHailingAccessShifterTest.java b/src/ext-test/java/org/opentripplanner/ext/ridehailing/RideHailingAccessShifterTest.java index 465697bf197..3e6df7135e5 100644 --- a/src/ext-test/java/org/opentripplanner/ext/ridehailing/RideHailingAccessShifterTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/ridehailing/RideHailingAccessShifterTest.java @@ -16,6 +16,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner._support.time.ZoneIds; import org.opentripplanner.model.GenericLocation; import org.opentripplanner.routing.algorithm.raptoradapter.transit.DefaultAccessEgress; @@ -24,7 +25,6 @@ import org.opentripplanner.routing.api.request.StreetMode; import org.opentripplanner.street.search.state.State; import org.opentripplanner.street.search.state.TestStateBuilder; -import org.opentripplanner.test.support.VariableSource; class RideHailingAccessShifterTest { @@ -42,21 +42,23 @@ class RideHailingAccessShifterTest { List.of() ); - static Stream testCases = Stream.of( - // leave now, so shift by 10 minutes - Arguments.of(TIME, DEFAULT_ARRIVAL_DURATION), - // only shift by 9 minutes because we are wanting to leave in 1 minute - Arguments.of(TIME.plus(ofMinutes(1)), ofMinutes(9)), - // only shift by 7 minutes because we are wanting to leave in 3 minutes - Arguments.of(TIME.plus(ofMinutes(3)), ofMinutes(7)), - // no shifting because it's far in the future - Arguments.of(TIME.plus(ofMinutes(15)), ZERO), - Arguments.of(TIME.plus(ofMinutes(30)), ZERO), - Arguments.of(TIME.plus(ofMinutes(40)), ZERO) - ); + static Stream testCases() { + return Stream.of( + // leave now, so shift by 10 minutes + Arguments.of(TIME, DEFAULT_ARRIVAL_DURATION), + // only shift by 9 minutes because we are wanting to leave in 1 minute + Arguments.of(TIME.plus(ofMinutes(1)), ofMinutes(9)), + // only shift by 7 minutes because we are wanting to leave in 3 minutes + Arguments.of(TIME.plus(ofMinutes(3)), ofMinutes(7)), + // no shifting because it's far in the future + Arguments.of(TIME.plus(ofMinutes(15)), ZERO), + Arguments.of(TIME.plus(ofMinutes(30)), ZERO), + Arguments.of(TIME.plus(ofMinutes(40)), ZERO) + ); + } @ParameterizedTest - @VariableSource("testCases") + @MethodSource("testCases") void testArrivalDelay(Instant searchTime, Duration expectedArrival) { var req = new RouteRequest(); req.setTo(FROM); @@ -73,14 +75,16 @@ void testArrivalDelay(Instant searchTime, Duration expectedArrival) { assertEquals(expectedArrival, actualArrival); } - static Stream accessShiftCases = Stream.of( - // leave now, so shift by 10 minutes - Arguments.of(TIME, TIME.plus(DEFAULT_ARRIVAL_DURATION)), - Arguments.of(TIME.plus(Duration.ofHours(4)), TIME) - ); + static Stream accessShiftCases() { + return Stream.of( + // leave now, so shift by 10 minutes + Arguments.of(TIME, TIME.plus(DEFAULT_ARRIVAL_DURATION)), + Arguments.of(TIME.plus(Duration.ofHours(4)), TIME) + ); + } @ParameterizedTest - @VariableSource("accessShiftCases") + @MethodSource("accessShiftCases") void shiftAccesses(Instant startTime, Instant expectedStartTime) { var drivingState = TestStateBuilder.ofDriving().streetEdge().streetEdge().build(); var access = new DefaultAccessEgress(0, drivingState); diff --git a/src/ext/java/org/opentripplanner/ext/fares/impl/CombinedInterlinedTransitLeg.java b/src/ext/java/org/opentripplanner/ext/fares/impl/CombinedInterlinedTransitLeg.java index 0e9c10de7eb..9d10b87bbd6 100644 --- a/src/ext/java/org/opentripplanner/ext/fares/impl/CombinedInterlinedTransitLeg.java +++ b/src/ext/java/org/opentripplanner/ext/fares/impl/CombinedInterlinedTransitLeg.java @@ -10,6 +10,7 @@ import org.opentripplanner.framework.collection.ListUtils; import org.opentripplanner.model.fare.FareProductUse; import org.opentripplanner.model.plan.Leg; +import org.opentripplanner.model.plan.LegTime; import org.opentripplanner.model.plan.Place; import org.opentripplanner.model.plan.StopArrival; import org.opentripplanner.model.plan.TransitLeg; @@ -56,6 +57,16 @@ public Trip getTrip() { return first.getTrip(); } + @Override + public LegTime start() { + return first.start(); + } + + @Override + public LegTime end() { + return second.end(); + } + @Override public ZonedDateTime getStartTime() { return first.getStartTime(); diff --git a/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLeg.java b/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLeg.java index 0e83372175a..fac1118556f 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLeg.java +++ b/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLeg.java @@ -16,6 +16,7 @@ import org.opentripplanner.model.PickDrop; import org.opentripplanner.model.fare.FareProductUse; import org.opentripplanner.model.plan.Leg; +import org.opentripplanner.model.plan.LegTime; import org.opentripplanner.model.plan.Place; import org.opentripplanner.model.plan.StopArrival; import org.opentripplanner.model.plan.TransitLeg; @@ -84,6 +85,16 @@ public Accessibility getTripWheelchairAccessibility() { return edge.getFlexTrip().getTrip().getWheelchairBoarding(); } + @Override + public LegTime start() { + return LegTime.ofStatic(startTime); + } + + @Override + public LegTime end() { + return LegTime.ofStatic(endTime); + } + @Override @Nonnull public TransitMode getMode() { diff --git a/src/ext/java/org/opentripplanner/ext/restapi/mapping/PlaceMapper.java b/src/ext/java/org/opentripplanner/ext/restapi/mapping/PlaceMapper.java index b1eb3410af8..28ceb0b84e3 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/mapping/PlaceMapper.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/mapping/PlaceMapper.java @@ -39,8 +39,8 @@ public List mapStopArrivals(Collection domain) { public ApiPlace mapStopArrival(StopArrival domain) { return mapPlace( domain.place, - domain.arrival, - domain.departure, + domain.arrival.time(), + domain.departure.time(), domain.stopPosInPattern, domain.gtfsStopSequence ); diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java index 6280ac28ac2..42ffe992539 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java @@ -10,18 +10,24 @@ import graphql.schema.CoercingParseValueException; import graphql.schema.CoercingSerializeException; import graphql.schema.GraphQLScalarType; +import java.text.ParseException; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import javax.annotation.Nonnull; import org.locationtech.jts.geom.Geometry; import org.opentripplanner.framework.geometry.GeometryUtils; import org.opentripplanner.framework.graphql.scalar.DurationScalarFactory; import org.opentripplanner.framework.model.Grams; +import org.opentripplanner.framework.time.OffsetDateTimeParser; public class GraphQLScalars { private static final ObjectMapper geoJsonMapper = new ObjectMapper() .registerModule(new JtsModule(GeometryUtils.getGeometryFactory())); - public static GraphQLScalarType durationScalar = DurationScalarFactory.createDurationScalar(); + public static GraphQLScalarType DURATION_SCALAR = DurationScalarFactory.createDurationScalar(); - public static GraphQLScalarType polylineScalar = GraphQLScalarType + public static final GraphQLScalarType POLYLINE_SCALAR = GraphQLScalarType .newScalar() .name("Polyline") .description( @@ -50,7 +56,63 @@ public String parseLiteral(Object input) { ) .build(); - public static GraphQLScalarType geoJsonScalar = GraphQLScalarType + public static final GraphQLScalarType OFFSET_DATETIME_SCALAR = GraphQLScalarType + .newScalar() + .name("OffsetDateTime") + .coercing( + new Coercing() { + @Override + public String serialize(@Nonnull Object dataFetcherResult) + throws CoercingSerializeException { + if (dataFetcherResult instanceof ZonedDateTime zdt) { + return zdt.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME); + } else if (dataFetcherResult instanceof OffsetDateTime odt) { + return odt.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME); + } else { + throw new CoercingSerializeException( + "Cannot serialize object of class %s".formatted( + dataFetcherResult.getClass().getSimpleName() + ) + ); + } + } + + @Override + public OffsetDateTime parseValue(Object input) throws CoercingParseValueException { + if (input instanceof CharSequence cs) { + try { + return OffsetDateTimeParser.parseLeniently(cs); + } catch (ParseException e) { + int errorOffset = e.getErrorOffset(); + throw new CoercingParseValueException( + "Cannot parse %s into an OffsetDateTime. Error at character index %s".formatted( + input, + errorOffset + ) + ); + } + } + throw new CoercingParseValueException( + "Cannot parse %s into an OffsetDateTime. Must be a string." + ); + } + + @Override + public OffsetDateTime parseLiteral(Object input) throws CoercingParseLiteralException { + if (input instanceof StringValue sv) { + try { + return OffsetDateTimeParser.parseLeniently(sv.getValue()); + } catch (ParseException e) { + throw new CoercingSerializeException(); + } + } + throw new CoercingParseLiteralException(); + } + } + ) + .build(); + + public static final GraphQLScalarType GEOJSON_SCALAR = GraphQLScalarType .newScalar() .name("GeoJson") .description("Geographic data structures in JSON format. See: https://geojson.org/") @@ -78,7 +140,7 @@ public Geometry parseLiteral(Object input) throws CoercingParseLiteralException ) .build(); - public static GraphQLScalarType graphQLIDScalar = GraphQLScalarType + public static final GraphQLScalarType GRAPHQL_ID_SCALAR = GraphQLScalarType .newScalar() .name("ID") .coercing( @@ -118,7 +180,7 @@ public Relay.ResolvedGlobalId parseLiteral(Object input) ) .build(); - public static GraphQLScalarType gramsScalar = GraphQLScalarType + public static final GraphQLScalarType GRAMS_SCALAR = GraphQLScalarType .newScalar() .name("Grams") .coercing( diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java index ff3e8681ce8..1fd78765a07 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java @@ -106,11 +106,12 @@ protected static GraphQLSchema buildSchema() { IntrospectionTypeWiring typeWiring = new IntrospectionTypeWiring(typeRegistry); RuntimeWiring runtimeWiring = RuntimeWiring .newRuntimeWiring() - .scalar(GraphQLScalars.durationScalar) - .scalar(GraphQLScalars.polylineScalar) - .scalar(GraphQLScalars.geoJsonScalar) - .scalar(GraphQLScalars.graphQLIDScalar) - .scalar(GraphQLScalars.gramsScalar) + .scalar(GraphQLScalars.DURATION_SCALAR) + .scalar(GraphQLScalars.POLYLINE_SCALAR) + .scalar(GraphQLScalars.GEOJSON_SCALAR) + .scalar(GraphQLScalars.GRAPHQL_ID_SCALAR) + .scalar(GraphQLScalars.GRAMS_SCALAR) + .scalar(GraphQLScalars.OFFSET_DATETIME_SCALAR) .scalar(ExtendedScalars.GraphQLLong) .type("Node", type -> type.typeResolver(new NodeTypeResolver())) .type("PlaceInterface", type -> type.typeResolver(new PlaceInterfaceTypeResolver())) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/ItineraryImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/ItineraryImpl.java index c7ae82a2355..20d5a9c348d 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/ItineraryImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/ItineraryImpl.java @@ -2,6 +2,7 @@ import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; +import java.time.OffsetDateTime; import java.util.List; import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; import org.opentripplanner.apis.gtfs.mapping.NumberMapper; @@ -32,6 +33,12 @@ public DataFetcher elevationLost() { return environment -> getSource(environment).getElevationLost(); } + @Override + public DataFetcher end() { + return environment -> getSource(environment).endTime().toOffsetDateTime(); + } + + @Deprecated @Override public DataFetcher endTime() { return environment -> getSource(environment).endTime().toInstant().toEpochMilli(); @@ -57,6 +64,12 @@ public DataFetcher numberOfTransfers() { return environment -> getSource(environment).getNumberOfTransfers(); } + @Override + public DataFetcher start() { + return environment -> getSource(environment).startTime().toOffsetDateTime(); + } + + @Deprecated @Override public DataFetcher startTime() { return environment -> getSource(environment).startTime().toInstant().toEpochMilli(); diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java index 639cb95bf28..975d8d56831 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java @@ -18,6 +18,7 @@ import org.opentripplanner.model.PickDrop; import org.opentripplanner.model.fare.FareProductUse; import org.opentripplanner.model.plan.Leg; +import org.opentripplanner.model.plan.LegTime; import org.opentripplanner.model.plan.ScheduledTransitLeg; import org.opentripplanner.model.plan.StopArrival; import org.opentripplanner.model.plan.StreetLeg; @@ -78,6 +79,12 @@ public DataFetcher duration() { } @Override + public DataFetcher end() { + return environment -> getSource(environment).end(); + } + + @Override + @Deprecated public DataFetcher endTime() { return environment -> getSource(environment).getEndTime().toInstant().toEpochMilli(); } @@ -93,8 +100,8 @@ public DataFetcher from() { Leg source = getSource(environment); return new StopArrival( source.getFrom(), - source.getStartTime(), - source.getStartTime(), + source.start(), + source.start(), source.getBoardStopPosInPattern(), source.getBoardingGtfsStopSequence() ); @@ -216,6 +223,12 @@ public DataFetcher serviceDate() { } @Override + public DataFetcher start() { + return environment -> getSource(environment).start(); + } + + @Override + @Deprecated public DataFetcher startTime() { return environment -> getSource(environment).getStartTime().toInstant().toEpochMilli(); } @@ -231,8 +244,8 @@ public DataFetcher to() { Leg source = getSource(environment); return new StopArrival( source.getTo(), - source.getEndTime(), - source.getEndTime(), + source.end(), + source.end(), source.getAlightStopPosInPattern(), source.getAlightGtfsStopSequence() ); diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PlaceImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PlaceImpl.java index ce3ca0986b1..145321f809c 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PlaceImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PlaceImpl.java @@ -7,6 +7,7 @@ import org.opentripplanner.apis.gtfs.model.StopPosition; import org.opentripplanner.apis.gtfs.model.StopPosition.PositionAtStop; import org.opentripplanner.framework.graphql.GraphQLUtils; +import org.opentripplanner.model.plan.LegTime; import org.opentripplanner.model.plan.Place; import org.opentripplanner.model.plan.StopArrival; import org.opentripplanner.model.plan.VertexType; @@ -17,9 +18,18 @@ public class PlaceImpl implements GraphQLDataFetchers.GraphQLPlace { + @Override + public DataFetcher arrival() { + return environment -> getSource(environment).arrival; + } + + @Deprecated @Override public DataFetcher arrivalTime() { - return environment -> getSource(environment).arrival.toInstant().toEpochMilli(); + return environment -> { + StopArrival stopArrival = getSource(environment); + return stopArrival.arrival.time().toInstant().toEpochMilli(); + }; } @Override @@ -30,7 +40,8 @@ public DataFetcher bikePark() { @Override public DataFetcher bikeRentalStation() { return environment -> { - Place place = getSource(environment).place; + StopArrival stopArrival = getSource(environment); + Place place = stopArrival.place; if (!place.vertexType.equals(VertexType.VEHICLERENTAL)) { return null; @@ -45,31 +56,49 @@ public DataFetcher carPark() { return this::getCarPark; } + @Deprecated + @Override + public DataFetcher departure() { + return environment -> getSource(environment).departure; + } + @Override public DataFetcher departureTime() { - return environment -> getSource(environment).departure.toInstant().toEpochMilli(); + return environment -> { + StopArrival stopArrival = getSource(environment); + return stopArrival.departure.time().toInstant().toEpochMilli(); + }; } @Override public DataFetcher lat() { - return environment -> getSource(environment).place.coordinate.latitude(); + return environment -> { + StopArrival stopArrival = getSource(environment); + return stopArrival.place.coordinate.latitude(); + }; } @Override public DataFetcher lon() { - return environment -> getSource(environment).place.coordinate.longitude(); + return environment -> { + StopArrival stopArrival = getSource(environment); + return stopArrival.place.coordinate.longitude(); + }; } @Override public DataFetcher name() { - return environment -> - GraphQLUtils.getTranslation(getSource(environment).place.name, environment); + return environment -> { + StopArrival stopArrival = getSource(environment); + return GraphQLUtils.getTranslation(stopArrival.place.name, environment); + }; } @Override public DataFetcher rentalVehicle() { return environment -> { - Place place = getSource(environment).place; + StopArrival stopArrival = getSource(environment); + Place place = stopArrival.place; if ( !place.vertexType.equals(VertexType.VEHICLERENTAL) || @@ -84,13 +113,17 @@ public DataFetcher rentalVehicle() { @Override public DataFetcher stop() { - return environment -> getSource(environment).place.stop; + return environment -> { + StopArrival stopArrival = getSource(environment); + return stopArrival.place.stop; + }; } @Override public DataFetcher stopPosition() { return environment -> { - var seq = getSource(environment).gtfsStopSequence; + StopArrival stopArrival = getSource(environment); + var seq = stopArrival.gtfsStopSequence; if (seq != null) { return new PositionAtStop(seq); } else { @@ -107,7 +140,8 @@ public DataFetcher vehicleParking() { @Override public DataFetcher vehicleRentalStation() { return environment -> { - Place place = getSource(environment).place; + StopArrival stopArrival = getSource(environment); + Place place = stopArrival.place; if ( !place.vertexType.equals(VertexType.VEHICLERENTAL) || @@ -123,7 +157,8 @@ public DataFetcher vehicleRentalStation() { @Override public DataFetcher vertexType() { return environment -> { - var place = getSource(environment).place; + StopArrival stopArrival = getSource(environment); + var place = stopArrival.place; return switch (place.vertexType) { case NORMAL -> GraphQLVertexType.NORMAL.name(); case TRANSIT -> GraphQLVertexType.TRANSIT.name(); @@ -134,7 +169,8 @@ public DataFetcher vertexType() { } private VehicleParking getBikePark(DataFetchingEnvironment environment) { - var vehicleParkingWithEntrance = getSource(environment).place.vehicleParkingWithEntrance; + StopArrival stopArrival = getSource(environment); + var vehicleParkingWithEntrance = stopArrival.place.vehicleParkingWithEntrance; if ( vehicleParkingWithEntrance == null || !vehicleParkingWithEntrance.getVehicleParking().hasBicyclePlaces() @@ -146,7 +182,8 @@ private VehicleParking getBikePark(DataFetchingEnvironment environment) { } private VehicleParking getCarPark(DataFetchingEnvironment environment) { - var vehicleParkingWithEntrance = getSource(environment).place.vehicleParkingWithEntrance; + StopArrival stopArrival = getSource(environment); + var vehicleParkingWithEntrance = stopArrival.place.vehicleParkingWithEntrance; if ( vehicleParkingWithEntrance == null || !vehicleParkingWithEntrance.getVehicleParking().hasAnyCarPlaces() @@ -158,7 +195,8 @@ private VehicleParking getCarPark(DataFetchingEnvironment environment) { } private VehicleParking getVehicleParking(DataFetchingEnvironment environment) { - var vehicleParkingWithEntrance = getSource(environment).place.vehicleParkingWithEntrance; + StopArrival stopArrival = getSource(environment); + var vehicleParkingWithEntrance = stopArrival.place.vehicleParkingWithEntrance; if (vehicleParkingWithEntrance == null) { return null; } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index 2f39f7f4030..7fab5720b9a 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -36,6 +36,7 @@ import org.opentripplanner.model.plan.Emissions; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Leg; +import org.opentripplanner.model.plan.LegTime; import org.opentripplanner.model.plan.StopArrival; import org.opentripplanner.model.plan.WalkStep; import org.opentripplanner.routing.alertpatch.TransitAlert; @@ -400,6 +401,8 @@ public interface GraphQLItinerary { public DataFetcher emissionsPerPerson(); + public DataFetcher end(); + public DataFetcher endTime(); public DataFetcher> fares(); @@ -410,6 +413,8 @@ public interface GraphQLItinerary { public DataFetcher numberOfTransfers(); + public DataFetcher start(); + public DataFetcher startTime(); public DataFetcher> systemNotices(); @@ -440,6 +445,8 @@ public interface GraphQLLeg { public DataFetcher duration(); + public DataFetcher end(); + public DataFetcher endTime(); public DataFetcher> fareProducts(); @@ -480,6 +487,8 @@ public interface GraphQLLeg { public DataFetcher serviceDate(); + public DataFetcher start(); + public DataFetcher startTime(); public DataFetcher> steps(); @@ -493,6 +502,16 @@ public interface GraphQLLeg { public DataFetcher walkingBike(); } + /** + * Time information about a passenger at a certain place. May contain real-time information if + * available. + */ + public interface GraphQLLegTime { + public DataFetcher estimated(); + + public DataFetcher scheduledTime(); + } + /** A span of time. */ public interface GraphQLLocalTimeSpan { public DataFetcher from(); @@ -576,6 +595,8 @@ public interface GraphQLPattern { } public interface GraphQLPlace { + public DataFetcher arrival(); + public DataFetcher arrivalTime(); public DataFetcher bikePark(); @@ -584,6 +605,8 @@ public interface GraphQLPlace { public DataFetcher carPark(); + public DataFetcher departure(); + public DataFetcher departureTime(); public DataFetcher lat(); @@ -740,6 +763,12 @@ public interface GraphQLQueryType { public DataFetcher viewer(); } + public interface GraphQLRealtimeEstimate { + public DataFetcher delay(); + + public DataFetcher time(); + } + /** Rental vehicle represents a vehicle that belongs to a rental network. */ public interface GraphQLRentalVehicle { public DataFetcher allowPickupNow(); @@ -1235,6 +1264,10 @@ public interface GraphQLElevationProfileComponent { public DataFetcher elevation(); } + /** + * This type is only here for backwards-compatibility and this API will never return it anymore. + * Please use the leg's `fareProducts` instead. + */ public interface GraphQLFare { public DataFetcher cents(); @@ -1245,7 +1278,10 @@ public interface GraphQLFare { public DataFetcher type(); } - /** Component of the fare (i.e. ticket) for a part of the itinerary */ + /** + * This type is only here for backwards-compatibility and this API will never return it anymore. + * Please use the leg's `fareProducts` instead. + */ public interface GraphQLFareComponent { public DataFetcher cents(); diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml b/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml index c141266d2e6..55f76863cc6 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml @@ -26,6 +26,7 @@ config: Polyline: String GeoJson: org.locationtech.jts.geom.Geometry Grams: org.opentripplanner.framework.model.Grams + OffsetDateTime: java.time.OffsetDateTime Duration: java.time.Duration mappers: AbsoluteDirection: org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLAbsoluteDirection#GraphQLAbsoluteDirection @@ -59,6 +60,7 @@ config: InputField: org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLInputField#GraphQLInputField Itinerary: org.opentripplanner.model.plan.Itinerary#Itinerary Leg: org.opentripplanner.model.plan.Leg#Leg + LegTime: org.opentripplanner.model.plan.LegTime#LegTime Mode: String OccupancyStatus: org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLOccupancyStatus#GraphQLOccupancyStatus TransitMode: org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLTransitMode#GraphQLTransitMode diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/scalars/DateTimeScalarFactory.java b/src/main/java/org/opentripplanner/apis/transmodel/model/scalars/DateTimeScalarFactory.java index 706adc81704..9738a277a41 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/scalars/DateTimeScalarFactory.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/scalars/DateTimeScalarFactory.java @@ -10,10 +10,10 @@ import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeFormatterBuilder; import java.time.format.DateTimeParseException; import java.time.temporal.TemporalAccessor; import javax.annotation.Nonnull; +import org.opentripplanner.framework.time.OffsetDateTimeParser; public final class DateTimeScalarFactory { @@ -25,22 +25,7 @@ public final class DateTimeScalarFactory { Example: `2017-04-23T18:25:43+02:00` or `2017-04-23T16:25:43Z`"""; - // We need to have two offsets, in order to parse both "+0200" and "+02:00". The first is not - // really ISO-8601 compatible with the extended date and time. We need to make parsing strict, in - // order to keep the minute mandatory, otherwise we would be left with an unparsed minute - private static final DateTimeFormatter PARSER = new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .parseLenient() - .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME) - .optionalStart() - .parseStrict() - .appendOffset("+HH:MM:ss", "Z") - .parseLenient() - .optionalEnd() - .optionalStart() - .appendOffset("+HHmmss", "Z") - .optionalEnd() - .toFormatter(); + private static final DateTimeFormatter PARSER = OffsetDateTimeParser.LENIENT_PARSER; private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_OFFSET_DATE_TIME; diff --git a/src/main/java/org/opentripplanner/framework/graphql/scalar/DurationScalarFactory.java b/src/main/java/org/opentripplanner/framework/graphql/scalar/DurationScalarFactory.java index 30dd3754198..50af0fbc759 100644 --- a/src/main/java/org/opentripplanner/framework/graphql/scalar/DurationScalarFactory.java +++ b/src/main/java/org/opentripplanner/framework/graphql/scalar/DurationScalarFactory.java @@ -33,7 +33,7 @@ private static class DurationCoercing implements Coercing { @Nonnull public String serialize(@Nonnull Object input) throws CoercingSerializeException { if (input instanceof Duration duration) { - return duration.toString(); + return DurationUtils.formatDurationWithLeadingMinus(duration); } throw new CoercingSerializeException(input + " cannot be cast to 'Duration'"); diff --git a/src/main/java/org/opentripplanner/framework/io/OtpHttpClient.java b/src/main/java/org/opentripplanner/framework/io/OtpHttpClient.java index 72b67441a18..c922cfcc4be 100644 --- a/src/main/java/org/opentripplanner/framework/io/OtpHttpClient.java +++ b/src/main/java/org/opentripplanner/framework/io/OtpHttpClient.java @@ -138,6 +138,7 @@ private OtpHttpClient(Duration timeout, Duration connectionTtl, int maxConnectio HttpClientBuilder httpClientBuilder = HttpClients .custom() + .setUserAgent("OpenTripPlanner") .setConnectionManager(connectionManager) .setDefaultRequestConfig(requestConfig(timeout)); diff --git a/src/main/java/org/opentripplanner/framework/lang/StringUtils.java b/src/main/java/org/opentripplanner/framework/lang/StringUtils.java index cbb32b9eaca..9eff448d0c7 100644 --- a/src/main/java/org/opentripplanner/framework/lang/StringUtils.java +++ b/src/main/java/org/opentripplanner/framework/lang/StringUtils.java @@ -1,5 +1,6 @@ package org.opentripplanner.framework.lang; +import java.util.regex.Pattern; import javax.annotation.Nonnull; /** @@ -7,6 +8,18 @@ */ public class StringUtils { + /** + * Regex to find unprintable characters like newlines and 'ZERO WIDTH SPACE' (U+200B). + *

+ * \p{C} was chosen over \p{Cntrl} because it also recognises invisible control characters in the + * middle of a word. + */ + private static final String INVISIBLE_CHARS_REGEX = "\\p{C}"; + /** + * Patterns are immutable and thread safe. + */ + private static final Pattern INVISIBLE_CHARS_PATTERN = Pattern.compile(INVISIBLE_CHARS_REGEX); + private StringUtils() {} /** true if the given text is not {@code null} or has at least one none white-space character. */ @@ -119,4 +132,14 @@ public static String quoteReplace(@Nonnull String text) { public static String kebabCase(String input) { return input.toLowerCase().replace('_', '-'); } + + /** + * Detects unprintable control characters like newlines, tabs and invisible whitespace + * like 'ZERO WIDTH SPACE' (U+200B) that don't have an immediate visual representation. + *

+ * Note that "regular" whitespace characters like U+0020 and U+2000 are considered visible. + */ + public static boolean containsInvisibleCharacters(String input) { + return INVISIBLE_CHARS_PATTERN.matcher(input).find(); + } } diff --git a/src/main/java/org/opentripplanner/framework/time/DurationUtils.java b/src/main/java/org/opentripplanner/framework/time/DurationUtils.java index bf59964fbb2..9a01d308a1c 100644 --- a/src/main/java/org/opentripplanner/framework/time/DurationUtils.java +++ b/src/main/java/org/opentripplanner/framework/time/DurationUtils.java @@ -196,4 +196,22 @@ public static int toIntMilliseconds(Duration timeout, int defaultValue) { private static String msToSecondsStr(String formatSeconds, double timeMs) { return String.format(ROOT, formatSeconds, timeMs / 1000.0) + " seconds"; } + + /** + * Formats a duration and if it's a negative amount, it places the minus before the "P" rather + * than in the middle of the value. + *

+ * Background: There are multiple ways to express -1.5 hours: "PT-1H-30M" and "-PT1H30M". + *

+ * The first version is what you get when calling toString() but it's quite confusing. Therefore, + * this method makes sure that you get the second form "-PT1H30M". + */ + public static String formatDurationWithLeadingMinus(Duration duration) { + if (duration.isNegative()) { + var positive = duration.abs().toString(); + return "-" + positive; + } else { + return duration.toString(); + } + } } diff --git a/src/main/java/org/opentripplanner/framework/time/OffsetDateTimeParser.java b/src/main/java/org/opentripplanner/framework/time/OffsetDateTimeParser.java new file mode 100644 index 00000000000..8b40697977a --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/time/OffsetDateTimeParser.java @@ -0,0 +1,42 @@ +package org.opentripplanner.framework.time; + +import java.text.ParseException; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; + +public class OffsetDateTimeParser { + + /** + * We need to have two offsets, in order to parse both "+0200" and "+02:00". The first is not + * really ISO-8601 compatible with the extended date and time. We need to make parsing strict, in + * order to keep the minute mandatory, otherwise we would be left with an unparsed minute + */ + public static final DateTimeFormatter LENIENT_PARSER = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .parseLenient() + .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + .optionalStart() + .parseStrict() + .appendOffset("+HH:MM:ss", "Z") + .parseLenient() + .optionalEnd() + .optionalStart() + .appendOffset("+HHmmss", "Z") + .optionalEnd() + .toFormatter(); + + /** + * Parses a ISO-8601 string into am OffsetDateTime instance allowing the offset to be both in + * '02:00' and '0200' format. + * @throws ParseException if the string cannot be parsed + */ + public static OffsetDateTime parseLeniently(CharSequence input) throws ParseException { + try { + return OffsetDateTime.parse(input, LENIENT_PARSER); + } catch (DateTimeParseException e) { + throw new ParseException(e.getParsedString(), e.getErrorIndex()); + } + } +} diff --git a/src/main/java/org/opentripplanner/gtfs/graphbuilder/GtfsModule.java b/src/main/java/org/opentripplanner/gtfs/graphbuilder/GtfsModule.java index cf9d48c64c5..2f7feb5993e 100644 --- a/src/main/java/org/opentripplanner/gtfs/graphbuilder/GtfsModule.java +++ b/src/main/java/org/opentripplanner/gtfs/graphbuilder/GtfsModule.java @@ -120,12 +120,20 @@ public void buildGraph() { boolean hasTransit = false; + Map feedIdsEncountered = new HashMap<>(); + try { for (GtfsBundle gtfsBundle : gtfsBundles) { GtfsMutableRelationalDao gtfsDao = loadBundle(gtfsBundle); + + final String feedId = gtfsBundle.getFeedId().getId(); + verifyUniqueFeedId(gtfsBundle, feedIdsEncountered, feedId); + + feedIdsEncountered.put(feedId, gtfsBundle); + GTFSToOtpTransitServiceMapper mapper = new GTFSToOtpTransitServiceMapper( new OtpTransitServiceBuilder(transitModel.getStopModel(), issueStore), - gtfsBundle.getFeedId().getId(), + feedId, issueStore, gtfsBundle.discardMinTransferTimes(), gtfsDao, @@ -203,6 +211,32 @@ public void buildGraph() { transitModel.updateCalendarServiceData(hasTransit, calendarServiceData, issueStore); } + /** + * Verifies that a feed id is not assigned twice. + *

+ * Duplicates can happen in the following cases: + * - the feed id is configured twice in build-config.json + * - two GTFS feeds have the same feed_info.feed_id + * - a GTFS feed defines a feed_info.feed_id like '3' that collides with an auto-generated one + *

+ * Debugging these cases is very confusing, so we prevent it from happening. + */ + private static void verifyUniqueFeedId( + GtfsBundle gtfsBundle, + Map feedIdsEncountered, + String feedId + ) { + if (feedIdsEncountered.containsKey(feedId)) { + LOG.error( + "Feed id '{}' has been used for {} but it was already assigned to {}.", + feedId, + gtfsBundle, + feedIdsEncountered.get(feedId) + ); + throw new IllegalArgumentException("Duplicate feed id: '%s'".formatted(feedId)); + } + } + @Override public void checkInputs() { for (GtfsBundle bundle : gtfsBundles) { diff --git a/src/main/java/org/opentripplanner/model/plan/FrequencyTransitLeg.java b/src/main/java/org/opentripplanner/model/plan/FrequencyTransitLeg.java index d2188fb1b0b..48c4b1b7b87 100644 --- a/src/main/java/org/opentripplanner/model/plan/FrequencyTransitLeg.java +++ b/src/main/java/org/opentripplanner/model/plan/FrequencyTransitLeg.java @@ -1,16 +1,9 @@ package org.opentripplanner.model.plan; -import java.time.LocalDate; -import java.time.ZoneId; -import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; -import javax.annotation.Nullable; import org.opentripplanner.framework.time.ServiceDateUtils; -import org.opentripplanner.model.transfer.ConstrainedTransfer; -import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.site.StopLocation; -import org.opentripplanner.transit.model.timetable.TripTimes; /** * One leg of a trip -- that is, a temporally continuous piece of the journey that takes place on a @@ -62,8 +55,8 @@ public List getIntermediateStops() { StopArrival visit = new StopArrival( Place.forStop(stop), - ServiceDateUtils.toZonedDateTime(serviceDate, zoneId, arrivalTime), - ServiceDateUtils.toZonedDateTime(serviceDate, zoneId, departureTime), + LegTime.ofStatic(ServiceDateUtils.toZonedDateTime(serviceDate, zoneId, arrivalTime)), + LegTime.ofStatic(ServiceDateUtils.toZonedDateTime(serviceDate, zoneId, departureTime)), i, tripTimes.gtfsSequenceOfStopIndex(i) ); diff --git a/src/main/java/org/opentripplanner/model/plan/Leg.java b/src/main/java/org/opentripplanner/model/plan/Leg.java index 05213dbdf5e..5a032def979 100644 --- a/src/main/java/org/opentripplanner/model/plan/Leg.java +++ b/src/main/java/org/opentripplanner/model/plan/Leg.java @@ -200,6 +200,16 @@ default Accessibility getTripWheelchairAccessibility() { return null; } + /** + * The time (including realtime information) when the leg starts. + */ + LegTime start(); + + /** + * The time (including realtime information) when the leg ends. + */ + LegTime end(); + /** * The date and time this leg begins. */ diff --git a/src/main/java/org/opentripplanner/model/plan/LegTime.java b/src/main/java/org/opentripplanner/model/plan/LegTime.java new file mode 100644 index 00000000000..354ce4f4b0b --- /dev/null +++ b/src/main/java/org/opentripplanner/model/plan/LegTime.java @@ -0,0 +1,45 @@ +package org.opentripplanner.model.plan; + +import java.time.Duration; +import java.time.ZonedDateTime; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * A scheduled time of a transit vehicle at a certain location with a optional realtime information. + */ +public record LegTime(@Nonnull ZonedDateTime scheduledTime, @Nullable RealTimeEstimate estimated) { + public LegTime { + Objects.requireNonNull(scheduledTime); + } + + @Nonnull + public static LegTime of(ZonedDateTime realtime, int delaySecs) { + var delay = Duration.ofSeconds(delaySecs); + return new LegTime(realtime.minus(delay), new RealTimeEstimate(realtime, delay)); + } + + @Nonnull + public static LegTime ofStatic(ZonedDateTime staticTime) { + return new LegTime(staticTime, null); + } + + /** + * The most up-to-date time available: if realtime data is available it is returned, if not then + * the scheduled one is. + */ + public ZonedDateTime time() { + if (estimated == null) { + return scheduledTime; + } else { + return estimated.time; + } + } + + /** + * Realtime information about a vehicle at a certain place. + * @param delay Delay or "earliness" of a vehicle. Earliness is expressed as a negative number. + */ + record RealTimeEstimate(ZonedDateTime time, Duration delay) {} +} diff --git a/src/main/java/org/opentripplanner/model/plan/ScheduledTransitLeg.java b/src/main/java/org/opentripplanner/model/plan/ScheduledTransitLeg.java index 876567035dd..6bf39d5aa4c 100644 --- a/src/main/java/org/opentripplanner/model/plan/ScheduledTransitLeg.java +++ b/src/main/java/org/opentripplanner/model/plan/ScheduledTransitLeg.java @@ -162,6 +162,24 @@ public Accessibility getTripWheelchairAccessibility() { return tripTimes.getWheelchairAccessibility(); } + @Override + public LegTime start() { + if (getRealTime()) { + return LegTime.of(startTime, getDepartureDelay()); + } else { + return LegTime.ofStatic(startTime); + } + } + + @Override + public LegTime end() { + if (getRealTime()) { + return LegTime.of(endTime, getArrivalDelay()); + } else { + return LegTime.ofStatic(endTime); + } + } + @Override @Nonnull public TransitMode getMode() { @@ -257,17 +275,11 @@ public Place getTo() { @Override public List getIntermediateStops() { List visits = new ArrayList<>(); + var mapper = new StopArrivalMapper(zoneId, serviceDate, tripTimes); for (int i = boardStopPosInPattern + 1; i < alightStopPosInPattern; i++) { StopLocation stop = tripPattern.getStop(i); - - StopArrival visit = new StopArrival( - Place.forStop(stop), - ServiceDateUtils.toZonedDateTime(serviceDate, zoneId, tripTimes.getArrivalTime(i)), - ServiceDateUtils.toZonedDateTime(serviceDate, zoneId, tripTimes.getDepartureTime(i)), - i, - tripTimes.gtfsSequenceOfStopIndex(i) - ); + final StopArrival visit = mapper.map(i, stop, getRealTime()); visits.add(visit); } return visits; diff --git a/src/main/java/org/opentripplanner/model/plan/StopArrival.java b/src/main/java/org/opentripplanner/model/plan/StopArrival.java index 10398768c85..489b122da09 100644 --- a/src/main/java/org/opentripplanner/model/plan/StopArrival.java +++ b/src/main/java/org/opentripplanner/model/plan/StopArrival.java @@ -1,39 +1,31 @@ package org.opentripplanner.model.plan; -import java.time.ZonedDateTime; import org.opentripplanner.framework.tostring.ToStringBuilder; /** * This class is used to represent a stop arrival event mostly for intermediate visits to a stops * along a route. */ -public class StopArrival { +public final class StopArrival { public final Place place; - /** - * The time the rider will arrive at the place. - */ - public final ZonedDateTime arrival; - - /** - * The time the rider will depart the place. - */ - public final ZonedDateTime departure; - - /** - * For transit trips, the stop index (numbered from zero from the start of the trip). - */ + public final LegTime arrival; + public final LegTime departure; public final Integer stopPosInPattern; + public final Integer gtfsStopSequence; /** - * For transit trips, the sequence number of the stop. Per GTFS, these numbers are increasing. + * @param arrival The time the rider will arrive at the place. + * @param departure The time the rider will depart the place. + * @param stopPosInPattern For transit trips, the stop index (numbered from zero from the start of + * the trip). + * @param gtfsStopSequence For transit trips, the sequence number of the stop. Per GTFS, these + * numbers are increasing. */ - public final Integer gtfsStopSequence; - public StopArrival( Place place, - ZonedDateTime arrival, - ZonedDateTime departure, + LegTime arrival, + LegTime departure, Integer stopPosInPattern, Integer gtfsStopSequence ) { @@ -48,8 +40,8 @@ public StopArrival( public String toString() { return ToStringBuilder .of(StopArrival.class) - .addTime("arrival", arrival) - .addTime("departure", departure) + .addObj("arrival", arrival) + .addObj("departure", departure) .addObj("place", place) .toString(); } diff --git a/src/main/java/org/opentripplanner/model/plan/StopArrivalMapper.java b/src/main/java/org/opentripplanner/model/plan/StopArrivalMapper.java new file mode 100644 index 00000000000..25bab2a7c3f --- /dev/null +++ b/src/main/java/org/opentripplanner/model/plan/StopArrivalMapper.java @@ -0,0 +1,53 @@ +package org.opentripplanner.model.plan; + +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.Objects; +import org.opentripplanner.framework.time.ServiceDateUtils; +import org.opentripplanner.transit.model.site.StopLocation; +import org.opentripplanner.transit.model.timetable.TripTimes; + +/** + * Maps leg-related information to an instance of {@link StopArrival}. + */ +class StopArrivalMapper { + + private final ZoneId zoneId; + private final LocalDate serviceDate; + private final TripTimes tripTimes; + + public StopArrivalMapper(ZoneId zoneId, LocalDate serviceDate, TripTimes tripTimes) { + this.zoneId = Objects.requireNonNull(zoneId); + this.serviceDate = Objects.requireNonNull(serviceDate); + this.tripTimes = Objects.requireNonNull(tripTimes); + } + + StopArrival map(int i, StopLocation stop, boolean realTime) { + final var arrivalTime = ServiceDateUtils.toZonedDateTime( + serviceDate, + zoneId, + tripTimes.getArrivalTime(i) + ); + final var departureTime = ServiceDateUtils.toZonedDateTime( + serviceDate, + zoneId, + tripTimes.getDepartureTime(i) + ); + + var arrival = LegTime.ofStatic(arrivalTime); + var departure = LegTime.ofStatic(departureTime); + + if (realTime) { + arrival = LegTime.of(arrivalTime, tripTimes.getArrivalDelay(i)); + departure = LegTime.of(departureTime, tripTimes.getDepartureDelay(i)); + } + + return new StopArrival( + Place.forStop(stop), + arrival, + departure, + i, + tripTimes.gtfsSequenceOfStopIndex(i) + ); + } +} diff --git a/src/main/java/org/opentripplanner/model/plan/StreetLeg.java b/src/main/java/org/opentripplanner/model/plan/StreetLeg.java index 319c9e01f69..6384159a4ec 100644 --- a/src/main/java/org/opentripplanner/model/plan/StreetLeg.java +++ b/src/main/java/org/opentripplanner/model/plan/StreetLeg.java @@ -156,6 +156,16 @@ public boolean hasSameMode(Leg other) { return other instanceof StreetLeg oSL && mode.equals(oSL.mode); } + @Override + public LegTime start() { + return LegTime.ofStatic(startTime); + } + + @Override + public LegTime end() { + return LegTime.ofStatic(endTime); + } + @Override public Leg withTimeShift(Duration duration) { return StreetLegBuilder diff --git a/src/main/java/org/opentripplanner/model/plan/UnknownTransitPathLeg.java b/src/main/java/org/opentripplanner/model/plan/UnknownTransitPathLeg.java index 382a09d34e2..df43bbcb411 100644 --- a/src/main/java/org/opentripplanner/model/plan/UnknownTransitPathLeg.java +++ b/src/main/java/org/opentripplanner/model/plan/UnknownTransitPathLeg.java @@ -68,6 +68,16 @@ public boolean hasSameMode(Leg other) { return false; } + @Override + public LegTime start() { + return LegTime.ofStatic(startTime); + } + + @Override + public LegTime end() { + return LegTime.ofStatic(endTime); + } + @Override public double getDistanceMeters() { return UNKNOWN; diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java index d070a804f9c..66b7227584d 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java @@ -194,7 +194,7 @@ private List filterOnTimePenaltyLimitIfExist(List e.timePenalty() > iterationTimePenaltyLimit).toList(); } return list; diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/AlertToLegMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/AlertToLegMapper.java index dd08ab6095d..94630be7600 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/AlertToLegMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/AlertToLegMapper.java @@ -91,8 +91,8 @@ public void addTransitAlertsToLeg(Leg leg, boolean isFirstLeg) { ) ); - ZonedDateTime stopArrival = visit.arrival; - ZonedDateTime stopDeparture = visit.departure; + ZonedDateTime stopArrival = visit.arrival.scheduledTime(); + ZonedDateTime stopDeparture = visit.departure.scheduledTime(); addTransitAlertsToLeg(leg, alerts, stopArrival, stopDeparture); } diff --git a/src/main/java/org/opentripplanner/service/worldenvelope/model/WorldEnvelope.java b/src/main/java/org/opentripplanner/service/worldenvelope/model/WorldEnvelope.java index 8d211fd33db..27a1fb4d30f 100644 --- a/src/main/java/org/opentripplanner/service/worldenvelope/model/WorldEnvelope.java +++ b/src/main/java/org/opentripplanner/service/worldenvelope/model/WorldEnvelope.java @@ -6,8 +6,7 @@ import org.opentripplanner.framework.tostring.ToStringBuilder; /** - * This class calculates borders of envelopes that can be also on 180th meridian The same way as it - * was previously calculated in GraphMetadata constructor + * This class calculates borders of envelopes that can be also on 180th meridian. */ public class WorldEnvelope implements Serializable { @@ -53,14 +52,6 @@ public WgsCoordinate upperRight() { return upperRight; } - /** - * This is the center of the Envelope including both street vertexes and transit stops - * if they exist. - */ - public WgsCoordinate meanCenter() { - return meanCenter; - } - /** * If transit data exist, then this is the median center of the transit stops. The median * is computed independently for the longitude and latitude. @@ -68,13 +59,21 @@ public WgsCoordinate meanCenter() { * If not transit data exist this return `empty`. */ public WgsCoordinate center() { - return transitMedianCenter().orElse(meanCenter); + return medianCenter().orElse(meanCenter); + } + + /** + * This is the center of the Envelope including both street vertexes and transit stops + * if they exist. + */ + public WgsCoordinate meanCenter() { + return meanCenter; } /** * Return the transit median center [if it exist] or the mean center. */ - public Optional transitMedianCenter() { + public Optional medianCenter() { return Optional.ofNullable(transitMedianCenter); } diff --git a/src/main/java/org/opentripplanner/service/worldenvelope/model/WorldEnvelopeBuilder.java b/src/main/java/org/opentripplanner/service/worldenvelope/model/WorldEnvelopeBuilder.java index abddba9c5fd..7c5bf13d5e5 100644 --- a/src/main/java/org/opentripplanner/service/worldenvelope/model/WorldEnvelopeBuilder.java +++ b/src/main/java/org/opentripplanner/service/worldenvelope/model/WorldEnvelopeBuilder.java @@ -58,8 +58,23 @@ public WorldEnvelopeBuilder expandToIncludeTransitEntities( var medianCalculator = new MedianCalcForDoubles(collection.size()); - collection.forEach(v -> medianCalculator.add(lonProvider.apply(v))); - double lon = medianCalculator.median(); + double lon = 0.0; + if (includeLongitude180()) { + collection.forEach(v -> { + double c = lonProvider.apply(v); + if (c < 0) { + c += 360.0; + } + medianCalculator.add(c); + }); + lon = medianCalculator.median(); + if (lon > 180.0) { + lon -= 180; + } + } else { + collection.forEach(v -> medianCalculator.add(lonProvider.apply(v))); + lon = medianCalculator.median(); + } medianCalculator.reset(); collection.forEach(v -> medianCalculator.add(latProvider.apply(v))); @@ -79,19 +94,26 @@ public WorldEnvelope build() { if (minLonEast == MIN_NOT_SET) { return new WorldEnvelope(minLat, minLonWest, maxLat, maxLonWest, transitMedianCenter); } - // Envelope intersects with either 0º or 180º - double dist0 = minLonEast - minLonWest; - double dist180 = 360d - maxLonEast + minLonWest; - - // A small gap between the east and west longitude at 0 degrees implies that the Envelope - // should include the 0 degrees longitude(meridian), and be split at 180 degrees. - if (dist0 < dist180) { - return new WorldEnvelope(minLat, maxLonWest, maxLat, maxLonEast, transitMedianCenter); - } else { + if (includeLongitude180()) { return new WorldEnvelope(minLat, minLonEast, maxLat, minLonWest, transitMedianCenter); + } else { + return new WorldEnvelope(minLat, minLonWest, maxLat, maxLonEast, transitMedianCenter); } } + /** + * A small gap between the east and west longitude at 180º degrees implies that the Envelope + * should include the 180º longitude, and be split at 0 degrees. + */ + boolean includeLongitude180() { + if (minLonWest == MIN_NOT_SET || minLonEast == MIN_NOT_SET) { + return false; + } + double dist0 = minLonEast - minLonWest; + double dist180 = 360d - maxLonEast + minLonWest; + return dist180 < dist0; + } + private WorldEnvelopeBuilder expandToInclude(double latitude, double longitude) { minLat = Math.min(minLat, latitude); maxLat = Math.max(maxLat, latitude); diff --git a/src/main/java/org/opentripplanner/transit/model/framework/FeedScopedId.java b/src/main/java/org/opentripplanner/transit/model/framework/FeedScopedId.java index d67ccc3d0e1..b48ff040c28 100644 --- a/src/main/java/org/opentripplanner/transit/model/framework/FeedScopedId.java +++ b/src/main/java/org/opentripplanner/transit/model/framework/FeedScopedId.java @@ -5,9 +5,9 @@ import java.io.Serializable; import java.util.Arrays; import java.util.List; -import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import org.opentripplanner.framework.lang.StringUtils; public final class FeedScopedId implements Serializable, Comparable { @@ -59,7 +59,17 @@ public static FeedScopedId parse(String value) throws IllegalArgumentException { * Parses a string consisting of concatenated FeedScopedIds to a List */ public static List parseList(String s) { - return Arrays.stream(s.split(",")).map(FeedScopedId::parse).collect(Collectors.toList()); + if (StringUtils.containsInvisibleCharacters(s)) { + throw new IllegalArgumentException( + "The input string '%s' contains invisible characters which is not allowed.".formatted(s) + ); + } + return Arrays + .stream(s.split(",")) + .map(String::strip) + .filter(i -> !i.isBlank()) + .map(FeedScopedId::parse) + .toList(); } public static boolean isValidString(String value) throws IllegalArgumentException { diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index daeb57d655e..c1189f21ba1 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1497,15 +1497,11 @@ type DefaultFareProduct implements FareProduct { } type Itinerary { - """ - Time when the user leaves from the origin. Format: Unix timestamp in milliseconds. - """ - startTime: Long + "Time when the user leaves from the origin." + start: OffsetDateTime - """ - Time when the user arrives to the destination.. Format: Unix timestamp in milliseconds. - """ - endTime: Long + "Time when the user leaves arrives at the destination." + end: OffsetDateTime """Duration of the trip on this itinerary, in seconds.""" duration: Long @@ -1584,6 +1580,16 @@ type Itinerary { and always returns an empty list. Use the leg's `fareProducts` instead. """ fares: [fare] @deprecated(reason: "Use the leg's `fareProducts`.") + + """ + Time when the user leaves from the origin. Format: Unix timestamp in milliseconds. + """ + startTime: Long @deprecated(reason: "Use `start` instead which includes timezone information.") + + """ + Time when the user arrives to the destination. Format: Unix timestamp in milliseconds. + """ + endTime: Long @deprecated(reason: "Use `end` instead which includes timezone information.") } "A currency" @@ -1616,10 +1622,12 @@ type Money { amount: Float! } -"""" +""" An ISO-8601-formatted duration, i.e. `PT2H30M` for 2 hours and 30 minutes. + +Negative durations are formatted like `-PT10M`. """ -scalar Duration +scalar Duration @specifiedBy(url:"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/time/Duration.html#parse(java.lang.CharSequence)") type RideHailingProvider { "The ID of the ride hailing provider." @@ -1640,30 +1648,46 @@ type RideHailingEstimate { productName: String } -type Leg { - """ - The date and time when this leg begins. Format: Unix timestamp in milliseconds. - """ - startTime: Long +""" +An ISO-8601-formatted datetime with offset, i.e. `2023-06-13T14:30+03:00` for 2:30pm on June 13th 2023 at Helsinki's offset from UTC at that time. + +ISO-8601 allows many different formats but OTP will only return the profile specified in RFC3339. +""" +scalar OffsetDateTime @specifiedBy(url: "https://www.rfcreader.com/#rfc3339") +"Real-time estimates for a vehicle at a certain place." +type RealTimeEstimate { + time: OffsetDateTime! """ - The date and time when this leg ends. Format: Unix timestamp in milliseconds. + The delay or "earliness" of the vehicle at a certain place. + + If the vehicle is early then this is a negative duration. """ - endTime: Long + delay: Duration! +} + +""" +Time information about a passenger at a certain place. May contain real-time information if +available. +""" +type LegTime { + "The scheduled time of the event." + scheduledTime: OffsetDateTime! + "The estimated time of the event. If no real-time information is available, this is null," + estimated: RealTimeEstimate +} + +type Leg { """ - For transit leg, the offset from the scheduled departure time of the boarding - stop in this leg, i.e. scheduled time of departure at boarding stop = - `startTime - departureDelay` + The time when the leg starts including real-time information, if available. """ - departureDelay: Int + start: LegTime! """ - For transit leg, the offset from the scheduled arrival time of the alighting - stop in this leg, i.e. scheduled time of arrival at alighting stop = `endTime - - arrivalDelay` + The time when the leg ends including real-time information, if available. """ - arrivalDelay: Int + end: LegTime! """The mode (e.g. `WALK`) used when traversing this leg.""" mode: Mode @@ -1825,6 +1849,31 @@ type Leg { that applies to multiple legs can appear several times. """ fareProducts: [FareProductUse] + + """ + The date and time when this leg begins. Format: Unix timestamp in milliseconds. + """ + startTime: Long @deprecated(reason: "Use `start.estimated.time` instead which contains timezone information.") + + """ + The date and time when this leg ends. Format: Unix timestamp in milliseconds. + """ + endTime: Long @deprecated(reason: "Use `end.estimated.time` instead which contains timezone information.") + + """ + For transit leg, the offset from the scheduled departure time of the boarding + stop in this leg, i.e. scheduled time of departure at boarding stop = + `startTime - departureDelay` + """ + departureDelay: Int @deprecated(reason: "Use `end.estimated.delay` instead.") + + """ + For transit leg, the offset from the scheduled arrival time of the alighting + stop in this leg, i.e. scheduled time of arrival at alighting stop = `endTime + - arrivalDelay` + """ + arrivalDelay: Int @deprecated(reason: "Use `start.estimated.delay` instead.") + } """A span of time.""" @@ -2260,14 +2309,16 @@ type Place { lon: Float! """ - The time the rider will arrive at the place. Format: Unix timestamp in milliseconds. + The time the rider will arrive at the place. This also includes real-time information + if available. """ - arrivalTime: Long! + arrival: LegTime """ - The time the rider will depart the place. Format: Unix timestamp in milliseconds. + The time the rider will depart the place. This also includes real-time information + if available. """ - departureTime: Long! + departure: LegTime """The stop related to the place.""" stop: Stop @@ -2305,6 +2356,16 @@ type Place { """The car parking related to the place""" carPark: CarPark @deprecated(reason: "carPark is deprecated. Use vehicleParking instead.") + + """ + The time the rider will arrive at the place. Format: Unix timestamp in milliseconds. + """ + arrivalTime: Long! @deprecated(reason: "Use `arrival` which includes timezone information.") + + """ + The time the rider will depart the place. Format: Unix timestamp in milliseconds. + """ + departureTime: Long! @deprecated(reason: "Use `departure` which includes timezone information.") } type placeAtDistance implements Node { diff --git a/src/test/java/org/opentripplanner/apis/gtfs/DurationScalarTest.java b/src/test/java/org/opentripplanner/apis/gtfs/DurationScalarTest.java new file mode 100644 index 00000000000..28d1ad08376 --- /dev/null +++ b/src/test/java/org/opentripplanner/apis/gtfs/DurationScalarTest.java @@ -0,0 +1,41 @@ +package org.opentripplanner.apis.gtfs; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.params.provider.Arguments.of; + +import graphql.schema.CoercingSerializeException; +import java.time.Duration; +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class DurationScalarTest { + + static List durationCases() { + return List.of( + of(Duration.ofMinutes(30), "PT30M"), + of(Duration.ofHours(23), "PT23H"), + of(Duration.ofMinutes(-10), "-PT10M"), + of(Duration.ofMinutes(-90), "-PT1H30M"), + of(Duration.ofMinutes(-184), "-PT3H4M") + ); + } + + @ParameterizedTest + @MethodSource("durationCases") + void duration(Duration duration, String expected) { + var string = GraphQLScalars.DURATION_SCALAR.getCoercing().serialize(duration); + assertEquals(expected, string); + } + + @Test + void nonDuration() { + Assertions.assertThrows( + CoercingSerializeException.class, + () -> GraphQLScalars.DURATION_SCALAR.getCoercing().serialize(new Object()) + ); + } +} diff --git a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLScalarsTest.java b/src/test/java/org/opentripplanner/apis/gtfs/GeoJsonScalarTest.java similarity index 59% rename from src/test/java/org/opentripplanner/apis/gtfs/GraphQLScalarsTest.java rename to src/test/java/org/opentripplanner/apis/gtfs/GeoJsonScalarTest.java index 90b35a31b9f..cc88e22d074 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLScalarsTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/GeoJsonScalarTest.java @@ -3,29 +3,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import com.fasterxml.jackson.core.JsonProcessingException; -import graphql.schema.CoercingSerializeException; -import java.time.Duration; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.GeometryFactory; import org.opentripplanner.framework.json.ObjectMappers; -class GraphQLScalarsTest { - - @Test - void duration() { - var string = GraphQLScalars.durationScalar.getCoercing().serialize(Duration.ofMinutes(30)); - assertEquals("PT30M", string); - } - - @Test - void nonDuration() { - Assertions.assertThrows( - CoercingSerializeException.class, - () -> GraphQLScalars.durationScalar.getCoercing().serialize(new Object()) - ); - } +class GeoJsonScalarTest { @Test void geoJson() throws JsonProcessingException { @@ -38,7 +21,7 @@ void geoJson() throws JsonProcessingException { new Coordinate(0, 0), } ); - var geoJson = GraphQLScalars.geoJsonScalar.getCoercing().serialize(polygon); + var geoJson = GraphQLScalars.GEOJSON_SCALAR.getCoercing().serialize(polygon); var jsonNode = ObjectMappers .ignoringExtraFields() diff --git a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java index d89afea6f9d..f630e0cee3c 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java @@ -61,7 +61,6 @@ import org.opentripplanner.routing.alertpatch.TimePeriod; import org.opentripplanner.routing.alertpatch.TransitAlert; import org.opentripplanner.routing.api.request.RouteRequest; -import org.opentripplanner.routing.core.FareType; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.graphfinder.GraphFinder; import org.opentripplanner.routing.graphfinder.NearbyStop; @@ -86,6 +85,7 @@ import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.site.StopLocation; +import org.opentripplanner.transit.model.timetable.RealTimeTripTimes; import org.opentripplanner.transit.model.timetable.TripTimesFactory; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.TransitModel; @@ -115,6 +115,7 @@ class GraphQLIntegrationTest { .parse("2023-02-15T12:03:28+01:00") .toInstant(); static final Instant ALERT_END_TIME = ALERT_START_TIME.plus(1, ChronoUnit.DAYS); + private static final int TEN_MINUTES = 10 * 60; private static GraphQLRequestContext context; @@ -179,6 +180,8 @@ static void setup() { .carHail(D10m, E) .build(); + add10MinuteDelay(i1); + var busLeg = i1.getTransitLeg(1); var railLeg = (ScheduledTransitLeg) i1.getTransitLeg(2); @@ -280,6 +283,20 @@ public TransitAlertService getTransitAlertService() { ); } + private static void add10MinuteDelay(Itinerary i1) { + i1.transformTransitLegs(tl -> { + if (tl instanceof ScheduledTransitLeg stl) { + var rtt = (RealTimeTripTimes) stl.getTripTimes(); + + for (var i = 0; i < rtt.getNumStops(); i++) { + rtt.updateArrivalTime(i, rtt.getArrivalTime(i) + TEN_MINUTES); + rtt.updateDepartureTime(i, rtt.getDepartureTime(i) + TEN_MINUTES); + } + } + return tl; + }); + } + @FilePatternSource(pattern = "src/test/resources/org/opentripplanner/apis/gtfs/queries/*.graphql") @ParameterizedTest(name = "Check GraphQL query in {0}") void graphQL(Path path) throws IOException { diff --git a/src/test/java/org/opentripplanner/apis/gtfs/OffsetDateTimeScalarTest.java b/src/test/java/org/opentripplanner/apis/gtfs/OffsetDateTimeScalarTest.java new file mode 100644 index 00000000000..45c8e9de837 --- /dev/null +++ b/src/test/java/org/opentripplanner/apis/gtfs/OffsetDateTimeScalarTest.java @@ -0,0 +1,62 @@ +package org.opentripplanner.apis.gtfs; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.params.provider.Arguments.of; + +import graphql.language.StringValue; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.List; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.opentripplanner._support.time.ZoneIds; + +public class OffsetDateTimeScalarTest { + + static final OffsetDateTime OFFSET_DATE_TIME = OffsetDateTime.of( + LocalDate.of(2024, 2, 4), + LocalTime.MIDNIGHT, + ZoneOffset.UTC + ); + + static List offsetDateTimeCases() { + return List.of( + of(OFFSET_DATE_TIME, "2024-02-04T00:00:00Z"), + of(OFFSET_DATE_TIME.plusHours(12).plusMinutes(8).plusSeconds(22), "2024-02-04T12:08:22Z"), + of( + OFFSET_DATE_TIME.atZoneSameInstant(ZoneIds.BERLIN).toOffsetDateTime(), + "2024-02-04T01:00:00+01:00" + ), + of( + OFFSET_DATE_TIME.atZoneSameInstant(ZoneIds.NEW_YORK).toOffsetDateTime(), + "2024-02-03T19:00:00-05:00" + ) + ); + } + + @ParameterizedTest + @MethodSource("offsetDateTimeCases") + void serializeOffsetDateTime(OffsetDateTime odt, String expected) { + var string = GraphQLScalars.OFFSET_DATETIME_SCALAR.getCoercing().serialize(odt); + assertEquals(expected, string); + } + + @ParameterizedTest + @MethodSource("offsetDateTimeCases") + void parseOffsetDateTime(OffsetDateTime expected, String input) { + var odt = GraphQLScalars.OFFSET_DATETIME_SCALAR.getCoercing().parseValue(input); + assertEquals(expected, odt); + } + + @ParameterizedTest + @MethodSource("offsetDateTimeCases") + void parseOffsetDateTimeLiteral(OffsetDateTime expected, String input) { + var odt = GraphQLScalars.OFFSET_DATETIME_SCALAR + .getCoercing() + .parseLiteral(new StringValue(input)); + assertEquals(expected, odt); + } +} diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java index ecba3320838..22a8583d705 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner._support.time.ZoneIds; import org.opentripplanner.apis.gtfs.GraphQLRequestContext; import org.opentripplanner.apis.gtfs.TestRoutingService; @@ -34,7 +35,6 @@ import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; import org.opentripplanner.service.vehiclerental.internal.DefaultVehicleRentalService; import org.opentripplanner.street.search.TraverseMode; -import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.TransitModel; @@ -91,25 +91,30 @@ void parkingFilters() { testParkingFilters(routeRequest.preferences().parking(TraverseMode.BICYCLE)); } - static Stream banningCases = Stream.of( - of(Map.of(), "[TransitFilterRequest{}]"), - of( - Map.of("routes", "trimet:555"), - "[TransitFilterRequest{not: [SelectRequest{transportModes: [], routes: [trimet:555]}]}]" - ), - of(Map.of("agencies", ""), "[TransitFilterRequest{not: [SelectRequest{transportModes: []}]}]"), - of( - Map.of("agencies", "trimet:666"), - "[TransitFilterRequest{not: [SelectRequest{transportModes: [], agencies: [trimet:666]}]}]" - ), - of( - Map.of("agencies", "trimet:666", "routes", "trimet:444"), - "[TransitFilterRequest{not: [SelectRequest{transportModes: [], routes: [trimet:444]}, SelectRequest{transportModes: [], agencies: [trimet:666]}]}]" - ) - ); + static Stream banningCases() { + return Stream.of( + of(Map.of(), "[TransitFilterRequest{}]"), + of( + Map.of("routes", "trimet:555"), + "[TransitFilterRequest{not: [SelectRequest{transportModes: [], routes: [trimet:555]}]}]" + ), + of( + Map.of("agencies", ""), + "[TransitFilterRequest{not: [SelectRequest{transportModes: []}]}]" + ), + of( + Map.of("agencies", "trimet:666"), + "[TransitFilterRequest{not: [SelectRequest{transportModes: [], agencies: [trimet:666]}]}]" + ), + of( + Map.of("agencies", "trimet:666", "routes", "trimet:444"), + "[TransitFilterRequest{not: [SelectRequest{transportModes: [], routes: [trimet:444]}, SelectRequest{transportModes: [], agencies: [trimet:666]}]}]" + ) + ); + } @ParameterizedTest - @VariableSource("banningCases") + @MethodSource("banningCases") void banning(Map banned, String expectedFilters) { Map arguments = Map.of("banned", banned); @@ -119,21 +124,23 @@ void banning(Map banned, String expectedFilters) { assertEquals(expectedFilters, routeRequest.journey().transit().filters().toString()); } - static Stream transportModesCases = Stream.of( - of(List.of(), "[ExcludeAllTransitFilter{}]"), - of(List.of(mode("BICYCLE")), "[ExcludeAllTransitFilter{}]"), - of( - List.of(mode("BUS")), - "[TransitFilterRequest{select: [SelectRequest{transportModes: [BUS, COACH]}]}]" - ), - of( - List.of(mode("BUS"), mode("MONORAIL")), - "[TransitFilterRequest{select: [SelectRequest{transportModes: [BUS, COACH, MONORAIL]}]}]" - ) - ); + static Stream transportModesCases() { + return Stream.of( + of(List.of(), "[ExcludeAllTransitFilter{}]"), + of(List.of(mode("BICYCLE")), "[ExcludeAllTransitFilter{}]"), + of( + List.of(mode("BUS")), + "[TransitFilterRequest{select: [SelectRequest{transportModes: [BUS, COACH]}]}]" + ), + of( + List.of(mode("BUS"), mode("MONORAIL")), + "[TransitFilterRequest{select: [SelectRequest{transportModes: [BUS, COACH, MONORAIL]}]}]" + ) + ); + } @ParameterizedTest - @VariableSource("transportModesCases") + @MethodSource("transportModesCases") void modes(List> modes, String expectedFilters) { Map arguments = Map.of("transportModes", modes); @@ -172,13 +179,15 @@ void bikeTriangle() { ); } - static Stream noTriangleCases = Arrays - .stream(GraphQLTypes.GraphQLOptimizeType.values()) - .filter(value -> value != GraphQLTypes.GraphQLOptimizeType.TRIANGLE) - .map(Arguments::of); + static Stream noTriangleCases() { + return Arrays + .stream(GraphQLTypes.GraphQLOptimizeType.values()) + .filter(value -> value != GraphQLTypes.GraphQLOptimizeType.TRIANGLE) + .map(Arguments::of); + } @ParameterizedTest - @VariableSource("noTriangleCases") + @MethodSource("noTriangleCases") void noTriangle(GraphQLTypes.GraphQLOptimizeType bot) { Map arguments = Map.of( "optimize", diff --git a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java index c0bd96b6310..e35f198d16e 100644 --- a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner._support.time.ZoneIds; import org.opentripplanner.apis.transmodel.TransmodelRequestContext; import org.opentripplanner.ext.emissions.DefaultEmissionsService; @@ -50,7 +51,6 @@ import org.opentripplanner.standalone.server.DefaultServerRequestContext; import org.opentripplanner.street.model.StreetLimitationParameters; import org.opentripplanner.street.service.DefaultStreetLimitationParametersService; -import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.network.Route; @@ -279,13 +279,12 @@ void testDefaultTriangleFactors() { assertEquals(TimeSlopeSafetyTriangle.DEFAULT, req2.preferences().bike().optimizeTriangle()); } - static Stream noTriangleCases = VehicleRoutingOptimizeType - .nonTriangleValues() - .stream() - .map(Arguments::of); + static Stream noTriangleCases() { + return VehicleRoutingOptimizeType.nonTriangleValues().stream().map(Arguments::of); + } @ParameterizedTest - @VariableSource("noTriangleCases") + @MethodSource("noTriangleCases") public void testBikeTriangleFactorsHasNoEffect(VehicleRoutingOptimizeType bot) { Map arguments = Map.of( "bicycleOptimisationMethod", diff --git a/src/test/java/org/opentripplanner/apis/transmodel/model/scalars/DateTimeScalarFactoryTest.java b/src/test/java/org/opentripplanner/apis/transmodel/model/scalars/DateTimeScalarFactoryTest.java index 43b44cbd5a6..c46c232751f 100644 --- a/src/test/java/org/opentripplanner/apis/transmodel/model/scalars/DateTimeScalarFactoryTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/model/scalars/DateTimeScalarFactoryTest.java @@ -13,7 +13,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; -import org.opentripplanner.test.support.VariableSource; +import org.junit.jupiter.params.provider.MethodSource; class DateTimeScalarFactoryTest { @@ -32,17 +32,19 @@ void serialize() { assertEquals(DATE_TIME, result); } - static Stream testCases = Stream.of( - Arguments.of(DATE_TIME), - Arguments.of("2023-01-27T12:59:00.000+01:00"), - Arguments.of("2023-01-27T12:59:00+0100"), - Arguments.of("2023-01-27T12:59:00+01"), - Arguments.of("2023-01-27T12:59:00"), - Arguments.of("2023-01-27T11:59:00Z") - ); + static Stream testCases() { + return Stream.of( + Arguments.of(DATE_TIME), + Arguments.of("2023-01-27T12:59:00.000+01:00"), + Arguments.of("2023-01-27T12:59:00+0100"), + Arguments.of("2023-01-27T12:59:00+01"), + Arguments.of("2023-01-27T12:59:00"), + Arguments.of("2023-01-27T11:59:00Z") + ); + } @ParameterizedTest - @VariableSource("testCases") + @MethodSource("testCases") void parse(String input) { var result = subject.getCoercing().parseValue(input); assertEquals(EPOCH_MILLIS, result); diff --git a/src/test/java/org/opentripplanner/framework/lang/StringUtilsTest.java b/src/test/java/org/opentripplanner/framework/lang/StringUtilsTest.java index 8998d82a5f2..0e67344b2bb 100644 --- a/src/test/java/org/opentripplanner/framework/lang/StringUtilsTest.java +++ b/src/test/java/org/opentripplanner/framework/lang/StringUtilsTest.java @@ -3,6 +3,7 @@ import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -10,20 +11,23 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; -import org.opentripplanner.test.support.VariableSource; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; class StringUtilsTest { - static final Stream hasValueTestCases = Stream.of( - Arguments.of("Text", TRUE), - Arguments.of("T", TRUE), - Arguments.of(null, FALSE), - Arguments.of("", FALSE), - Arguments.of("\t\n", FALSE) - ); + static Stream hasValueTestCases() { + return Stream.of( + Arguments.of("Text", TRUE), + Arguments.of("T", TRUE), + Arguments.of(null, FALSE), + Arguments.of("", FALSE), + Arguments.of("\t\n", FALSE) + ); + } @ParameterizedTest - @VariableSource("hasValueTestCases") + @MethodSource("hasValueTestCases") void hasValue(String input, Boolean hasValue) { assertEquals(hasValue, StringUtils.hasValue(input)); assertEquals(!hasValue, StringUtils.hasNoValue(input)); @@ -82,4 +86,18 @@ void padRight() { void quoteReplace() { assertEquals("\"key\" : \"value\"", StringUtils.quoteReplace("'key' : 'value'")); } + + @ParameterizedTest + @ValueSource( + strings = { "\u200B", "\n", "\t", "\thello", "f\noo", "\ntri\nmet:123\t", "tri\u200Bmet:123" } + ) + void containsInvisibleChars(String input) { + assertTrue(StringUtils.containsInvisibleCharacters(input)); + } + + @ParameterizedTest + @ValueSource(strings = { "", " ", "hello", " hello", " fo o " }) + void noInvisibleChars(String input) { + assertFalse(StringUtils.containsInvisibleCharacters(input)); + } } diff --git a/src/test/java/org/opentripplanner/framework/time/DurationUtilsTest.java b/src/test/java/org/opentripplanner/framework/time/DurationUtilsTest.java index 86953760485..2457aa14b60 100644 --- a/src/test/java/org/opentripplanner/framework/time/DurationUtilsTest.java +++ b/src/test/java/org/opentripplanner/framework/time/DurationUtilsTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.params.provider.Arguments.of; import static org.opentripplanner.framework.time.DurationUtils.requireNonNegative; import static org.opentripplanner.framework.time.DurationUtils.toIntMilliseconds; @@ -13,6 +14,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.ResourceLock; import org.junit.jupiter.api.parallel.Resources; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; public class DurationUtilsTest { @@ -151,6 +155,29 @@ public void msToSecondsStr() { } } + static List durationCases() { + return List.of( + of(Duration.ofSeconds(30), "PT30S"), + of(Duration.ofMinutes(30), "PT30M"), + of(Duration.ofHours(23), "PT23H"), + of(Duration.ofSeconds(-30), "-PT30S"), + of(Duration.ofMinutes(-10), "-PT10M"), + of(Duration.ofMinutes(-90), "-PT1H30M"), + of(Duration.ofMinutes(-184), "-PT3H4M") + ); + } + + @ParameterizedTest + @MethodSource("durationCases") + void formatDuration(Duration duration, String expected) { + var string = DurationUtils.formatDurationWithLeadingMinus(duration); + + assertEquals(expected, string); + + var parsed = Duration.parse(expected); + assertEquals(parsed, duration); + } + private static int durationSec(int hour, int min, int sec) { return 60 * (60 * hour + min) + sec; } diff --git a/src/test/java/org/opentripplanner/framework/time/OffsetDateTimeParserTest.java b/src/test/java/org/opentripplanner/framework/time/OffsetDateTimeParserTest.java new file mode 100644 index 00000000000..d8944b9a5f0 --- /dev/null +++ b/src/test/java/org/opentripplanner/framework/time/OffsetDateTimeParserTest.java @@ -0,0 +1,51 @@ +package org.opentripplanner.framework.time; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.text.ParseException; +import java.time.OffsetDateTime; +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +class OffsetDateTimeParserTest { + + private static final String TIME_STRING = "2023-01-27T12:59:00+01:00"; + private static final OffsetDateTime TIME = OffsetDateTime.parse(TIME_STRING); + + static List successfulCases() { + return List.of( + TIME_STRING, + "2023-01-27T12:59:00.000+01:00", + "2023-01-27T12:59:00+0100", + "2023-01-27T12:59:00+01", + "2023-01-27T11:59:00Z", + "2023-01-27T11:59Z", + "2023-01-27T06:59:00-05:00", + "2023-01-27T06:59:00-0500" + ); + } + + @ParameterizedTest + @MethodSource("successfulCases") + void parse(String input) throws ParseException { + var res = OffsetDateTimeParser.parseLeniently(input); + assertTrue(res.isEqual(TIME)); + } + + static List failedCases() { + return List.of("2023-01-27T11:59:00", "2023-01-27T11", "2023-01-27T11:00"); + } + + @ParameterizedTest + @MethodSource("failedCases") + void failed(String input) { + Assertions.assertThrows( + ParseException.class, + () -> { + OffsetDateTimeParser.parseLeniently(input); + } + ); + } +} diff --git a/src/test/java/org/opentripplanner/graph_builder/module/GtfsFeedIdTest.java b/src/test/java/org/opentripplanner/graph_builder/module/GtfsFeedIdTest.java index 71a1d382983..133ec71070a 100644 --- a/src/test/java/org/opentripplanner/graph_builder/module/GtfsFeedIdTest.java +++ b/src/test/java/org/opentripplanner/graph_builder/module/GtfsFeedIdTest.java @@ -9,16 +9,18 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; -import org.opentripplanner.test.support.VariableSource; +import org.junit.jupiter.params.provider.MethodSource; class GtfsFeedIdTest { private static final String NUMBERS_ONLY_REGEX = "^\\d+$"; - static Stream emptyCases = Stream.of(null, "", " ", "\n", " ").map(Arguments::of); + static Stream emptyCases() { + return Stream.of(null, "", " ", "\n", " ").map(Arguments::of); + } @ParameterizedTest - @VariableSource("emptyCases") + @MethodSource("emptyCases") void autogenerateNumber(String id) { String feedId = feedId(id); assertTrue(feedId.matches(NUMBERS_ONLY_REGEX), "'%s' is not an integer.".formatted(feedId)); diff --git a/src/test/java/org/opentripplanner/graph_builder/module/GtfsModuleTest.java b/src/test/java/org/opentripplanner/graph_builder/module/GtfsModuleTest.java index c628a14ad43..8123661990e 100644 --- a/src/test/java/org/opentripplanner/graph_builder/module/GtfsModuleTest.java +++ b/src/test/java/org/opentripplanner/graph_builder/module/GtfsModuleTest.java @@ -2,6 +2,7 @@ import static graphql.Assert.assertNotNull; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.List; import java.util.stream.Collectors; @@ -23,7 +24,7 @@ class GtfsModuleTest { @Test - public void addShapesForFrequencyTrips() { + void addShapesForFrequencyTrips() { var model = buildTestModel(); var bundle = new GtfsBundle(ConstantsForTests.SIMPLE_GTFS); @@ -44,7 +45,7 @@ public void addShapesForFrequencyTrips() { assertEquals(1, frequencyTripPattern.size()); - var tripPattern = frequencyTripPattern.get(0); + var tripPattern = frequencyTripPattern.getFirst(); assertNotNull(tripPattern.getGeometry()); assertNotNull(tripPattern.getHopGeometry(0)); @@ -53,6 +54,20 @@ public void addShapesForFrequencyTrips() { assertNotNull(pattern.getHopGeometry(0)); } + @Test + void duplicateFeedId() { + var bundles = List.of(bundle("A"), bundle("A")); + var model = buildTestModel(); + + var module = new GtfsModule( + bundles, + model.transitModel, + model.graph, + ServiceDateInterval.unbounded() + ); + assertThrows(IllegalArgumentException.class, module::buildGraph); + } + private static TestModels buildTestModel() { var deduplicator = new Deduplicator(); var stopModel = new StopModel(); @@ -63,15 +78,15 @@ private static TestModels buildTestModel() { record TestModels(Graph graph, TransitModel transitModel) {} + static GtfsBundle bundle(String feedId) { + var b = new GtfsBundle(ResourceLoader.of(GtfsModuleTest.class).file("/gtfs/interlining")); + b.setFeedId(new GtfsFeedId.Builder().id(feedId).build()); + return b; + } + @Nested class Interlining { - static GtfsBundle bundle(String feedId) { - var b = new GtfsBundle(ResourceLoader.of(GtfsModuleTest.class).file("/gtfs/interlining")); - b.setFeedId(new GtfsFeedId.Builder().id(feedId).build()); - return b; - } - static List interliningCases() { return List.of( Arguments.of(List.of(bundle("A")), 2), @@ -86,7 +101,7 @@ static List interliningCases() { */ @ParameterizedTest(name = "Bundles {0} should generate {1} stay-seated transfers") @MethodSource("interliningCases") - public void interline(List bundles, int expectedTransfers) { + void interline(List bundles, int expectedTransfers) { var model = buildTestModel(); var feedIds = bundles.stream().map(GtfsBundle::getFeedId).collect(Collectors.toSet()); diff --git a/src/test/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModuleTest.java b/src/test/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModuleTest.java index 41c77f5df6e..1d8d819b5cb 100644 --- a/src/test/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModuleTest.java +++ b/src/test/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModuleTest.java @@ -9,6 +9,7 @@ import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.framework.i18n.NonLocalizedString; import org.opentripplanner.graph_builder.module.osm.OsmModule; import org.opentripplanner.openstreetmap.OsmProvider; @@ -23,7 +24,6 @@ import org.opentripplanner.street.model.vertex.VertexFactory; import org.opentripplanner.street.model.vertex.VertexLabel; import org.opentripplanner.test.support.ResourceLoader; -import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.framework.Deduplicator; @@ -49,21 +49,23 @@ class OsmBoardingLocationsModuleTest { RegularStop busStop = testModel.stop("de:08115:4512:5:C", 48.59434, 8.86452).build(); RegularStop floatingBusStop = testModel.stop("floating-bus-stop", 48.59417, 8.86464).build(); - static Stream testCases = Stream.of( - Arguments.of( - false, - Stream - .of(302563833L, 3223067049L, 302563836L, 3223067680L, 302563834L, 768590748L, 302563839L) - .map(VertexLabel::osm) - .collect(Collectors.toSet()) - ), - Arguments.of(true, Set.of(VertexLabel.osm(3223067049L), VertexLabel.osm(768590748))) - ); + static Stream testCases() { + return Stream.of( + Arguments.of( + false, + Stream + .of(302563833L, 3223067049L, 302563836L, 3223067680L, 302563834L, 768590748L, 302563839L) + .map(VertexLabel::osm) + .collect(Collectors.toSet()) + ), + Arguments.of(true, Set.of(VertexLabel.osm(3223067049L), VertexLabel.osm(768590748))) + ); + } @ParameterizedTest( name = "add boarding locations and link them to platform edges when skipVisibility={0}" ) - @VariableSource("testCases") + @MethodSource("testCases") void addAndLinkBoardingLocations(boolean areaVisibility, Set linkedVertices) { var deduplicator = new Deduplicator(); var graph = new Graph(deduplicator); diff --git a/src/test/java/org/opentripplanner/gtfs/interlining/InterlineProcessorTest.java b/src/test/java/org/opentripplanner/gtfs/interlining/InterlineProcessorTest.java index 1e931589204..67ca61f0403 100644 --- a/src/test/java/org/opentripplanner/gtfs/interlining/InterlineProcessorTest.java +++ b/src/test/java/org/opentripplanner/gtfs/interlining/InterlineProcessorTest.java @@ -10,12 +10,12 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.gtfs.mapping.StaySeatedNotAllowed; import org.opentripplanner.model.calendar.CalendarServiceData; import org.opentripplanner.model.plan.PlanTestConstants; import org.opentripplanner.model.transfer.DefaultTransferService; -import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.FeedScopedId; @@ -35,63 +35,65 @@ class InterlineProcessorTest implements PlanTestConstants { tripPattern("trip-5", "block-2", "service-4") ); - static Stream interlineTestCases = Stream.of( - Arguments.of( - List.of( - new FeedScopedId("1", "service-1"), - new FeedScopedId("1", "service-2"), - new FeedScopedId("1", "service-3"), - new FeedScopedId("1", "service-4") + static Stream interlineTestCases() { + return Stream.of( + Arguments.of( + List.of( + new FeedScopedId("1", "service-1"), + new FeedScopedId("1", "service-2"), + new FeedScopedId("1", "service-3"), + new FeedScopedId("1", "service-4") + ), + List.of( + List.of(LocalDate.of(2023, Month.JANUARY, 1), LocalDate.of(2023, Month.JANUARY, 5)), + List.of(LocalDate.of(2023, Month.JANUARY, 1)), + List.of(LocalDate.of(2023, Month.JANUARY, 1)), + List.of(LocalDate.of(2023, Month.JANUARY, 1)) + ), + "[ConstrainedTransfer{from: TripTP{F:trip-2, stopPos 2}, to: TripTP{F:trip-3, stopPos 0}, " + + "constraint: {staySeated}}, ConstrainedTransfer{from: TripTP{F:trip-1, stopPos 2}, " + + "to: TripTP{F:trip-2, stopPos 0}, constraint: {staySeated}}, " + + "ConstrainedTransfer{from: TripTP{F:trip-3, stopPos 2}, to: TripTP{F:trip-4, stopPos 0}, constraint: {staySeated}}]" ), - List.of( - List.of(LocalDate.of(2023, Month.JANUARY, 1), LocalDate.of(2023, Month.JANUARY, 5)), - List.of(LocalDate.of(2023, Month.JANUARY, 1)), - List.of(LocalDate.of(2023, Month.JANUARY, 1)), - List.of(LocalDate.of(2023, Month.JANUARY, 1)) + Arguments.of( + List.of( + new FeedScopedId("1", "service-1"), + new FeedScopedId("1", "service-2"), + new FeedScopedId("1", "service-3"), + new FeedScopedId("1", "service-4") + ), + List.of( + List.of(LocalDate.of(2023, Month.JANUARY, 1), LocalDate.of(2023, Month.JANUARY, 5)), + List.of(LocalDate.of(2023, Month.JANUARY, 1)), + List.of(LocalDate.of(2023, Month.JANUARY, 5)), + List.of(LocalDate.of(2023, Month.JANUARY, 1)) + ), + "[ConstrainedTransfer{from: TripTP{F:trip-2, stopPos 2}, to: TripTP{F:trip-3, stopPos 0}, " + + "constraint: {staySeated}}, ConstrainedTransfer{from: TripTP{F:trip-1, stopPos 2}, " + + "to: TripTP{F:trip-2, stopPos 0}, constraint: {staySeated}}, " + + "ConstrainedTransfer{from: TripTP{F:trip-2, stopPos 2}, to: TripTP{F:trip-4, stopPos 0}, constraint: {staySeated}}]" ), - "[ConstrainedTransfer{from: TripTP{F:trip-2, stopPos 2}, to: TripTP{F:trip-3, stopPos 0}, " + - "constraint: {staySeated}}, ConstrainedTransfer{from: TripTP{F:trip-1, stopPos 2}, " + - "to: TripTP{F:trip-2, stopPos 0}, constraint: {staySeated}}, " + - "ConstrainedTransfer{from: TripTP{F:trip-3, stopPos 2}, to: TripTP{F:trip-4, stopPos 0}, constraint: {staySeated}}]" - ), - Arguments.of( - List.of( - new FeedScopedId("1", "service-1"), - new FeedScopedId("1", "service-2"), - new FeedScopedId("1", "service-3"), - new FeedScopedId("1", "service-4") - ), - List.of( - List.of(LocalDate.of(2023, Month.JANUARY, 1), LocalDate.of(2023, Month.JANUARY, 5)), - List.of(LocalDate.of(2023, Month.JANUARY, 1)), - List.of(LocalDate.of(2023, Month.JANUARY, 5)), - List.of(LocalDate.of(2023, Month.JANUARY, 1)) - ), - "[ConstrainedTransfer{from: TripTP{F:trip-2, stopPos 2}, to: TripTP{F:trip-3, stopPos 0}, " + - "constraint: {staySeated}}, ConstrainedTransfer{from: TripTP{F:trip-1, stopPos 2}, " + - "to: TripTP{F:trip-2, stopPos 0}, constraint: {staySeated}}, " + - "ConstrainedTransfer{from: TripTP{F:trip-2, stopPos 2}, to: TripTP{F:trip-4, stopPos 0}, constraint: {staySeated}}]" - ), - // No common days between services - Arguments.of( - List.of( - new FeedScopedId("1", "service-1"), - new FeedScopedId("1", "service-2"), - new FeedScopedId("1", "service-3"), - new FeedScopedId("1", "service-4") - ), - List.of( - List.of(LocalDate.of(2023, Month.JANUARY, 1), LocalDate.of(2023, Month.JANUARY, 5)), - List.of(LocalDate.of(2023, Month.JANUARY, 2)), - List.of(LocalDate.of(2023, Month.JANUARY, 3)), - List.of(LocalDate.of(2023, Month.JANUARY, 1)) - ), - "[ConstrainedTransfer{from: TripTP{F:trip-1, stopPos 2}, to: TripTP{F:trip-2, stopPos 0}, constraint: {staySeated}}]" - ) - ); + // No common days between services + Arguments.of( + List.of( + new FeedScopedId("1", "service-1"), + new FeedScopedId("1", "service-2"), + new FeedScopedId("1", "service-3"), + new FeedScopedId("1", "service-4") + ), + List.of( + List.of(LocalDate.of(2023, Month.JANUARY, 1), LocalDate.of(2023, Month.JANUARY, 5)), + List.of(LocalDate.of(2023, Month.JANUARY, 2)), + List.of(LocalDate.of(2023, Month.JANUARY, 3)), + List.of(LocalDate.of(2023, Month.JANUARY, 1)) + ), + "[ConstrainedTransfer{from: TripTP{F:trip-1, stopPos 2}, to: TripTP{F:trip-2, stopPos 0}, constraint: {staySeated}}]" + ) + ); + } @ParameterizedTest(name = "{0} services with {1} dates should generate transfers: {2}") - @VariableSource("interlineTestCases") + @MethodSource("interlineTestCases") void testInterline( List serviceIds, List> serviceDates, diff --git a/src/test/java/org/opentripplanner/gtfs/mapping/LocationMapperTest.java b/src/test/java/org/opentripplanner/gtfs/mapping/LocationMapperTest.java index e7a291fa976..4461c5a31f1 100644 --- a/src/test/java/org/opentripplanner/gtfs/mapping/LocationMapperTest.java +++ b/src/test/java/org/opentripplanner/gtfs/mapping/LocationMapperTest.java @@ -8,20 +8,19 @@ import org.geojson.Polygon; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.gtfs.model.Location; -import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.service.StopModel; class LocationMapperTest { - static Stream testCases = Stream.of( - Arguments.of(null, true), - Arguments.of("a name", false) - ); + static Stream testCases() { + return Stream.of(Arguments.of(null, true), Arguments.of("a name", false)); + } @ParameterizedTest(name = "a name of <{0}> should set bogusName={1}") - @VariableSource("testCases") + @MethodSource("testCases") void testMapping(String name, boolean isBogusName) { var gtfsLocation = new Location(); gtfsLocation.setId(new AgencyAndId("1", "zone-3")); diff --git a/src/test/java/org/opentripplanner/gtfs/mapping/TransitModeMapperTest.java b/src/test/java/org/opentripplanner/gtfs/mapping/TransitModeMapperTest.java index 39973a769c0..b2e5b1a8a4d 100644 --- a/src/test/java/org/opentripplanner/gtfs/mapping/TransitModeMapperTest.java +++ b/src/test/java/org/opentripplanner/gtfs/mapping/TransitModeMapperTest.java @@ -7,23 +7,25 @@ import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; -import org.opentripplanner.test.support.VariableSource; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.transit.model.basic.TransitMode; class TransitModeMapperTest { - static Stream testCases = Stream.of( - Arguments.of(1500, TAXI), - Arguments.of(1510, TAXI), - Arguments.of(1551, CARPOOL), - Arguments.of(1555, CARPOOL), - Arguments.of(1560, CARPOOL), - Arguments.of(1561, TAXI), - Arguments.of(1580, TAXI) - ); + static Stream testCases() { + return Stream.of( + Arguments.of(1500, TAXI), + Arguments.of(1510, TAXI), + Arguments.of(1551, CARPOOL), + Arguments.of(1555, CARPOOL), + Arguments.of(1560, CARPOOL), + Arguments.of(1561, TAXI), + Arguments.of(1580, TAXI) + ); + } @ParameterizedTest(name = "{0} should map to {1}") - @VariableSource("testCases") + @MethodSource("testCases") void map(int mode, TransitMode expectedMode) { assertEquals(expectedMode, TransitModeMapper.mapMode(mode)); } diff --git a/src/test/java/org/opentripplanner/model/plan/PlaceTest.java b/src/test/java/org/opentripplanner/model/plan/PlaceTest.java index 573e4597e39..95d7cf629a2 100644 --- a/src/test/java/org/opentripplanner/model/plan/PlaceTest.java +++ b/src/test/java/org/opentripplanner/model/plan/PlaceTest.java @@ -9,13 +9,13 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.opentripplanner.framework.geometry.GeometryUtils; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.framework.i18n.NonLocalizedString; import org.opentripplanner.street.model.vertex.SimpleVertex; -import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.RegularStop; @@ -65,17 +65,19 @@ public void sameLocationBasedOnStopId() { assertFalse(otherPlace.sameLocation(aPlace), "other place(symmetric)"); } - static Stream flexStopCases = Stream.of( - Arguments.of(null, "an intersection name"), - Arguments.of(new NonLocalizedString("1:stop_id"), "an intersection name (part of 1:stop_id)"), - Arguments.of( - new NonLocalizedString("Flex Zone 123"), - "an intersection name (part of Flex Zone 123)" - ) - ); + static Stream flexStopCases() { + return Stream.of( + Arguments.of(null, "an intersection name"), + Arguments.of(new NonLocalizedString("1:stop_id"), "an intersection name (part of 1:stop_id)"), + Arguments.of( + new NonLocalizedString("Flex Zone 123"), + "an intersection name (part of Flex Zone 123)" + ) + ); + } @ParameterizedTest(name = "Flex stop name of {0} should lead to a place name of {1}") - @VariableSource("flexStopCases") + @MethodSource("flexStopCases") public void flexStop(I18NString stopName, String expectedPlaceName) { var stop = StopModel .of() diff --git a/src/test/java/org/opentripplanner/model/plan/RelativeDirectionTest.java b/src/test/java/org/opentripplanner/model/plan/RelativeDirectionTest.java index c60861d28f8..c3cb4e0a78c 100644 --- a/src/test/java/org/opentripplanner/model/plan/RelativeDirectionTest.java +++ b/src/test/java/org/opentripplanner/model/plan/RelativeDirectionTest.java @@ -15,37 +15,39 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; -import org.opentripplanner.test.support.VariableSource; +import org.junit.jupiter.params.provider.MethodSource; class RelativeDirectionTest { @SuppressWarnings("unused") - static Stream testCasesNormal = Stream.of( - // Turn Right - tc(0, CONTINUE), - tc(17, CONTINUE), - tc(18, SLIGHTLY_RIGHT), - tc(40, SLIGHTLY_RIGHT), - tc(41, RIGHT), - tc(114, RIGHT), - tc(115, HARD_RIGHT), - tc(179, HARD_RIGHT), - tc(180, HARD_LEFT), - // Turn Left - tc(0, CONTINUE), - tc(-17, CONTINUE), - tc(-18, SLIGHTLY_LEFT), - tc(-40, SLIGHTLY_LEFT), - tc(-41, LEFT), - tc(-114, LEFT), - tc(-115, HARD_LEFT), - tc(-179, HARD_LEFT), - tc(-180, HARD_LEFT), - tc(-181, HARD_RIGHT) - ); + static Stream testCasesNormal() { + return Stream.of( + // Turn Right + tc(0, CONTINUE), + tc(17, CONTINUE), + tc(18, SLIGHTLY_RIGHT), + tc(40, SLIGHTLY_RIGHT), + tc(41, RIGHT), + tc(114, RIGHT), + tc(115, HARD_RIGHT), + tc(179, HARD_RIGHT), + tc(180, HARD_LEFT), + // Turn Left + tc(0, CONTINUE), + tc(-17, CONTINUE), + tc(-18, SLIGHTLY_LEFT), + tc(-40, SLIGHTLY_LEFT), + tc(-41, LEFT), + tc(-114, LEFT), + tc(-115, HARD_LEFT), + tc(-179, HARD_LEFT), + tc(-180, HARD_LEFT), + tc(-181, HARD_RIGHT) + ); + } @ParameterizedTest(name = "Turning {0} degrees should give a relative direction of {1}") - @VariableSource("testCasesNormal") + @MethodSource("testCasesNormal") void testCalculateForNormalIntersections(int thisAngleDegrees, RelativeDirection expected) { assertEquals(expected, RelativeDirection.calculate(angle(thisAngleDegrees), false)); } diff --git a/src/test/java/org/opentripplanner/model/plan/ScheduledTransitLegTest.java b/src/test/java/org/opentripplanner/model/plan/ScheduledTransitLegTest.java index e45beb8ebad..b41ace81e8d 100644 --- a/src/test/java/org/opentripplanner/model/plan/ScheduledTransitLegTest.java +++ b/src/test/java/org/opentripplanner/model/plan/ScheduledTransitLegTest.java @@ -1,6 +1,8 @@ package org.opentripplanner.model.plan; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.opentripplanner.transit.model._data.TransitModelForTest.id; import java.time.OffsetDateTime; @@ -9,33 +11,74 @@ import org.junit.jupiter.api.Test; import org.opentripplanner._support.time.ZoneIds; import org.opentripplanner.transit.model._data.TransitModelForTest; +import org.opentripplanner.transit.model.network.Route; +import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.model.timetable.RealTimeTripTimes; +import org.opentripplanner.transit.model.timetable.ScheduledTripTimes; +import org.opentripplanner.transit.model.timetable.Trip; class ScheduledTransitLegTest { static final ZonedDateTime TIME = OffsetDateTime .parse("2023-04-17T17:49:06+02:00") .toZonedDateTime(); + private static final TransitModelForTest TEST_MODEL = TransitModelForTest.of(); + private static final Route ROUTE = TransitModelForTest.route(id("2")).build(); + private static final TripPattern PATTERN = TransitModelForTest + .tripPattern("1", ROUTE) + .withStopPattern(TEST_MODEL.stopPattern(3)) + .build(); + private static final Trip TRIP = TransitModelForTest.trip("trip1").build(); + private static final ScheduledTripTimes TRIP_TIMES = ScheduledTripTimes + .of() + .withArrivalTimes("10:00 11:00 12:00") + .withDepartureTimes("10:01 11:02 12:03") + .withTrip(TRIP) + .build(); @Test void defaultFares() { - var testModel = TransitModelForTest.of(); - var route = TransitModelForTest.route(id("2")).build(); - var pattern = TransitModelForTest - .tripPattern("1", route) - .withStopPattern(testModel.stopPattern(3)) + var leg = builder().build(); + + assertEquals(List.of(), leg.fareProducts()); + } + + @Test + void legTimesWithoutRealTime() { + var leg = builder().withTripTimes(TRIP_TIMES).build(); + + assertNull(leg.start().estimated()); + assertNull(leg.end().estimated()); + } + + @Test + void legTimesWithRealTime() { + var tt = ScheduledTripTimes + .of() + .withArrivalTimes("10:00 11:00 12:00") + .withDepartureTimes("10:01 11:02 12:03") + .withTrip(TRIP) .build(); - var leg = new ScheduledTransitLegBuilder() + + var rtt = RealTimeTripTimes.of(tt); + rtt.updateArrivalTime(0, 111); + + var leg = builder().withTripTimes(rtt).build(); + + assertNotNull(leg.start().estimated()); + assertNotNull(leg.end().estimated()); + } + + private static ScheduledTransitLegBuilder builder() { + return new ScheduledTransitLegBuilder() .withTripTimes(null) - .withTripPattern(pattern) + .withTripPattern(PATTERN) .withBoardStopIndexInPattern(0) .withAlightStopIndexInPattern(2) .withStartTime(TIME) .withEndTime(TIME.plusMinutes(10)) .withServiceDate(TIME.toLocalDate()) .withZoneId(ZoneIds.BERLIN) - .withGeneralizedCost(100) - .build(); - - assertEquals(List.of(), leg.fareProducts()); + .withGeneralizedCost(100); } } diff --git a/src/test/java/org/opentripplanner/openstreetmap/OSMOpeningHoursParserTest.java b/src/test/java/org/opentripplanner/openstreetmap/OSMOpeningHoursParserTest.java index f1068bb8220..34f141e8504 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/OSMOpeningHoursParserTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/OSMOpeningHoursParserTest.java @@ -12,9 +12,9 @@ import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner._support.time.ZoneIds; import org.opentripplanner.model.calendar.openinghours.OpeningHoursCalendarService; -import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model.framework.Deduplicator; public class OSMOpeningHoursParserTest { @@ -30,150 +30,152 @@ public class OSMOpeningHoursParserTest { ZoneIds.PARIS ); - static Stream osmOpeningHoursTestCases = Stream.of( - Arguments.of( - "Mo-Fr 14:00-19:00", - List.of("2022-10-25T14:30:00Z"), - List.of("2022-10-25T08:30:00Z", "2022-10-29T14:30:00Z") - ), - Arguments.of( - "Tu 10:00-15:00", - List.of("2022-10-25T10:30:00Z"), - List.of("2022-10-26T00:30:00Z", "2022-10-27T10:30:00Z") - ), - Arguments.of( - "Mo-Fr 08:00-17:00;Sa-Su 10:00-13:00", - List.of("2022-10-25T07:30:00Z", "2022-10-29T10:30:00Z"), - List.of("2022-10-25T03:30:00Z", "2022-10-29T15:30:00Z") - ), - // TODO implement support for public holidays, currently only the first two rules are handled - Arguments.of( - "Mo-Fr 08:00-17:00;Sa-Su 10:00-13:00; PH off", - List.of("2022-10-25T07:30:00Z", "2022-10-29T10:30:00Z"), - List.of("2022-10-25T03:30:00Z", "2022-10-29T15:30:00Z") - ), - // TODO implement support for public holidays, currently only the first two rules are handled - Arguments.of( - "Mo,We,Th,Fr,Su 00:00-24:00; Tu,Sa 00:00-02:00, 14:30-24:00; PH 00:00-24:00", - List.of("2022-10-24T15:30:00Z", "2022-10-24T23:30:00Z", "2022-10-25T15:30:00Z"), - List.of("2022-10-25T05:30:00Z") - ), - // The second rule overrides the first rule for Wednesdays, i.e. on Wednesdays it should only be - // open from 10:00 to 13:00 - Arguments.of( - "Mo-Fr 16:00-02:00; We 10:00-13:00", - List.of("2022-10-25T23:30:00Z", "2022-10-26T10:30:00Z", "2022-10-27T23:30:00Z"), - List.of("2022-10-25T10:30:00Z", "2022-10-26T23:30:00Z") - ), - // The second rule overrides the first rule for Tuesdays, i.e. it should be closed on Tuesdays - Arguments.of( - "Mo - Sa 10:00-18:00; Tu off", - List.of("2022-10-24T15:30:00Z"), - List.of("2022-10-24T05:30:00Z", "2022-10-25T10:30:00Z") - ), - // Even though the following rules are not additive, they should be handled as such because they - // have the off modifier (https://github.com/opening-hours/opening_hours.js/issues/53). - // Therefore, it should be open according to the first rule outside of the defined off periods, - // so for example, on Tuesdays it should be open from 07:30 to 12:00 and from 16:00 to 22:00. - // These different off cases are needed because they overlap slightly differently with the first - // rule and have slightly different handling in code. - Arguments.of( - "Mo-Sa 07:30-22:00; Mo 05:00-23:00 off; Tu 12:00-16:00 off; We 06:00-16:00 off; Th 07:30-16:00 off, Fr 16:00-22:00 off, Sa 16:00-23:00 off", - List.of( - "2022-10-25T07:30:00Z", - "2022-10-25T17:30:00Z", - "2022-10-26T17:30:00Z", - "2022-10-27T17:30:00Z", - "2022-10-28T07:30:00Z", - "2022-10-29T07:30:00Z" - ), - List.of( - "2022-10-24T07:30:00Z", - "2022-10-25T12:30:00Z", - "2022-10-26T07:30:00Z", - "2022-10-27T07:30:00Z", - "2022-10-28T16:30:00Z", - "2022-10-29T16:30:00Z", - "2022-10-30T07:30:00Z" + static Stream osmOpeningHoursTestCases() { + return Stream.of( + Arguments.of( + "Mo-Fr 14:00-19:00", + List.of("2022-10-25T14:30:00Z"), + List.of("2022-10-25T08:30:00Z", "2022-10-29T14:30:00Z") + ), + Arguments.of( + "Tu 10:00-15:00", + List.of("2022-10-25T10:30:00Z"), + List.of("2022-10-26T00:30:00Z", "2022-10-27T10:30:00Z") + ), + Arguments.of( + "Mo-Fr 08:00-17:00;Sa-Su 10:00-13:00", + List.of("2022-10-25T07:30:00Z", "2022-10-29T10:30:00Z"), + List.of("2022-10-25T03:30:00Z", "2022-10-29T15:30:00Z") + ), + // TODO implement support for public holidays, currently only the first two rules are handled + Arguments.of( + "Mo-Fr 08:00-17:00;Sa-Su 10:00-13:00; PH off", + List.of("2022-10-25T07:30:00Z", "2022-10-29T10:30:00Z"), + List.of("2022-10-25T03:30:00Z", "2022-10-29T15:30:00Z") + ), + // TODO implement support for public holidays, currently only the first two rules are handled + Arguments.of( + "Mo,We,Th,Fr,Su 00:00-24:00; Tu,Sa 00:00-02:00, 14:30-24:00; PH 00:00-24:00", + List.of("2022-10-24T15:30:00Z", "2022-10-24T23:30:00Z", "2022-10-25T15:30:00Z"), + List.of("2022-10-25T05:30:00Z") + ), + // The second rule overrides the first rule for Wednesdays, i.e. on Wednesdays it should only be + // open from 10:00 to 13:00 + Arguments.of( + "Mo-Fr 16:00-02:00; We 10:00-13:00", + List.of("2022-10-25T23:30:00Z", "2022-10-26T10:30:00Z", "2022-10-27T23:30:00Z"), + List.of("2022-10-25T10:30:00Z", "2022-10-26T23:30:00Z") + ), + // The second rule overrides the first rule for Tuesdays, i.e. it should be closed on Tuesdays + Arguments.of( + "Mo - Sa 10:00-18:00; Tu off", + List.of("2022-10-24T15:30:00Z"), + List.of("2022-10-24T05:30:00Z", "2022-10-25T10:30:00Z") + ), + // Even though the following rules are not additive, they should be handled as such because they + // have the off modifier (https://github.com/opening-hours/opening_hours.js/issues/53). + // Therefore, it should be open according to the first rule outside of the defined off periods, + // so for example, on Tuesdays it should be open from 07:30 to 12:00 and from 16:00 to 22:00. + // These different off cases are needed because they overlap slightly differently with the first + // rule and have slightly different handling in code. + Arguments.of( + "Mo-Sa 07:30-22:00; Mo 05:00-23:00 off; Tu 12:00-16:00 off; We 06:00-16:00 off; Th 07:30-16:00 off, Fr 16:00-22:00 off, Sa 16:00-23:00 off", + List.of( + "2022-10-25T07:30:00Z", + "2022-10-25T17:30:00Z", + "2022-10-26T17:30:00Z", + "2022-10-27T17:30:00Z", + "2022-10-28T07:30:00Z", + "2022-10-29T07:30:00Z" + ), + List.of( + "2022-10-24T07:30:00Z", + "2022-10-25T12:30:00Z", + "2022-10-26T07:30:00Z", + "2022-10-27T07:30:00Z", + "2022-10-28T16:30:00Z", + "2022-10-29T16:30:00Z", + "2022-10-30T07:30:00Z" + ) + ), + // This tests that it's correctly closed outside with an off rule that extends over midnight + Arguments.of( + "Mo-Fr 12:30-04:00; We 18:00-02:00 off", + List.of( + "2022-10-25T19:30:00Z", + "2022-10-25T23:30:00Z", + "2022-10-26T13:30:00Z", + "2022-10-27T01:30:00Z", + "2022-10-27T23:30:00Z" + ), + List.of("2022-10-26T19:30:00Z", "2022-10-26T23:30:00Z", "2022-10-27T05:30:00Z") + ), + Arguments.of( + "open; Tu 13:00-16:00 off", + List.of("2022-10-24T12:30:00Z", "2022-10-25T07:30:00Z", "2022-10-24T18:30:00Z"), + List.of("2022-10-25T13:30:00Z") + ), + Arguments.of( + "Su-Tu 11:00-02:00, We-Th 11:00-03:00, Fr 11:00-06:00, Sa 11:00-07:00", + List.of("2022-10-25T14:30:00Z", "2022-10-25T23:30:00Z", "2022-10-29T03:30:00Z"), + List.of("2022-10-25T02:30:00Z", "2022-10-27T03:30:00Z") + ), + Arguments.of( + "Sep-Oct: Mo-Sa 10:00-02:00", + List.of("2022-09-30T23:30:00Z", "2022-10-29T10:30:00Z", "2022-10-31T23:30:00Z"), + List.of("2022-10-30T10:30:00Z", "2022-11-26T10:30:00Z") + ), + Arguments.of( + "Oct: Mo 10:00-02:00", + List.of("2022-10-24T10:30:00Z", "2022-10-31T23:30:00Z"), + List.of("2022-09-30T23:30:00Z", "2022-10-30T10:30:00Z", "2022-11-07T10:30:00Z") + ), + // TODO implement support for dates with and without year, this is now completely unparsed + // which means that it's never open + Arguments.of("Oct 23-Jan 3: 10:00-23:00", List.of(), List.of("2022-10-20T12:30:00Z")), + // TODO implement support for nth weekday in a month and offsets + Arguments.of( + "Mar Su[-1]-Oct Su[1] -2 days: 22:00-23:00", + List.of(), + List.of("2022-11-20T12:30:00Z") + ), + Arguments.of("24/7", List.of("2022-10-25T07:30:00Z"), List.of()), + Arguments.of("24/7 closed", List.of(), List.of("2022-10-25T07:30:00Z")), + // TODO implement support for school holidays and special dates, only the first rule is now + // handled + Arguments.of( + "Mo-Su,SH 15:00-03:00; easter -2 days off", + List.of("2022-10-25T15:30:00Z"), + List.of("2022-10-25T05:30:00Z") + ), + // TODO implement support for comment modifiers, this is now interpreted to be always closed + Arguments.of("\"by appointment\"", List.of(), List.of("2022-10-25T07:30:00Z")), + // TODO implement support for fallbacks if feasible, now the last rule is ignored and the first + // rule is respected + Arguments.of( + "Mo-Sa 08:00-13:00,16:00-18:00 || \"by appointment\"", + List.of("2022-10-25T07:30:00Z", "2022-10-25T15:30:00Z"), + List.of("2022-10-25T13:30:00Z", "2022-10-30T07:30:00Z") + ), + // TODO implement support for weeks, these rules are now ignored and it's always closed + Arguments.of( + "week 1-53/2 Fr 09:00-12:00; week 2-52/2 We 09:00-12:00", + List.of(), + List.of("2022-10-28T07:30:00Z") + ), + // TODO implement support for events, this is now interpreted to be always closed + Arguments.of("sunrise-sunset", List.of(), List.of("2022-10-25T07:30:00Z")), + // This is interpreted to be open on sundays from 10:00 until 23:59 + Arguments.of( + "Su 10:00+", + List.of("2022-10-30T10:30:00Z"), + List.of("2022-10-25T10:30:00Z", "2022-10-30T06:30:00Z") ) - ), - // This tests that it's correctly closed outside with an off rule that extends over midnight - Arguments.of( - "Mo-Fr 12:30-04:00; We 18:00-02:00 off", - List.of( - "2022-10-25T19:30:00Z", - "2022-10-25T23:30:00Z", - "2022-10-26T13:30:00Z", - "2022-10-27T01:30:00Z", - "2022-10-27T23:30:00Z" - ), - List.of("2022-10-26T19:30:00Z", "2022-10-26T23:30:00Z", "2022-10-27T05:30:00Z") - ), - Arguments.of( - "open; Tu 13:00-16:00 off", - List.of("2022-10-24T12:30:00Z", "2022-10-25T07:30:00Z", "2022-10-24T18:30:00Z"), - List.of("2022-10-25T13:30:00Z") - ), - Arguments.of( - "Su-Tu 11:00-02:00, We-Th 11:00-03:00, Fr 11:00-06:00, Sa 11:00-07:00", - List.of("2022-10-25T14:30:00Z", "2022-10-25T23:30:00Z", "2022-10-29T03:30:00Z"), - List.of("2022-10-25T02:30:00Z", "2022-10-27T03:30:00Z") - ), - Arguments.of( - "Sep-Oct: Mo-Sa 10:00-02:00", - List.of("2022-09-30T23:30:00Z", "2022-10-29T10:30:00Z", "2022-10-31T23:30:00Z"), - List.of("2022-10-30T10:30:00Z", "2022-11-26T10:30:00Z") - ), - Arguments.of( - "Oct: Mo 10:00-02:00", - List.of("2022-10-24T10:30:00Z", "2022-10-31T23:30:00Z"), - List.of("2022-09-30T23:30:00Z", "2022-10-30T10:30:00Z", "2022-11-07T10:30:00Z") - ), - // TODO implement support for dates with and without year, this is now completely unparsed - // which means that it's never open - Arguments.of("Oct 23-Jan 3: 10:00-23:00", List.of(), List.of("2022-10-20T12:30:00Z")), - // TODO implement support for nth weekday in a month and offsets - Arguments.of( - "Mar Su[-1]-Oct Su[1] -2 days: 22:00-23:00", - List.of(), - List.of("2022-11-20T12:30:00Z") - ), - Arguments.of("24/7", List.of("2022-10-25T07:30:00Z"), List.of()), - Arguments.of("24/7 closed", List.of(), List.of("2022-10-25T07:30:00Z")), - // TODO implement support for school holidays and special dates, only the first rule is now - // handled - Arguments.of( - "Mo-Su,SH 15:00-03:00; easter -2 days off", - List.of("2022-10-25T15:30:00Z"), - List.of("2022-10-25T05:30:00Z") - ), - // TODO implement support for comment modifiers, this is now interpreted to be always closed - Arguments.of("\"by appointment\"", List.of(), List.of("2022-10-25T07:30:00Z")), - // TODO implement support for fallbacks if feasible, now the last rule is ignored and the first - // rule is respected - Arguments.of( - "Mo-Sa 08:00-13:00,16:00-18:00 || \"by appointment\"", - List.of("2022-10-25T07:30:00Z", "2022-10-25T15:30:00Z"), - List.of("2022-10-25T13:30:00Z", "2022-10-30T07:30:00Z") - ), - // TODO implement support for weeks, these rules are now ignored and it's always closed - Arguments.of( - "week 1-53/2 Fr 09:00-12:00; week 2-52/2 We 09:00-12:00", - List.of(), - List.of("2022-10-28T07:30:00Z") - ), - // TODO implement support for events, this is now interpreted to be always closed - Arguments.of("sunrise-sunset", List.of(), List.of("2022-10-25T07:30:00Z")), - // This is interpreted to be open on sundays from 10:00 until 23:59 - Arguments.of( - "Su 10:00+", - List.of("2022-10-30T10:30:00Z"), - List.of("2022-10-25T10:30:00Z", "2022-10-30T06:30:00Z") - ) - ); + ); + } @ParameterizedTest(name = "{0} should be open on {1} but not on {2}") - @VariableSource("osmOpeningHoursTestCases") + @MethodSource("osmOpeningHoursTestCases") void testOSMOpeningHoursParsing( String openingHours, List openTimes, diff --git a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/PortlandMapperTest.java b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/PortlandMapperTest.java index 06369baddc8..26f45fd8c32 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/PortlandMapperTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/PortlandMapperTest.java @@ -18,30 +18,33 @@ import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.openstreetmap.model.OSMWithTags; import org.opentripplanner.openstreetmap.wayproperty.WayPropertySet; -import org.opentripplanner.test.support.VariableSource; public class PortlandMapperTest { static double delta = 0.1; static WayPropertySet wps = new WayPropertySet(); - static Stream cases = Stream.of( - Arguments.of(southeastLaBonitaWay(), 0.8), - Arguments.of(southwestMayoStreet(), 0.9), - Arguments.of(sidewalkBoth(), 0.96), - Arguments.of(pedestrianTunnel(), 1.0), - Arguments.of(highwayTertiaryWithSidewalk(), 1.056), - Arguments.of(cobblestones(), 1.2), - Arguments.of(noSidewalk(), 1.2), - Arguments.of(carTunnel(), 1.2), - Arguments.of(footwaySidewalk(), 1.32), - Arguments.of(highwayTertiary(), 1.32), - Arguments.of(highwayTrunk(), 1.44), - Arguments.of(fiveLanes(), 1.584), - Arguments.of(noSidewalkHighSpeed(), 7.19) - ); + + static Stream cases() { + return Stream.of( + Arguments.of(southeastLaBonitaWay(), 0.8), + Arguments.of(southwestMayoStreet(), 0.9), + Arguments.of(sidewalkBoth(), 0.96), + Arguments.of(pedestrianTunnel(), 1.0), + Arguments.of(highwayTertiaryWithSidewalk(), 1.056), + Arguments.of(cobblestones(), 1.2), + Arguments.of(noSidewalk(), 1.2), + Arguments.of(carTunnel(), 1.2), + Arguments.of(footwaySidewalk(), 1.32), + Arguments.of(highwayTertiary(), 1.32), + Arguments.of(highwayTrunk(), 1.44), + Arguments.of(fiveLanes(), 1.584), + Arguments.of(noSidewalkHighSpeed(), 7.19) + ); + } static { var source = new PortlandMapper(); @@ -49,7 +52,7 @@ public class PortlandMapperTest { } @ParameterizedTest(name = "way {0} should have walk safety factor {1}") - @VariableSource("cases") + @MethodSource("cases") void walkSafety(OSMWithTags way, double expected) { var score = wps.getDataForWay(way); diff --git a/src/test/java/org/opentripplanner/openstreetmap/wayproperty/specifier/BestMatchSpecifierTest.java b/src/test/java/org/opentripplanner/openstreetmap/wayproperty/specifier/BestMatchSpecifierTest.java index 19575c9312f..517e4b57bfd 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/wayproperty/specifier/BestMatchSpecifierTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/wayproperty/specifier/BestMatchSpecifierTest.java @@ -8,8 +8,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.openstreetmap.model.OSMWithTags; -import org.opentripplanner.test.support.VariableSource; class BestMatchSpecifierTest extends SpecifierTest { @@ -48,17 +48,19 @@ void leftRightMatch() { assertEquals(100, result.forward()); } - static Stream leftRightTestCases = Stream.of( - Arguments.of(cyclewayLeft(), bikeLane, 210, 100), - Arguments.of(cyclewayLaneTrack(), cyclewayTrack, 100, 210), - Arguments.of(cyclewayLaneTrack(), highwayFootwayCyclewayLane, 210, 100), - Arguments.of(cyclewayLaneTrack(), cyclewayLane, 110, 0) - ); + static Stream leftRightTestCases() { + return Stream.of( + Arguments.of(cyclewayLeft(), bikeLane, 210, 100), + Arguments.of(cyclewayLaneTrack(), cyclewayTrack, 100, 210), + Arguments.of(cyclewayLaneTrack(), highwayFootwayCyclewayLane, 210, 100), + Arguments.of(cyclewayLaneTrack(), cyclewayLane, 110, 0) + ); + } @ParameterizedTest( name = "way {0} with specifier {1} should have a backward score {2} and forward score {3}" ) - @VariableSource("leftRightTestCases") + @MethodSource("leftRightTestCases") void leftRight(OSMWithTags way, OsmSpecifier spec, int expectedBackward, int expectedForward) { var result = spec.matchScores(way); assertEquals(expectedBackward, result.backward()); diff --git a/src/test/java/org/opentripplanner/openstreetmap/wayproperty/specifier/ConditionTest.java b/src/test/java/org/opentripplanner/openstreetmap/wayproperty/specifier/ConditionTest.java index a1d126003a5..def272652f6 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/wayproperty/specifier/ConditionTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/wayproperty/specifier/ConditionTest.java @@ -25,6 +25,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.openstreetmap.model.OSMWithTags; import org.opentripplanner.openstreetmap.wayproperty.specifier.Condition.Absent; import org.opentripplanner.openstreetmap.wayproperty.specifier.Condition.Equals; @@ -34,7 +35,6 @@ import org.opentripplanner.openstreetmap.wayproperty.specifier.Condition.LessThan; import org.opentripplanner.openstreetmap.wayproperty.specifier.Condition.MatchResult; import org.opentripplanner.openstreetmap.wayproperty.specifier.Condition.Present; -import org.opentripplanner.test.support.VariableSource; class ConditionTest { @@ -56,18 +56,20 @@ class ConditionTest { ); static Condition noSidewalk = new Condition.EqualsAnyInOrAbsent("sidewalk"); - static Stream equalsCases = Stream.of( - Arguments.of(cyclewayLeft(), cyclewayLane, EXACT, NONE), - Arguments.of(cyclewayLaneTrack(), cyclewayLane, EXACT, NONE), - Arguments.of(cyclewayBoth(), cyclewayLane, EXACT, EXACT), - Arguments.of(cyclewayLaneTrack(), cyclewayTrack, NONE, EXACT), - Arguments.of(tramsForward(), embeddedTrams, NONE, EXACT) - ); + static Stream equalsCases() { + return Stream.of( + Arguments.of(cyclewayLeft(), cyclewayLane, EXACT, NONE), + Arguments.of(cyclewayLaneTrack(), cyclewayLane, EXACT, NONE), + Arguments.of(cyclewayBoth(), cyclewayLane, EXACT, EXACT), + Arguments.of(cyclewayLaneTrack(), cyclewayTrack, NONE, EXACT), + Arguments.of(tramsForward(), embeddedTrams, NONE, EXACT) + ); + } @ParameterizedTest( name = "way {0} with op {1} should have a backward result {2}, forward result {3}" ) - @VariableSource("equalsCases") + @MethodSource("equalsCases") void leftRight( OSMWithTags way, Condition op, @@ -78,32 +80,34 @@ void leftRight( assertEquals(forwardExpectation, op.matchForward(way)); } - static Stream otherCases = Stream.of( - Arguments.of(cycleway(), cyclewayPresent, WILDCARD), - Arguments.of(carTunnel(), cyclewayPresent, NONE), - Arguments.of(carTunnel(), cyclewayAbsent, EXACT), - Arguments.of(cobblestones(), cyclewayAbsent, EXACT), - Arguments.of(cycleway(), cyclewayAbsent, NONE), - Arguments.of(cycleway(), moreThanFourLanes, NONE), - Arguments.of(carTunnel(), moreThanFourLanes, NONE), - Arguments.of(pedestrianTunnel(), moreThanFourLanes, NONE), - Arguments.of(fiveLanes(), moreThanFourLanes, EXACT), - Arguments.of(fiveLanes(), lessThanFourLanes, NONE), - Arguments.of(threeLanes(), lessThanFourLanes, EXACT), - Arguments.of(carTunnel(), lessThanFourLanes, NONE), - Arguments.of(cycleway(), lessThanFourLanes, NONE), - Arguments.of(fiveLanes(), betweenFiveAndThreeLanes, EXACT), - Arguments.of(threeLanes(), betweenFiveAndThreeLanes, EXACT), - Arguments.of(veryBadSmoothness(), smoothnessBadAndWorseThanBad, EXACT), - Arguments.of(cobblestones(), smoothnessBadAndWorseThanBad, NONE), - Arguments.of(excellentSmoothness(), smoothnessBadAndWorseThanBad, NONE), - Arguments.of(noSidewalk(), noSidewalk, EXACT), - Arguments.of(highwayTertiary(), noSidewalk, EXACT), - Arguments.of(sidewalkBoth(), noSidewalk, NONE) - ); + static Stream otherCases() { + return Stream.of( + Arguments.of(cycleway(), cyclewayPresent, WILDCARD), + Arguments.of(carTunnel(), cyclewayPresent, NONE), + Arguments.of(carTunnel(), cyclewayAbsent, EXACT), + Arguments.of(cobblestones(), cyclewayAbsent, EXACT), + Arguments.of(cycleway(), cyclewayAbsent, NONE), + Arguments.of(cycleway(), moreThanFourLanes, NONE), + Arguments.of(carTunnel(), moreThanFourLanes, NONE), + Arguments.of(pedestrianTunnel(), moreThanFourLanes, NONE), + Arguments.of(fiveLanes(), moreThanFourLanes, EXACT), + Arguments.of(fiveLanes(), lessThanFourLanes, NONE), + Arguments.of(threeLanes(), lessThanFourLanes, EXACT), + Arguments.of(carTunnel(), lessThanFourLanes, NONE), + Arguments.of(cycleway(), lessThanFourLanes, NONE), + Arguments.of(fiveLanes(), betweenFiveAndThreeLanes, EXACT), + Arguments.of(threeLanes(), betweenFiveAndThreeLanes, EXACT), + Arguments.of(veryBadSmoothness(), smoothnessBadAndWorseThanBad, EXACT), + Arguments.of(cobblestones(), smoothnessBadAndWorseThanBad, NONE), + Arguments.of(excellentSmoothness(), smoothnessBadAndWorseThanBad, NONE), + Arguments.of(noSidewalk(), noSidewalk, EXACT), + Arguments.of(highwayTertiary(), noSidewalk, EXACT), + Arguments.of(sidewalkBoth(), noSidewalk, NONE) + ); + } @ParameterizedTest(name = "way {0} with op {1} should have a result {2}") - @VariableSource("otherCases") + @MethodSource("otherCases") void otherTests(OSMWithTags way, Condition op, MatchResult expectation) { assertEquals(expectation, op.match(way)); } diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPathsTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPathsTest.java index bf8e06a73d7..8611a046a5b 100644 --- a/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPathsTest.java +++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPathsTest.java @@ -83,7 +83,7 @@ void calculateMaxNumberOfRides() { } @Test - void iterateOverPathsWithPenalty() { + void iterateOverPathsWithTimePenalty() { // Expected at departure 540 var flexFastWithPenalty = FLEX_FAST.withTimePenalty(60); @@ -113,6 +113,25 @@ void iterateOverPathsWithPenalty() { FORWARD ); + // Make sure standard iterator works + expect( + accessPaths.arrivedOnStreetByNumOfRides(0), + WALK_B, + walkFastWithPenalty, + walkCostWithPenalty + ); + expect(accessPaths.arrivedOnBoardByNumOfRides(1)); + expect(accessPaths.arrivedOnStreetByNumOfRides(1)); + expect(accessPaths.arrivedOnBoardByNumOfRides(2), flexTxWithPenalty); + expect(accessPaths.arrivedOnStreetByNumOfRides(2), FLEX_WALK_B); + expect( + accessPaths.arrivedOnBoardByNumOfRides(3), + FLEX_B, + flexFastWithPenalty, + flexCostWithPenalty + ); + expect(accessPaths.arrivedOnStreetByNumOfRides(3)); + var iterator = accessPaths.iterateOverPathsWithPenalty(600); // First iteration @@ -146,7 +165,7 @@ void iterateOverPathsWithPenalty() { } @Test - void iterateOverPathsWithPenaltyInReversDirection() { + void iterateOverPathsWithTimePenaltyInReversDirection() { // Expected at departure 540 var flexFastWithPenalty = FLEX_FAST.withTimePenalty(60); @@ -164,6 +183,10 @@ void iterateOverPathsWithPenaltyInReversDirection() { REVERSE ); + // Make sure standard iterator works + expect(accessPaths.arrivedOnStreetByNumOfRides(0), WALK_B, walkFastWithPenalty); + expect(accessPaths.arrivedOnBoardByNumOfRides(3), FLEX_B, flexFastWithPenalty); + var iterator = accessPaths.iterateOverPathsWithPenalty(600); // First iteration @@ -196,6 +219,34 @@ void iterateOverPathsWithPenaltyInReversDirection() { assertFalse(iterator.hasNext()); } + @Test + void testRegularIteratorsAndIteratorWithPenaltyWorksTogether() { + var walkFastWithPenalty = WALK_FAST.withTimePenalty(60); + + // Without time-penalty, the iterator should be empty + var accessPaths = AccessPaths.create( + 60, + List.of(walkFastWithPenalty, WALK_COST), + MULTI_CRITERIA, + FORWARD + ); + + // Both accesses are expected before with enter the "time-penalty" iteration + expect(accessPaths.arrivedOnStreetByNumOfRides(0), WALK_COST, walkFastWithPenalty); + expect(accessPaths.arrivedOnBoardByNumOfRides(0)); + + var iterator = accessPaths.iterateOverPathsWithPenalty(600); + + // First iteration - only access with time-penalty is expected + assertTrue(iterator.hasNext()); + assertEquals(540, iterator.next()); + expect(accessPaths.arrivedOnStreetByNumOfRides(0), walkFastWithPenalty); + expect(accessPaths.arrivedOnBoardByNumOfRides(0)); + + // Second iteration - Done + assertFalse(iterator.hasNext()); + } + @Test void hasTimeDependentAccess() { var accessPaths = AccessPaths.create( diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TripPatternForDateTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TripPatternForDateTest.java index ecd75f5d273..dd9a2fffa66 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TripPatternForDateTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TripPatternForDateTest.java @@ -8,9 +8,9 @@ import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.model.Frequency; import org.opentripplanner.model.StopTime; -import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.network.Route; @@ -33,12 +33,14 @@ class TripPatternForDateTest { new Deduplicator() ); - static Stream testCases = Stream - .of(List.of(new FrequencyEntry(new Frequency(), tripTimes)), List.of()) - .map(Arguments::of); + static Stream testCases() { + return Stream + .of(List.of(new FrequencyEntry(new Frequency(), tripTimes)), List.of()) + .map(Arguments::of); + } @ParameterizedTest(name = "trip with frequencies {0} should be correctly filtered") - @VariableSource("testCases") + @MethodSource("testCases") void shouldExcludeAndIncludeBasedOnFrequency(List freqs) { var stopTime = new StopTime(); stopTime.setStop(STOP); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/WheelchairCostCalculatorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/WheelchairCostCalculatorTest.java index 0c2384ebabb..b7d3f255a9d 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/WheelchairCostCalculatorTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/WheelchairCostCalculatorTest.java @@ -5,11 +5,11 @@ import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.raptor._data.transit.TestTripSchedule; import org.opentripplanner.raptor.api.model.RaptorTransferConstraint; import org.opentripplanner.raptor.spi.RaptorCostCalculator; import org.opentripplanner.routing.api.request.preference.AccessibilityPreferences; -import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model.basic.Accessibility; public class WheelchairCostCalculatorTest { @@ -34,14 +34,16 @@ public class WheelchairCostCalculatorTest { ); private final TestTripSchedule.Builder scheduleBuilder = TestTripSchedule.schedule("12:00 12:01"); - static Stream testCases = Stream.of( - Arguments.of(Accessibility.POSSIBLE, 0), - Arguments.of(Accessibility.NO_INFORMATION, UNKNOWN_ACCESSIBILITY_COST), - Arguments.of(Accessibility.NOT_POSSIBLE, INACCESSIBLE_TRIP_COST) - ); + static Stream testCases() { + return Stream.of( + Arguments.of(Accessibility.POSSIBLE, 0), + Arguments.of(Accessibility.NO_INFORMATION, UNKNOWN_ACCESSIBILITY_COST), + Arguments.of(Accessibility.NOT_POSSIBLE, INACCESSIBLE_TRIP_COST) + ); + } @ParameterizedTest(name = "accessibility of {0} should add an extra cost of {1}") - @VariableSource("testCases") + @MethodSource("testCases") public void calculateExtraBoardingCost(Accessibility wcb, int expectedExtraCost) { var schedule = scheduleBuilder.wheelchairBoarding(wcb).build(); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RouteRequestTransitDataProviderFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RouteRequestTransitDataProviderFilterTest.java index 4823ea84300..be6266ccdbe 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RouteRequestTransitDataProviderFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RouteRequestTransitDataProviderFilterTest.java @@ -13,6 +13,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import org.opentripplanner.apis.transmodel.model.TransmodelTransportSubmode; import org.opentripplanner.framework.geometry.WgsCoordinate; @@ -25,7 +26,6 @@ import org.opentripplanner.routing.api.request.request.filter.SelectRequest; import org.opentripplanner.routing.api.request.request.filter.TransitFilter; import org.opentripplanner.routing.api.request.request.filter.TransitFilterRequest; -import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.basic.Accessibility; import org.opentripplanner.transit.model.basic.MainAndSubMode; @@ -70,14 +70,16 @@ class RouteRequestTransitDataProviderFilterTest { .withElevator(RELAXED_ACCESSIBILITY_PREFERENCE) .build(); - static Stream wheelchairCases = Stream.of( - Arguments.of(Accessibility.POSSIBLE, DEFAULT_ACCESSIBILITY), - Arguments.of(Accessibility.POSSIBLE, RELAXED_ACCESSIBILITY), - Arguments.of(Accessibility.NOT_POSSIBLE, DEFAULT_ACCESSIBILITY), - Arguments.of(Accessibility.NOT_POSSIBLE, RELAXED_ACCESSIBILITY), - Arguments.of(Accessibility.NO_INFORMATION, DEFAULT_ACCESSIBILITY), - Arguments.of(Accessibility.NO_INFORMATION, RELAXED_ACCESSIBILITY) - ); + static Stream wheelchairCases() { + return Stream.of( + Arguments.of(Accessibility.POSSIBLE, DEFAULT_ACCESSIBILITY), + Arguments.of(Accessibility.POSSIBLE, RELAXED_ACCESSIBILITY), + Arguments.of(Accessibility.NOT_POSSIBLE, DEFAULT_ACCESSIBILITY), + Arguments.of(Accessibility.NOT_POSSIBLE, RELAXED_ACCESSIBILITY), + Arguments.of(Accessibility.NO_INFORMATION, DEFAULT_ACCESSIBILITY), + Arguments.of(Accessibility.NO_INFORMATION, RELAXED_ACCESSIBILITY) + ); + } /** * Test filter for wheelchair access. @@ -85,7 +87,7 @@ class RouteRequestTransitDataProviderFilterTest { * @param wheelchair Accessibility for stops */ @ParameterizedTest - @VariableSource("wheelchairCases") + @MethodSource("wheelchairCases") void testWheelchairAccess(Accessibility wheelchair, WheelchairPreferences accessibility) { var firstStop = stopForTest("TEST:START", wheelchair, 0.0, 0.0); var lastStop = stopForTest("TEST:END", wheelchair, 0.0, 0.0); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferGeneratorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferGeneratorTest.java index 230097ebfc3..4283e5cca62 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferGeneratorTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferGeneratorTest.java @@ -15,6 +15,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.framework.time.TimeUtils; import org.opentripplanner.model.transfer.TransferConstraint; import org.opentripplanner.raptor._data.RaptorTestConstants; @@ -28,7 +29,6 @@ import org.opentripplanner.raptor.api.path.TransitPathLeg; import org.opentripplanner.raptor.spi.DefaultSlackProvider; import org.opentripplanner.raptor.spi.RaptorSlackProvider; -import org.opentripplanner.test.support.VariableSource; public class TransferGeneratorTest implements RaptorTestConstants { @@ -497,24 +497,26 @@ void findTransferWithLongMinTimeTransfer() { // TODO: here we check that minimum transfer time and slack are NOT added up, but perhaps that is // asserting the wrong behaviour - static Stream minTransferTimeSlackCases = Stream.of( - // transfer takes 1 min plus 0 slack, passenger will make it - Arguments.of(ofMinutes(1), ofMinutes(0), true), - // slack is 30 minutes, passenger won't make the connection - Arguments.of(ofMinutes(1), ofMinutes(30), false), - // tight since 8 minutes slack + 1 min transfer time but still less than the 10 minutes required - Arguments.of(ofMinutes(1), ofMinutes(8), true), - // transfer slack is ignored since minimumTransferTime is short - Arguments.of(ofMinutes(1), ofMinutes(9), true), - Arguments.of(ofMinutes(11), ofMinutes(0), false), - Arguments.of(ofMinutes(9), ofMinutes(1), true), - Arguments.of(ofMinutes(0), ofMinutes(11), false) - ); + static Stream minTransferTimeSlackCases() { + return Stream.of( + // transfer takes 1 min plus 0 slack, passenger will make it + Arguments.of(ofMinutes(1), ofMinutes(0), true), + // slack is 30 minutes, passenger won't make the connection + Arguments.of(ofMinutes(1), ofMinutes(30), false), + // tight since 8 minutes slack + 1 min transfer time but still less than the 10 minutes required + Arguments.of(ofMinutes(1), ofMinutes(8), true), + // transfer slack is ignored since minimumTransferTime is short + Arguments.of(ofMinutes(1), ofMinutes(9), true), + Arguments.of(ofMinutes(11), ofMinutes(0), false), + Arguments.of(ofMinutes(9), ofMinutes(1), true), + Arguments.of(ofMinutes(0), ofMinutes(11), false) + ); + } @ParameterizedTest( name = "minimum transfer time of {0}, transfer slack of {1} should expectTransfer={2} on 10 min transfer window" ) - @VariableSource("minTransferTimeSlackCases") + @MethodSource("minTransferTimeSlackCases") void includeTransferSlackInMinimumTransferTime( Duration minTransferTime, Duration transferSlack, diff --git a/src/test/java/org/opentripplanner/routing/api/request/WheelchairPreferencesTest.java b/src/test/java/org/opentripplanner/routing/api/request/WheelchairPreferencesTest.java index 157235c4483..9dd533704c7 100644 --- a/src/test/java/org/opentripplanner/routing/api/request/WheelchairPreferencesTest.java +++ b/src/test/java/org/opentripplanner/routing/api/request/WheelchairPreferencesTest.java @@ -7,20 +7,22 @@ import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.routing.api.request.preference.WheelchairPreferences; -import org.opentripplanner.test.support.VariableSource; class WheelchairPreferencesTest { - static Stream roundingTestCases = Stream.of( - Arguments.of(0.33333333333, 0.33, 0.333), - Arguments.of(0.77777777777, 0.78, 0.778) - ); + static Stream roundingTestCases() { + return Stream.of( + Arguments.of(0.33333333333, 0.33, 0.333), + Arguments.of(0.77777777777, 0.78, 0.778) + ); + } @ParameterizedTest( name = "Normalize value of {0} to rounded value {1} (maxSlope) and {2} (reluctance fields)" ) - @VariableSource("roundingTestCases") + @MethodSource("roundingTestCases") void testConstructorNormalization(double raw, double rounded2, double rounded3) { var roundedRequest = WheelchairPreferences .of() @@ -39,41 +41,43 @@ void testConstructorNormalization(double raw, double rounded2, double rounded3) assertEquals(roundedRequest.slopeExceededReluctance(), rounded2); } - static Stream toStringTestCases = Stream.of( - Arguments.of(DEFAULT, "WheelchairPreferences{}"), - Arguments.of( - WheelchairPreferences.of().withElevatorOnlyAccessible().build(), - "WheelchairPreferences{elevator: OnlyConsiderAccessible}" - ), - Arguments.of( - WheelchairPreferences.of().withTrip(DEFAULT_COSTS).build(), - "WheelchairPreferences{trip: AccessibilityPreferences{}}" - ), - Arguments.of( - WheelchairPreferences.of().withTrip(it -> it.withInaccessibleCost(100)).build(), - "WheelchairPreferences{trip: AccessibilityPreferences{inaccessibleCost: $100}}" - ), - Arguments.of( - WheelchairPreferences.of().withTripCost(99, 100).build(), - "WheelchairPreferences{trip: AccessibilityPreferences{unknownCost: $99, inaccessibleCost: $100}}" - ), - Arguments.of( - WheelchairPreferences - .of() - .withTripCost(10, 100) - .withStopCost(20, 200) - .withElevatorCost(30, 300) - .withInaccessibleStreetReluctance(1.0) - .withMaxSlope(0.123) - .withSlopeExceededReluctance(3) - .withStairsReluctance(4) - .build(), - "WheelchairPreferences{trip: AccessibilityPreferences{unknownCost: $10, inaccessibleCost: $100}, stop: AccessibilityPreferences{unknownCost: $20, inaccessibleCost: $200}, elevator: AccessibilityPreferences{unknownCost: $30, inaccessibleCost: $300}, inaccessibleStreetReluctance: 1.0, maxSlope: 0.123, slopeExceededReluctance: 3.0, stairsReluctance: 4.0}" - ) - ); + static Stream toStringTestCases() { + return Stream.of( + Arguments.of(DEFAULT, "WheelchairPreferences{}"), + Arguments.of( + WheelchairPreferences.of().withElevatorOnlyAccessible().build(), + "WheelchairPreferences{elevator: OnlyConsiderAccessible}" + ), + Arguments.of( + WheelchairPreferences.of().withTrip(DEFAULT_COSTS).build(), + "WheelchairPreferences{trip: AccessibilityPreferences{}}" + ), + Arguments.of( + WheelchairPreferences.of().withTrip(it -> it.withInaccessibleCost(100)).build(), + "WheelchairPreferences{trip: AccessibilityPreferences{inaccessibleCost: $100}}" + ), + Arguments.of( + WheelchairPreferences.of().withTripCost(99, 100).build(), + "WheelchairPreferences{trip: AccessibilityPreferences{unknownCost: $99, inaccessibleCost: $100}}" + ), + Arguments.of( + WheelchairPreferences + .of() + .withTripCost(10, 100) + .withStopCost(20, 200) + .withElevatorCost(30, 300) + .withInaccessibleStreetReluctance(1.0) + .withMaxSlope(0.123) + .withSlopeExceededReluctance(3) + .withStairsReluctance(4) + .build(), + "WheelchairPreferences{trip: AccessibilityPreferences{unknownCost: $10, inaccessibleCost: $100}, stop: AccessibilityPreferences{unknownCost: $20, inaccessibleCost: $200}, elevator: AccessibilityPreferences{unknownCost: $30, inaccessibleCost: $300}, inaccessibleStreetReluctance: 1.0, maxSlope: 0.123, slopeExceededReluctance: 3.0, stairsReluctance: 4.0}" + ) + ); + } @ParameterizedTest(name = "Verify toString() value is {1}") - @VariableSource("toStringTestCases") + @MethodSource("toStringTestCases") void testToString(WheelchairPreferences subject, String expected) { assertEquals(expected, subject.toString()); } diff --git a/src/test/java/org/opentripplanner/routing/api/request/framework/LinearFunctionSerializationTest.java b/src/test/java/org/opentripplanner/routing/api/request/framework/LinearFunctionSerializationTest.java index 9701fb412cc..e1785db61b1 100644 --- a/src/test/java/org/opentripplanner/routing/api/request/framework/LinearFunctionSerializationTest.java +++ b/src/test/java/org/opentripplanner/routing/api/request/framework/LinearFunctionSerializationTest.java @@ -11,9 +11,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.framework.time.DurationUtils; import org.opentripplanner.test.support.TestTableParser; -import org.opentripplanner.test.support.VariableSource; class LinearFunctionSerializationTest { @@ -21,25 +21,27 @@ class LinearFunctionSerializationTest { private static final Duration D1h = Duration.ofSeconds(3600); @SuppressWarnings("unused") - static Stream parseTestCases = TestTableParser.of( - """ - # INPUT || EXPECTED - # || CONSTANT | COEFFICIENT - 0+0t || 0s | 0.0 - 1+0.0111 t || 1s | 0.01 - 120 + 0.111 t || 2m | 0.11 - 120 + 0.111 t || 2m | 0.11 - 12.0 + 0 t || 12s | 0.0 - 2h3m + 1.111 t || 2h3m | 1.11 - 2h3m + 2.111 t || 2h3m | 2.1 - 3h + 5.111 t || 3h | 5.1 - 7m + 10.1 x || 7m | 10.0 - PT7s + 10.1 x || 7s | 10.0 - """ - ); + static Stream parseTestCases() { + return TestTableParser.of( + """ + # INPUT || EXPECTED + # || CONSTANT | COEFFICIENT + 0+0t || 0s | 0.0 + 1+0.0111 t || 1s | 0.01 + 120 + 0.111 t || 2m | 0.11 + 120 + 0.111 t || 2m | 0.11 + 12.0 + 0 t || 12s | 0.0 + 2h3m + 1.111 t || 2h3m | 1.11 + 2h3m + 2.111 t || 2h3m | 2.1 + 3h + 5.111 t || 3h | 5.1 + 7m + 10.1 x || 7m | 10.0 + PT7s + 10.1 x || 7s | 10.0 + """ + ); + } @ParameterizedTest - @VariableSource("parseTestCases") + @MethodSource("parseTestCases") void parseTest(String input, String expectedConstant, double expectedCoefficient) { Optional result = LinearFunctionSerialization.parse( input, diff --git a/src/test/java/org/opentripplanner/routing/api/request/preference/TimeSlopeSafetyTriangleTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/TimeSlopeSafetyTriangleTest.java index 7b2fe416290..2ba7f2569e4 100644 --- a/src/test/java/org/opentripplanner/routing/api/request/preference/TimeSlopeSafetyTriangleTest.java +++ b/src/test/java/org/opentripplanner/routing/api/request/preference/TimeSlopeSafetyTriangleTest.java @@ -7,26 +7,28 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; -import org.opentripplanner.test.support.VariableSource; +import org.junit.jupiter.params.provider.MethodSource; public class TimeSlopeSafetyTriangleTest { float DELTA = 0.001f; @SuppressWarnings("unused") - static Stream testCases = Stream.of( - // Input: time | slope | safety || Expected: time | slope | safety - Arguments.of(0.5, 0.3, 0.2, 0.5, 0.3, 0.2, "Exact"), - Arguments.of(1d, 1d, 1d, 0.33, 0.33, 0.34, "Greater than 1"), - Arguments.of(30d, 10d, 20d, 0.5, 0.17, 0.33, "Greater than 1 - big"), - Arguments.of(1d, 0d, 0d, 1d, 0d, 0d, "Two zeros"), - Arguments.of(0d, 0d, 0d, 0.33, 0.33, 0.34, "All zeros"), - Arguments.of(0.1, -1d, -1d, 1d, 0d, 0d, "Less than zero"), - Arguments.of(0d, 0.07, 0.93, 0d, 0.07, 0.93, "None precise round-off: " + (1.0 - 0.07)) - ); + static Stream testCases() { + return Stream.of( + // Input: time | slope | safety || Expected: time | slope | safety + Arguments.of(0.5, 0.3, 0.2, 0.5, 0.3, 0.2, "Exact"), + Arguments.of(1d, 1d, 1d, 0.33, 0.33, 0.34, "Greater than 1"), + Arguments.of(30d, 10d, 20d, 0.5, 0.17, 0.33, "Greater than 1 - big"), + Arguments.of(1d, 0d, 0d, 1d, 0d, 0d, "Two zeros"), + Arguments.of(0d, 0d, 0d, 0.33, 0.33, 0.34, "All zeros"), + Arguments.of(0.1, -1d, -1d, 1d, 0d, 0d, "Less than zero"), + Arguments.of(0d, 0.07, 0.93, 0d, 0.07, 0.93, "None precise round-off: " + (1.0 - 0.07)) + ); + } @ParameterizedTest(name = "Time/slope/safety: | {0} {1} {2} || {3} {4} {5} | {6}") - @VariableSource("testCases") + @MethodSource("testCases") public void test( double inTime, double inSlope, diff --git a/src/test/java/org/opentripplanner/routing/core/MoneyTest.java b/src/test/java/org/opentripplanner/routing/core/MoneyTest.java index 2323b396d5c..5f197708f1d 100644 --- a/src/test/java/org/opentripplanner/routing/core/MoneyTest.java +++ b/src/test/java/org/opentripplanner/routing/core/MoneyTest.java @@ -14,7 +14,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; -import org.opentripplanner.test.support.VariableSource; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.transit.model.basic.Money; class MoneyTest { @@ -27,36 +27,40 @@ class MoneyTest { private static final Money twoDollars = Money.usDollars(2); static Money threeEuroTwelve = Money.euros(3.12f); - static Stream testCases = Stream.of( - of(oneDollar, Locale.US, "$1.00"), - of(oneDollar, Locale.GERMANY, "1,00 $"), - of(Money.euros(1), Locale.GERMANY, "1,00 €"), - of(oneDollar, NORWEGIAN_BOKMAL, "USD 1,00"), - //of(oneDollar, NORWEGIAN_NYNORSK, "1.00 USD"), - of(hundredNOK, NORWEGIAN_BOKMAL, "kr 100,00") - //of(hundredNOK, NORWEGIAN_NYNORSK, "100.00 kr") - ); + static Stream testCases() { + return Stream.of( + of(oneDollar, Locale.US, "$1.00"), + of(oneDollar, Locale.GERMANY, "1,00 $"), + of(Money.euros(1), Locale.GERMANY, "1,00 €"), + of(oneDollar, NORWEGIAN_BOKMAL, "USD 1,00"), + //of(oneDollar, NORWEGIAN_NYNORSK, "1.00 USD"), + of(hundredNOK, NORWEGIAN_BOKMAL, "kr 100,00") + //of(hundredNOK, NORWEGIAN_NYNORSK, "100.00 kr") + ); + } @ParameterizedTest(name = "{0} with locale {1} should localise to \"{2}\"") - @VariableSource("testCases") + @MethodSource("testCases") void localize(Money money, Locale locale, String expected) { var localized = money.localize(locale); assertEquals(expected, localized); } - static Stream amountCases = Stream.of( - of(oneDollar, 1.0f), - of(threeEuroTwelve, 3.12f), - of(Money.euros(3.1f), 3.1f), - of(Money.euros(999.99f), 999.99f), - of(hundredNOK, 100.0f), - // Yen doesn't have fractional digits - of(yen(1000), 1000f), - of(yen(9999), 9999f) - ); + static Stream amountCases() { + return Stream.of( + of(oneDollar, 1.0f), + of(threeEuroTwelve, 3.12f), + of(Money.euros(3.1f), 3.1f), + of(Money.euros(999.99f), 999.99f), + of(hundredNOK, 100.0f), + // Yen doesn't have fractional digits + of(yen(1000), 1000f), + of(yen(9999), 9999f) + ); + } @ParameterizedTest - @VariableSource("amountCases") + @MethodSource("amountCases") void fractionalAmount(Money money, float expected) { var fractionalAmount = money.fractionalAmount(); assertEquals(expected, fractionalAmount.floatValue()); diff --git a/src/test/java/org/opentripplanner/service/worldenvelope/model/WorldEnvelopeTest.java b/src/test/java/org/opentripplanner/service/worldenvelope/model/WorldEnvelopeTest.java index bff99800fdb..136caf31a5b 100644 --- a/src/test/java/org/opentripplanner/service/worldenvelope/model/WorldEnvelopeTest.java +++ b/src/test/java/org/opentripplanner/service/worldenvelope/model/WorldEnvelopeTest.java @@ -5,6 +5,9 @@ import java.util.List; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.framework.geometry.WgsCoordinate; class WorldEnvelopeTest { @@ -18,83 +21,102 @@ class WorldEnvelopeTest { private static final int W60 = -60; private static final int W170 = -170; - private static final WorldEnvelope EAST = WorldEnvelope - .of() - .expandToIncludeStreetEntities(S10, E50) - .expandToIncludeStreetEntities(S20, E160) - .build(); - private static final WorldEnvelope WEST = WorldEnvelope - .of() - .expandToIncludeStreetEntities(N30, W60) - .expandToIncludeStreetEntities(N40, W170) - .build(); - private static final WorldEnvelope GREENWICH = WorldEnvelope - .of() - .expandToIncludeStreetEntities(N30, W60) - .expandToIncludeStreetEntities(S10, E50) - .build(); - private static final WorldEnvelope MERIDIAN_180 = WorldEnvelope - .of() - .expandToIncludeStreetEntities(N40, W170) - .expandToIncludeStreetEntities(S20, E160) - .build(); - - @Test - void testEast() { - var expectedCenter = new WgsCoordinate(-15d, 105d); - - assertEquals(S20, EAST.lowerLeft().latitude()); - assertEquals(E50, EAST.lowerLeft().longitude()); - assertEquals(S10, EAST.upperRight().latitude()); - assertEquals(E160, EAST.upperRight().longitude()); - assertEquals(expectedCenter, EAST.meanCenter()); - assertEquals(expectedCenter, EAST.center()); - assertTrue(EAST.transitMedianCenter().isEmpty()); + /** + * To make sure we cover all cases we add a case for each combination of: + * - latitude + * - south hemisphere + * - north hemisphere + * - both sides of the equator + * - longitude + * - east side of 0º (Greenwich) + * - west side of 0º + * - both sides of 0º + * - both sides of 180º + * Skip cases for North- and South-pole - not relevant - obscure cases) + */ + static List testCases() { + return List.of( + // name, lower-lat, left-lon, upper-lat, right-lon, center-lat, center-lon + Arguments.of("South-East", S20, E50, S10, E160, -15d, 105d), + Arguments.of("Equator-East", S10, E50, N30, E160, 10d, 105d), + Arguments.of("North-East", N30, E50, N40, E160, 35d, 105d), + Arguments.of("South-West", S20, W170, S10, W60, -15d, -115d), + Arguments.of("Equator-West", S10, W170, N30, W60, 10d, -115d), + Arguments.of("North-West", N30, W170, N40, W60, 35d, -115d), + Arguments.of("North-Greenwich", N30, W60, N40, E50, 35d, -5d), + Arguments.of("Equator-Greenwich", S10, W60, N30, E50, 10d, -5d), + Arguments.of("South-Greenwich", S20, W60, S10, E50, -15d, -5d), + Arguments.of("North-180º", N30, E160, N40, W170, 35d, 175d), + Arguments.of("Equator-180º", S10, E160, N30, W170, 10d, 175d), + Arguments.of("South-180º", S20, E160, S10, W170, -15d, 175d) + ); } - @Test - void transitMedianCenter() { - var expectedCenter = new WgsCoordinate(S10, E50); + @ParameterizedTest + @MethodSource("testCases") + void testWorldEnvelope( + String name, + double lowerLat, + double leftLon, + double upperLat, + double rightLon, + double centerLat, + double centerLon + ) { + // Add a point close to the center + var median = new WgsCoordinate(centerLat + 1.0, centerLon + 1.0); - var subject = WorldEnvelope + // WorldEnvelope should normalize to lower-left and upper-right + // Add lower-right & upper-left the world-envelope + var subjectWithoutMedian = WorldEnvelope + .of() + .expandToIncludeStreetEntities(lowerLat, rightLon) + .expandToIncludeStreetEntities(upperLat, leftLon) + .build(); + // Add the ~middle point between each corner of the envelope + median point + // We offset the one center value to the "other" side of the median by adding 2.0 + var subjectWithMedian = WorldEnvelope .of() .expandToIncludeTransitEntities( List.of( - new WgsCoordinate(S10, E50), - new WgsCoordinate(S20, E160), - new WgsCoordinate(N40, W60) + new WgsCoordinate(upperLat, centerLon), + new WgsCoordinate(lowerLat, centerLon + 2d), + new WgsCoordinate(centerLat, rightLon), + new WgsCoordinate(centerLat + 2d, leftLon), + median ), WgsCoordinate::latitude, WgsCoordinate::longitude ) .build(); - assertTrue(subject.transitMedianCenter().isPresent(), subject.transitMedianCenter().toString()); - assertEquals(expectedCenter, subject.transitMedianCenter().get()); - assertEquals(expectedCenter, subject.center()); - assertEquals( - "WorldEnvelope{lowerLeft: (-20.0, -60.0), upperRight: (40.0, 160.0), meanCenter: (10.0, 50.0), transitMedianCenter: (-10.0, 50.0)}", - subject.toString() + for (WorldEnvelope subject : List.of(subjectWithoutMedian, subjectWithMedian)) { + assertEquals(lowerLat, subject.lowerLeft().latitude(), name + " lower-latitude"); + assertEquals(leftLon, subject.lowerLeft().longitude(), name + " left-longitude"); + assertEquals(upperLat, subject.upperRight().latitude(), name + " upper-latitude"); + assertEquals(rightLon, subject.upperRight().longitude(), name + " right-longitude"); + assertEquals(centerLat, subject.meanCenter().latitude(), name + " center-latitude"); + assertEquals(centerLon, subject.meanCenter().longitude(), name + " center-longitude"); + } + + assertTrue( + subjectWithoutMedian.medianCenter().isEmpty(), + "First envelope does not have a median" ); + assertTrue(subjectWithMedian.medianCenter().isPresent(), "Second envelope does have a median"); + assertEquals(median, subjectWithMedian.medianCenter().get(), name + " median"); } @Test - void testToString() { - assertEquals( - "WorldEnvelope{lowerLeft: (-20.0, 50.0), upperRight: (-10.0, 160.0), meanCenter: (-15.0, 105.0)}", - EAST.toString() - ); - assertEquals( - "WorldEnvelope{lowerLeft: (30.0, -170.0), upperRight: (40.0, -60.0), meanCenter: (35.0, -115.0)}", - WEST.toString() - ); - assertEquals( - "WorldEnvelope{lowerLeft: (-10.0, -60.0), upperRight: (30.0, 50.0), meanCenter: (10.0, -5.0)}", - GREENWICH.toString() - ); + void testWorldEnvelopeToString() { assertEquals( - "WorldEnvelope{lowerLeft: (-20.0, 160.0), upperRight: (40.0, -170.0), meanCenter: (10.0, 175.0)}", - MERIDIAN_180.toString() + "WorldEnvelope{lowerLeft: (-10.0, -60.0), upperRight: (40.0, 50.0), meanCenter: (15.0, -5.0)}", + WorldEnvelope + .of() + .expandToIncludeStreetEntities(S10, E50) + .expandToIncludeStreetEntities(N40, W60) + .build() + .toString() ); } } diff --git a/src/test/java/org/opentripplanner/standalone/config/routerequest/WheelchairConfigTest.java b/src/test/java/org/opentripplanner/standalone/config/routerequest/WheelchairConfigTest.java index 98b18f45d05..64216e5d83f 100644 --- a/src/test/java/org/opentripplanner/standalone/config/routerequest/WheelchairConfigTest.java +++ b/src/test/java/org/opentripplanner/standalone/config/routerequest/WheelchairConfigTest.java @@ -11,59 +11,61 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.routing.api.request.preference.AccessibilityPreferences; -import org.opentripplanner.test.support.VariableSource; class WheelchairConfigTest { - static Stream mapAccessibilityPreferencesTestCases = Stream.of( - Arguments.of( - "default ofOnlyAccessible()", - "{}", - ofOnlyAccessible(), - DEFAULT_COSTS, - ofOnlyAccessible() - ), - Arguments.of("default cost", "{}", ofCost(100, 200), ofCost(100, 200), ofCost(100, 200)), - Arguments.of( - "onlyConsiderAccessible with default costs", - "{\"onlyConsiderAccessible\": true}", - DEFAULT_COSTS, - DEFAULT_COSTS, - ofOnlyAccessible() - ), - Arguments.of( - "Default costs with default ofOnlyAccessible()", - "{\"onlyConsiderAccessible\": false}", - ofOnlyAccessible(), - DEFAULT_COSTS, - DEFAULT_COSTS - ), - Arguments.of( - "Only unknownCost set with default ofOnlyAccessible()", - "{\"unknownCost\": 100}", - ofOnlyAccessible(), - DEFAULT_COSTS, - ofCost(100, DEFAULT_COSTS.inaccessibleCost()) - ), - Arguments.of( - "Only inaccessibleCost set with default ofOnlyAccessible()", - "{\"inaccessibleCost\": 100}", - ofOnlyAccessible(), - DEFAULT_COSTS, - ofCost(DEFAULT_COSTS.unknownCost(), 100) - ), - Arguments.of( - "All values set", - "{\"unknownCost\": 200, \"inaccessibleCost\": 100, \"onlyConsiderAccessible\": false}", - ofOnlyAccessible(), - DEFAULT_COSTS, - ofCost(200, 100) - ) - ); + static Stream mapAccessibilityPreferencesTestCases() { + return Stream.of( + Arguments.of( + "default ofOnlyAccessible()", + "{}", + ofOnlyAccessible(), + DEFAULT_COSTS, + ofOnlyAccessible() + ), + Arguments.of("default cost", "{}", ofCost(100, 200), ofCost(100, 200), ofCost(100, 200)), + Arguments.of( + "onlyConsiderAccessible with default costs", + "{\"onlyConsiderAccessible\": true}", + DEFAULT_COSTS, + DEFAULT_COSTS, + ofOnlyAccessible() + ), + Arguments.of( + "Default costs with default ofOnlyAccessible()", + "{\"onlyConsiderAccessible\": false}", + ofOnlyAccessible(), + DEFAULT_COSTS, + DEFAULT_COSTS + ), + Arguments.of( + "Only unknownCost set with default ofOnlyAccessible()", + "{\"unknownCost\": 100}", + ofOnlyAccessible(), + DEFAULT_COSTS, + ofCost(100, DEFAULT_COSTS.inaccessibleCost()) + ), + Arguments.of( + "Only inaccessibleCost set with default ofOnlyAccessible()", + "{\"inaccessibleCost\": 100}", + ofOnlyAccessible(), + DEFAULT_COSTS, + ofCost(DEFAULT_COSTS.unknownCost(), 100) + ), + Arguments.of( + "All values set", + "{\"unknownCost\": 200, \"inaccessibleCost\": 100, \"onlyConsiderAccessible\": false}", + ofOnlyAccessible(), + DEFAULT_COSTS, + ofCost(200, 100) + ) + ); + } @ParameterizedTest(name = "{0}") - @VariableSource("mapAccessibilityPreferencesTestCases") + @MethodSource("mapAccessibilityPreferencesTestCases") void testMapAccessibilityPreferences( String name, String json, diff --git a/src/test/java/org/opentripplanner/standalone/server/EtagRequestFilterTest.java b/src/test/java/org/opentripplanner/standalone/server/EtagRequestFilterTest.java index 5adf8264d8e..ae19643db72 100644 --- a/src/test/java/org/opentripplanner/standalone/server/EtagRequestFilterTest.java +++ b/src/test/java/org/opentripplanner/standalone/server/EtagRequestFilterTest.java @@ -16,27 +16,29 @@ import org.jets3t.service.utils.Mimetypes; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.test.support.HttpForTest; -import org.opentripplanner.test.support.VariableSource; class EtagRequestFilterTest { static final String vectorTilesResponse = "some vector tiles"; static final String vectorTilesEtag = "\"20c17790\""; - static Stream etagCases = Stream.of( - Arguments.of("GET", 200, APPLICATION_X_PROTOBUF, bytes(vectorTilesResponse), vectorTilesEtag), - Arguments.of("GET", 404, APPLICATION_X_PROTOBUF, bytes("hello123"), null), - Arguments.of("GET", 200, "application/json", bytes("{}"), null), - Arguments.of("POST", 200, APPLICATION_X_PROTOBUF, bytes("hello123"), null), - Arguments.of("GET", 200, APPLICATION_X_PROTOBUF, bytes(""), null), - Arguments.of("POST", 200, Mimetypes.MIMETYPE_HTML, bytes(""), null) - ); + static Stream etagCases() { + return Stream.of( + Arguments.of("GET", 200, APPLICATION_X_PROTOBUF, bytes(vectorTilesResponse), vectorTilesEtag), + Arguments.of("GET", 404, APPLICATION_X_PROTOBUF, bytes("hello123"), null), + Arguments.of("GET", 200, "application/json", bytes("{}"), null), + Arguments.of("POST", 200, APPLICATION_X_PROTOBUF, bytes("hello123"), null), + Arguments.of("GET", 200, APPLICATION_X_PROTOBUF, bytes(""), null), + Arguments.of("POST", 200, Mimetypes.MIMETYPE_HTML, bytes(""), null) + ); + } @ParameterizedTest( name = "{0} request with response status={1} type={2}, entity={3} produces ETag header {4}" ) - @VariableSource("etagCases") + @MethodSource("etagCases") void writeEtag( String method, int status, @@ -56,13 +58,15 @@ void writeEtag( assertEquals(expectedEtag, response.getHeaderString(EtagRequestFilter.HEADER_ETAG)); } - static Stream ifNoneMatchCases = Stream.of( - Arguments.of("XXX", 200, bytes(vectorTilesResponse)), - Arguments.of(vectorTilesEtag, 304, null) - ); + static Stream ifNoneMatchCases() { + return Stream.of( + Arguments.of("XXX", 200, bytes(vectorTilesResponse)), + Arguments.of(vectorTilesEtag, 304, null) + ); + } @ParameterizedTest(name = "If-None-Match header of {0} should lead to a status code of {2}") - @VariableSource("ifNoneMatchCases") + @MethodSource("ifNoneMatchCases") void ifNoneMatch(String ifNoneMatch, int expectedStatus, byte[] expectedEntity) throws IOException { var request = HttpForTest.containerRequest("GET"); diff --git a/src/test/java/org/opentripplanner/standalone/server/RequestTraceFilterTest.java b/src/test/java/org/opentripplanner/standalone/server/RequestTraceFilterTest.java index 126ca887cc3..8198444f6be 100644 --- a/src/test/java/org/opentripplanner/standalone/server/RequestTraceFilterTest.java +++ b/src/test/java/org/opentripplanner/standalone/server/RequestTraceFilterTest.java @@ -7,7 +7,7 @@ import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; -import org.opentripplanner.test.support.VariableSource; +import org.junit.jupiter.params.provider.MethodSource; class RequestTraceFilterTest { @@ -19,22 +19,24 @@ class RequestTraceFilterTest { private static final String A_TOO_LONG_STRING = A_VERY_LONG_STRING + "1"; @SuppressWarnings("unused") - private static final Stream headerCheckTestCases = Stream.of( - Arguments.of(true, "ok"), - Arguments.of(true, "special characters: -_,;.:!#$%&/(){}[]=?+"), - Arguments.of(true, "quote: \"quoted\" 'single' `back` ´forward´"), - Arguments.of(true, "international characters: æøå öâò≈∰🧐"), - Arguments.of(true, A_VERY_LONG_STRING), - Arguments.of(false, A_TOO_LONG_STRING), - Arguments.of(false, "Vertical space new-line: -\n-"), - Arguments.of(false, "Vertical space return: -\r-"), - Arguments.of(false, "Vertical space form-feed: -\f-"), - Arguments.of(false, "Control character 0x01: -\u0001-"), - Arguments.of(false, "Control character 0x19: -\u0019-") - ); + private static final Stream headerCheckTestCases() { + return Stream.of( + Arguments.of(true, "ok"), + Arguments.of(true, "special characters: -_,;.:!#$%&/(){}[]=?+"), + Arguments.of(true, "quote: \"quoted\" 'single' `back` ´forward´"), + Arguments.of(true, "international characters: æøå öâò≈∰🧐"), + Arguments.of(true, A_VERY_LONG_STRING), + Arguments.of(false, A_TOO_LONG_STRING), + Arguments.of(false, "Vertical space new-line: -\n-"), + Arguments.of(false, "Vertical space return: -\r-"), + Arguments.of(false, "Vertical space form-feed: -\f-"), + Arguments.of(false, "Control character 0x01: -\u0001-"), + Arguments.of(false, "Control character 0x19: -\u0019-") + ); + } @ParameterizedTest - @VariableSource("headerCheckTestCases") + @MethodSource("headerCheckTestCases") void headerCheck(boolean expectedMatch, String input) { assertEquals( expectedMatch, diff --git a/src/test/java/org/opentripplanner/street/model/edge/ElevatorHopEdgeTest.java b/src/test/java/org/opentripplanner/street/model/edge/ElevatorHopEdgeTest.java index 5d7c9d1213f..8efa6940d20 100644 --- a/src/test/java/org/opentripplanner/street/model/edge/ElevatorHopEdgeTest.java +++ b/src/test/java/org/opentripplanner/street/model/edge/ElevatorHopEdgeTest.java @@ -8,13 +8,13 @@ import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.routing.api.request.preference.AccessibilityPreferences; import org.opentripplanner.routing.api.request.preference.WheelchairPreferences; import org.opentripplanner.street.model.StreetTraversalPermission; import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.street.search.request.StreetSearchRequest; import org.opentripplanner.street.search.state.State; -import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model.basic.Accessibility; class ElevatorHopEdgeTest { @@ -22,12 +22,12 @@ class ElevatorHopEdgeTest { Vertex from = intersectionVertex(0, 0); Vertex to = intersectionVertex(1, 1); - static Stream noTraverse = Stream - .of(Accessibility.NO_INFORMATION, Accessibility.NOT_POSSIBLE) - .map(Arguments::of); + static Stream noTraverse() { + return Stream.of(Accessibility.NO_INFORMATION, Accessibility.NOT_POSSIBLE).map(Arguments::of); + } @ParameterizedTest(name = "{0} should be allowed to traverse when requesting onlyAccessible") - @VariableSource("noTraverse") + @MethodSource("noTraverse") void shouldNotTraverse(Accessibility wheelchair) { var req = StreetSearchRequest.of(); AccessibilityPreferences feature = AccessibilityPreferences.ofOnlyAccessible(); @@ -52,17 +52,19 @@ void shouldNotTraverse(Accessibility wheelchair) { assertTrue(State.isEmpty(result)); } - static Stream all = Stream.of( - // no extra cost - Arguments.of(Accessibility.POSSIBLE, 20), - // low extra cost - Arguments.of(Accessibility.NO_INFORMATION, 40), - // high extra cost - Arguments.of(Accessibility.NOT_POSSIBLE, 3620) - ); + static Stream all() { + return Stream.of( + // no extra cost + Arguments.of(Accessibility.POSSIBLE, 20), + // low extra cost + Arguments.of(Accessibility.NO_INFORMATION, 40), + // high extra cost + Arguments.of(Accessibility.NOT_POSSIBLE, 3620) + ); + } @ParameterizedTest(name = "{0} should allowed to traverse with a cost of {1}") - @VariableSource("all") + @MethodSource("all") void allowByDefault(Accessibility wheelchair, double expectedCost) { var req = StreetSearchRequest.of().build(); var result = traverse(wheelchair, req)[0]; diff --git a/src/test/java/org/opentripplanner/street/model/edge/EscalatorEdgeTest.java b/src/test/java/org/opentripplanner/street/model/edge/EscalatorEdgeTest.java index 1c7fdba7ae7..60859290646 100644 --- a/src/test/java/org/opentripplanner/street/model/edge/EscalatorEdgeTest.java +++ b/src/test/java/org/opentripplanner/street/model/edge/EscalatorEdgeTest.java @@ -7,22 +7,24 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.routing.api.request.StreetMode; import org.opentripplanner.street.model.vertex.SimpleVertex; import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.street.search.request.StreetSearchRequest; import org.opentripplanner.street.search.state.State; -import org.opentripplanner.test.support.VariableSource; class EscalatorEdgeTest { Vertex from = new SimpleVertex("A", 10, 10); Vertex to = new SimpleVertex("B", 10.001, 10.001); - static Stream args = Stream.of(Arguments.of(1.5, 150), Arguments.of(3.0, 300)); + static Stream args() { + return Stream.of(Arguments.of(1.5, 150), Arguments.of(3.0, 300)); + } @ParameterizedTest(name = "escalatorReluctance of {0} should lead to traversal costs of {1}") - @VariableSource("args") + @MethodSource("args") void testWalking(double escalatorReluctance, double expectedWeight) { var edge = EscalatorEdge.createEscalatorEdge(from, to, 45); var req = StreetSearchRequest diff --git a/src/test/java/org/opentripplanner/street/model/edge/PathwayEdgeTest.java b/src/test/java/org/opentripplanner/street/model/edge/PathwayEdgeTest.java index 9d4b1ff4fe2..6929f665938 100644 --- a/src/test/java/org/opentripplanner/street/model/edge/PathwayEdgeTest.java +++ b/src/test/java/org/opentripplanner/street/model/edge/PathwayEdgeTest.java @@ -12,6 +12,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.framework.i18n.NonLocalizedString; import org.opentripplanner.routing.api.request.StreetMode; @@ -19,7 +20,6 @@ import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.street.search.request.StreetSearchRequest; import org.opentripplanner.street.search.state.State; -import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model.site.PathwayMode; class PathwayEdgeTest { @@ -141,20 +141,22 @@ void wheelchair() { assertEquals(300.0, state.getWeight()); } - static Stream slopeCases = Stream.of( - // no extra cost - Arguments.of(0.07, 120), - // no extra cost - Arguments.of(0.08, 120), - // 1 % above max - Arguments.of(0.09, 239), - // 1.1 % above the max slope, tiny extra cost - Arguments.of(0.091, 251), - // 1.15 % above the max slope, will incur larger cost - Arguments.of(0.0915, 257), - // 3 % above max slope, will incur very large cost - Arguments.of(0.11, 480) - ); + static Stream slopeCases() { + return Stream.of( + // no extra cost + Arguments.of(0.07, 120), + // no extra cost + Arguments.of(0.08, 120), + // 1 % above max + Arguments.of(0.09, 239), + // 1.1 % above the max slope, tiny extra cost + Arguments.of(0.091, 251), + // 1.15 % above the max slope, will incur larger cost + Arguments.of(0.0915, 257), + // 3 % above max slope, will incur very large cost + Arguments.of(0.11, 480) + ); + } /** * This makes sure that when you exceed the max slope in a wheelchair there isn't a hard cut-off @@ -164,7 +166,7 @@ void wheelchair() { * dramatically to the point where it's only used as a last resort. */ @ParameterizedTest(name = "slope of {0} should lead to traversal costs of {1}") - @VariableSource("slopeCases") + @MethodSource("slopeCases") void shouldScaleCostWithMaxSlope(double slope, long expectedCost) { var edge = PathwayEdge.createPathwayEdge( from, diff --git a/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeCostTest.java b/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeCostTest.java index 2ecfa51822a..acd02653941 100644 --- a/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeCostTest.java +++ b/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeCostTest.java @@ -8,23 +8,25 @@ import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.routing.api.request.StreetMode; import org.opentripplanner.street.model.StreetTraversalPermission; import org.opentripplanner.street.search.request.StreetSearchRequest; import org.opentripplanner.street.search.state.State; -import org.opentripplanner.test.support.VariableSource; class StreetEdgeCostTest { - static Stream walkReluctanceCases = Stream.of( - Arguments.of(0.5, 37), - Arguments.of(1, 75), - Arguments.of(2, 150), - Arguments.of(3, 225) - ); + static Stream walkReluctanceCases() { + return Stream.of( + Arguments.of(0.5, 37), + Arguments.of(1, 75), + Arguments.of(2, 150), + Arguments.of(3, 225) + ); + } @ParameterizedTest(name = "walkRelucance of {0} should lead to traversal costs of {1}") - @VariableSource("walkReluctanceCases") + @MethodSource("walkReluctanceCases") public void walkReluctance(double walkReluctance, long expectedCost) { double length = 100; var edge = new StreetEdgeBuilder<>() @@ -45,15 +47,17 @@ public void walkReluctance(double walkReluctance, long expectedCost) { assertEquals(76, result.getElapsedTimeSeconds()); } - static Stream bikeReluctanceCases = Stream.of( - Arguments.of(0.5, 10), - Arguments.of(1, 20), - Arguments.of(2, 40), - Arguments.of(3, 60) - ); + static Stream bikeReluctanceCases() { + return Stream.of( + Arguments.of(0.5, 10), + Arguments.of(1, 20), + Arguments.of(2, 40), + Arguments.of(3, 60) + ); + } @ParameterizedTest(name = "bikeReluctance of {0} should lead to traversal costs of {1}") - @VariableSource("bikeReluctanceCases") + @MethodSource("bikeReluctanceCases") public void bikeReluctance(double bikeReluctance, long expectedCost) { double length = 100; var edge = new StreetEdgeBuilder<>() @@ -75,15 +79,17 @@ public void bikeReluctance(double bikeReluctance, long expectedCost) { assertEquals(20, result.getElapsedTimeSeconds()); } - static Stream carReluctanceCases = Stream.of( - Arguments.of(0.5, 4), - Arguments.of(1, 8), - Arguments.of(2, 17), - Arguments.of(3, 26) - ); + static Stream carReluctanceCases() { + return Stream.of( + Arguments.of(0.5, 4), + Arguments.of(1, 8), + Arguments.of(2, 17), + Arguments.of(3, 26) + ); + } @ParameterizedTest(name = "carReluctance of {0} should lead to traversal costs of {1}") - @VariableSource("carReluctanceCases") + @MethodSource("carReluctanceCases") public void carReluctance(double carReluctance, long expectedCost) { double length = 100; var edge = new StreetEdgeBuilder<>() @@ -105,14 +111,12 @@ public void carReluctance(double carReluctance, long expectedCost) { assertEquals(9, result.getElapsedTimeSeconds()); } - static Stream stairsCases = Stream.of( - Arguments.of(1, 22), - Arguments.of(1.5, 33), - Arguments.of(3, 67) - ); + static Stream stairsCases() { + return Stream.of(Arguments.of(1, 22), Arguments.of(1.5, 33), Arguments.of(3, 67)); + } @ParameterizedTest(name = "stairs reluctance of {0} should lead to traversal costs of {1}") - @VariableSource("stairsCases") + @MethodSource("stairsCases") public void stairsReluctance(double stairsReluctance, long expectedCost) { double length = 10; var stairsEdge = new StreetEdgeBuilder<>() @@ -138,14 +142,12 @@ public void stairsReluctance(double stairsReluctance, long expectedCost) { assertEquals(15, (long) notStairsResult.weight); } - static Stream bikeStairsCases = Stream.of( - Arguments.of(1, 45), - Arguments.of(1.5, 67), - Arguments.of(3, 135) - ); + static Stream bikeStairsCases() { + return Stream.of(Arguments.of(1, 45), Arguments.of(1.5, 67), Arguments.of(3, 135)); + } @ParameterizedTest(name = "bike stairs reluctance of {0} should lead to traversal costs of {1}") - @VariableSource("bikeStairsCases") + @MethodSource("bikeStairsCases") public void bikeStairsReluctance(double stairsReluctance, long expectedCost) { double length = 10; var stairsEdge = new StreetEdgeBuilder<>() @@ -173,14 +175,12 @@ public void bikeStairsReluctance(double stairsReluctance, long expectedCost) { assertEquals(37, (long) notStairsResult.weight); } - static Stream walkSafetyCases = Stream.of( - Arguments.of(0, 15), - Arguments.of(0.5, 22), - Arguments.of(1, 30) - ); + static Stream walkSafetyCases() { + return Stream.of(Arguments.of(0, 15), Arguments.of(0.5, 22), Arguments.of(1, 30)); + } @ParameterizedTest(name = "walk safety factor of {0} should lead to traversal costs of {1}") - @VariableSource("walkSafetyCases") + @MethodSource("walkSafetyCases") public void walkSafetyFactor(double walkSafetyFactor, long expectedCost) { double length = 10; var safeEdge = new StreetEdgeBuilder<>() diff --git a/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeRentalTraversalTest.java b/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeRentalTraversalTest.java index 20682b6048b..44de661f327 100644 --- a/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeRentalTraversalTest.java +++ b/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeRentalTraversalTest.java @@ -17,6 +17,7 @@ import javax.annotation.Nonnull; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.routing.api.request.StreetMode; import org.opentripplanner.street.model.RentalFormFactor; import org.opentripplanner.street.model.StreetTraversalPermission; @@ -24,7 +25,6 @@ import org.opentripplanner.street.search.request.StreetSearchRequest; import org.opentripplanner.street.search.state.State; import org.opentripplanner.street.search.state.StateEditor; -import org.opentripplanner.test.support.VariableSource; public class StreetEdgeRentalTraversalTest { @@ -42,18 +42,20 @@ private static Stream baseCases(StreetTraversalPermission p) { ); } - static Stream allowedToTraverse = Stream - .of( - StreetTraversalPermission.ALL, - StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE, - StreetTraversalPermission.BICYCLE - ) - .flatMap(StreetEdgeRentalTraversalTest::baseCases); + static Stream allowedToTraverse() { + return Stream + .of( + StreetTraversalPermission.ALL, + StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE, + StreetTraversalPermission.BICYCLE + ) + .flatMap(StreetEdgeRentalTraversalTest::baseCases); + } @ParameterizedTest( name = "Form factor {0}, street mode {1} should be able to traverse edge with permission {2}" ) - @VariableSource("allowedToTraverse") + @MethodSource("allowedToTraverse") void scooterBicycleTraversal( RentalFormFactor formFactor, StreetMode streetMode, @@ -74,14 +76,16 @@ void scooterBicycleTraversal( assertEquals(formFactor.traverseMode, afterTraversal.currentMode()); } - static Stream noTraversal = Stream - .of(StreetTraversalPermission.CAR, StreetTraversalPermission.NONE) - .flatMap(StreetEdgeRentalTraversalTest::baseCases); + static Stream noTraversal() { + return Stream + .of(StreetTraversalPermission.CAR, StreetTraversalPermission.NONE) + .flatMap(StreetEdgeRentalTraversalTest::baseCases); + } @ParameterizedTest( name = "Form factor {0}, street mode {1} should not be able to traverse edge with permission {2}" ) - @VariableSource("noTraversal") + @MethodSource("noTraversal") void noTraversal( RentalFormFactor formFactor, StreetMode streetMode, diff --git a/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeWheelchairCostTest.java b/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeWheelchairCostTest.java index ed4b92da779..7c2767a6935 100644 --- a/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeWheelchairCostTest.java +++ b/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeWheelchairCostTest.java @@ -7,6 +7,7 @@ import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.impl.PackedCoordinateSequence; import org.opentripplanner.routing.api.request.preference.WheelchairPreferences; @@ -14,7 +15,6 @@ import org.opentripplanner.street.model.vertex.StreetVertex; import org.opentripplanner.street.search.request.StreetSearchRequest; import org.opentripplanner.street.search.state.State; -import org.opentripplanner.test.support.VariableSource; class StreetEdgeWheelchairCostTest { @@ -26,26 +26,28 @@ public StreetEdgeWheelchairCostTest() { V2 = intersectionVertex("V2", 2.0, 0.0); } - static Stream slopeCases = Stream.of( - // no extra cost - Arguments.of(0.07, 1, 5081), - // no extra cost - Arguments.of(0.08, 1, 5945), - // no extra cost - Arguments.of(0.09, 1, 6908), - // 0.1 % above the max slope, tiny extra cost - Arguments.of(0.091, 1, 7708), - // 3 % above max slope, will incur very large cost - Arguments.of(0.091, 3, 9110), - // 0.1 % above the max slope, but high reluctance will large cost - Arguments.of(0.0915, 1, 8116), - // 2 % above max slope, but lowered reluctance - Arguments.of(0.11, 0.5, 17649), - // 2 % above max slope, will incur very large cost - Arguments.of(0.11, 1, 26474), - // 3 % above max slope, will incur very large cost - Arguments.of(0.12, 1, 37978) - ); + static Stream slopeCases() { + return Stream.of( + // no extra cost + Arguments.of(0.07, 1, 5081), + // no extra cost + Arguments.of(0.08, 1, 5945), + // no extra cost + Arguments.of(0.09, 1, 6908), + // 0.1 % above the max slope, tiny extra cost + Arguments.of(0.091, 1, 7708), + // 3 % above max slope, will incur very large cost + Arguments.of(0.091, 3, 9110), + // 0.1 % above the max slope, but high reluctance will large cost + Arguments.of(0.0915, 1, 8116), + // 2 % above max slope, but lowered reluctance + Arguments.of(0.11, 0.5, 17649), + // 2 % above max slope, will incur very large cost + Arguments.of(0.11, 1, 26474), + // 3 % above max slope, will incur very large cost + Arguments.of(0.12, 1, 37978) + ); + } /** * This makes sure that when you exceed the max slope in a wheelchair there isn't a hard cut-off @@ -57,7 +59,7 @@ public StreetEdgeWheelchairCostTest() { @ParameterizedTest( name = "slope of {0} with maxSlopeExceededReluctance of {1} should lead to traversal costs of {2}" ) - @VariableSource("slopeCases") + @MethodSource("slopeCases") public void shouldScaleCostWithMaxSlope(double slope, double reluctance, long expectedCost) { double length = 1000; var edge = new StreetEdgeBuilder<>() @@ -104,16 +106,14 @@ public void shouldScaleCostWithMaxSlope(double slope, double reluctance, long ex assertEquals(expectedCost, (long) result.weight); } - static Stream wheelchairStairsCases = Stream.of( - Arguments.of(1, 22), - Arguments.of(10, 225), - Arguments.of(100, 2255) - ); + static Stream wheelchairStairsCases() { + return Stream.of(Arguments.of(1, 22), Arguments.of(10, 225), Arguments.of(100, 2255)); + } @ParameterizedTest( name = "wheelchair stairs reluctance of {0} should lead to traversal costs of {1}" ) - @VariableSource("wheelchairStairsCases") + @MethodSource("wheelchairStairsCases") public void wheelchairStairsReluctance(double stairsReluctance, long expectedCost) { double length = 10; var stairEdge = new StreetEdgeBuilder<>() @@ -153,16 +153,14 @@ public void wheelchairStairsReluctance(double stairsReluctance, long expectedCos assertEquals(7, (long) notStairsResult.weight); } - static Stream inaccessibleStreetCases = Stream.of( - Arguments.of(1f, 15), - Arguments.of(10f, 150), - Arguments.of(100f, 1503) - ); + static Stream inaccessibleStreetCases() { + return Stream.of(Arguments.of(1f, 15), Arguments.of(10f, 150), Arguments.of(100f, 1503)); + } @ParameterizedTest( name = "an inaccessible street with the reluctance of {0} should lead to traversal costs of {1}" ) - @VariableSource("inaccessibleStreetCases") + @MethodSource("inaccessibleStreetCases") public void inaccessibleStreet(float inaccessibleStreetReluctance, long expectedCost) { double length = 10; var edge = new StreetEdgeBuilder<>() @@ -201,17 +199,19 @@ public void inaccessibleStreet(float inaccessibleStreetReluctance, long expected assertEquals(15, (long) accessibleResult.weight); } - static Stream walkReluctanceCases = Stream.of( - Arguments.of(0.5, 3), - Arguments.of(1, 7), - Arguments.of(10, 75), - Arguments.of(100, 751) - ); + static Stream walkReluctanceCases() { + return Stream.of( + Arguments.of(0.5, 3), + Arguments.of(1, 7), + Arguments.of(10, 75), + Arguments.of(100, 751) + ); + } @ParameterizedTest( name = "walkReluctance of {0} should affect wheelchair users and lead to traversal costs of {1}" ) - @VariableSource("walkReluctanceCases") + @MethodSource("walkReluctanceCases") public void walkReluctance(double walkReluctance, long expectedCost) { double length = 10; var edge = new StreetEdgeBuilder<>() diff --git a/src/test/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLinkTest.java b/src/test/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLinkTest.java index a2a052d3b5c..99de720a5a2 100644 --- a/src/test/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLinkTest.java +++ b/src/test/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLinkTest.java @@ -11,6 +11,7 @@ import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.framework.i18n.NonLocalizedString; import org.opentripplanner.routing.api.request.StreetMode; @@ -20,22 +21,23 @@ import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.street.search.request.StreetSearchRequest; import org.opentripplanner.street.search.state.State; -import org.opentripplanner.test.support.VariableSource; class StreetVehicleParkingLinkTest { - static Stream testCases = Stream.of( - of(Set.of(), Set.of(), Set.of(), true), - of(Set.of("a-tag"), Set.of(), Set.of(), true), - of(Set.of("a"), Set.of("a"), Set.of(), false), - of(Set.of("a"), Set.of("a"), Set.of("a"), false), - of(Set.of("a", "b"), Set.of("b"), Set.of("a"), false), - of(Set.of("a", "b"), Set.of(), Set.of("a"), true), - of(Set.of("a", "b"), Set.of(), Set.of("c"), false) - ); + static Stream testCases() { + return Stream.of( + of(Set.of(), Set.of(), Set.of(), true), + of(Set.of("a-tag"), Set.of(), Set.of(), true), + of(Set.of("a"), Set.of("a"), Set.of(), false), + of(Set.of("a"), Set.of("a"), Set.of("a"), false), + of(Set.of("a", "b"), Set.of("b"), Set.of("a"), false), + of(Set.of("a", "b"), Set.of(), Set.of("a"), true), + of(Set.of("a", "b"), Set.of(), Set.of("c"), false) + ); + } @ParameterizedTest(name = "Parking[tags={0}], Request[not={1}, select={2}] should traverse={3}") - @VariableSource("testCases") + @MethodSource("testCases") void foo(Set parkingTags, Set not, Set select, boolean shouldTraverse) { var streetVertex = intersectionVertex(1, 1); var parking = VehicleParking diff --git a/src/test/java/org/opentripplanner/street/search/state/StateDataTest.java b/src/test/java/org/opentripplanner/street/search/state/StateDataTest.java index 1138fd6cf02..83cec24cd77 100644 --- a/src/test/java/org/opentripplanner/street/search/state/StateDataTest.java +++ b/src/test/java/org/opentripplanner/street/search/state/StateDataTest.java @@ -6,18 +6,20 @@ import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.routing.api.request.StreetMode; import org.opentripplanner.street.search.request.StreetSearchRequest; -import org.opentripplanner.test.support.VariableSource; class StateDataTest { - static Stream cases = Arrays - .stream(StreetMode.values()) - .flatMap(mode -> Stream.of(Arguments.of(true, mode), Arguments.of(false, mode))); + static Stream cases() { + return Arrays + .stream(StreetMode.values()) + .flatMap(mode -> Stream.of(Arguments.of(true, mode), Arguments.of(false, mode))); + } @ParameterizedTest(name = "arriveBy={0}, streetMode={1}") - @VariableSource("cases") + @MethodSource("cases") void baseCases(boolean arriveBy, StreetMode streetMode) { var req = StreetSearchRequest.of().withArriveBy(arriveBy).withMode(streetMode).build(); var data = StateData.getBaseCaseStateData(req); diff --git a/src/test/java/org/opentripplanner/street/search/state/StateTest.java b/src/test/java/org/opentripplanner/street/search/state/StateTest.java index 34d3af11b68..9722f1dd785 100644 --- a/src/test/java/org/opentripplanner/street/search/state/StateTest.java +++ b/src/test/java/org/opentripplanner/street/search/state/StateTest.java @@ -31,11 +31,11 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.routing.api.request.StreetMode; import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.street.search.TraverseMode; import org.opentripplanner.street.search.request.StreetSearchRequest; -import org.opentripplanner.test.support.VariableSource; class StateTest { @@ -48,30 +48,32 @@ class StateTest { NULL_RENTAL_STATES.add(null); } - static Stream testCases = Stream.of( - of(SCOOTER_RENTAL, false, Set.of(BEFORE_RENTING), Set.of(WALK)), - //FIXME: it's strange that the arriveBy rental searches all start on a bicycle - of(SCOOTER_RENTAL, true, Set.of(HAVE_RENTED, RENTING_FLOATING), Set.of(WALK, BICYCLE)), - of(BIKE_RENTAL, false, Set.of(BEFORE_RENTING), Set.of(WALK)), - of(BIKE_RENTAL, true, Set.of(HAVE_RENTED, RENTING_FLOATING), Set.of(WALK, BICYCLE)), - of(CAR_RENTAL, false, Set.of(BEFORE_RENTING), Set.of(WALK)), - of(CAR_RENTAL, true, Set.of(HAVE_RENTED, RENTING_FLOATING), Set.of(WALK, BICYCLE)), - of(StreetMode.CAR, false, NULL_RENTAL_STATES, Set.of(CAR)), - of(BIKE, false, NULL_RENTAL_STATES, Set.of(BICYCLE)), - of(StreetMode.WALK, false, NULL_RENTAL_STATES, Set.of(TraverseMode.WALK)), - of(BIKE_TO_PARK, false, NULL_RENTAL_STATES, Set.of(BICYCLE)), - of(CAR_TO_PARK, false, NULL_RENTAL_STATES, Set.of(CAR)), - of(FLEXIBLE, false, NULL_RENTAL_STATES, Set.of(WALK)), - of(CAR_PICKUP, false, NULL_RENTAL_STATES, Set.of(CAR, WALK)), - of(CAR_PICKUP, true, NULL_RENTAL_STATES, Set.of(CAR, WALK)), - of(CAR_HAILING, false, NULL_RENTAL_STATES, Set.of(CAR, WALK)), - of(CAR_HAILING, true, NULL_RENTAL_STATES, Set.of(CAR, WALK)) - ); + static Stream testCases() { + return Stream.of( + of(SCOOTER_RENTAL, false, Set.of(BEFORE_RENTING), Set.of(WALK)), + //FIXME: it's strange that the arriveBy rental searches all start on a bicycle + of(SCOOTER_RENTAL, true, Set.of(HAVE_RENTED, RENTING_FLOATING), Set.of(WALK, BICYCLE)), + of(BIKE_RENTAL, false, Set.of(BEFORE_RENTING), Set.of(WALK)), + of(BIKE_RENTAL, true, Set.of(HAVE_RENTED, RENTING_FLOATING), Set.of(WALK, BICYCLE)), + of(CAR_RENTAL, false, Set.of(BEFORE_RENTING), Set.of(WALK)), + of(CAR_RENTAL, true, Set.of(HAVE_RENTED, RENTING_FLOATING), Set.of(WALK, BICYCLE)), + of(StreetMode.CAR, false, NULL_RENTAL_STATES, Set.of(CAR)), + of(BIKE, false, NULL_RENTAL_STATES, Set.of(BICYCLE)), + of(StreetMode.WALK, false, NULL_RENTAL_STATES, Set.of(TraverseMode.WALK)), + of(BIKE_TO_PARK, false, NULL_RENTAL_STATES, Set.of(BICYCLE)), + of(CAR_TO_PARK, false, NULL_RENTAL_STATES, Set.of(CAR)), + of(FLEXIBLE, false, NULL_RENTAL_STATES, Set.of(WALK)), + of(CAR_PICKUP, false, NULL_RENTAL_STATES, Set.of(CAR, WALK)), + of(CAR_PICKUP, true, NULL_RENTAL_STATES, Set.of(CAR, WALK)), + of(CAR_HAILING, false, NULL_RENTAL_STATES, Set.of(CAR, WALK)), + of(CAR_HAILING, true, NULL_RENTAL_STATES, Set.of(CAR, WALK)) + ); + } @ParameterizedTest( name = "street mode {0}, arriveBy={1} should lead to initial states with rentalStates={2}, currentModes={3}" ) - @VariableSource("testCases") + @MethodSource("testCases") void initialStates( StreetMode streetMode, boolean arriveBy, diff --git a/src/test/java/org/opentripplanner/test/support/VariableArgumentsProvider.java b/src/test/java/org/opentripplanner/test/support/VariableArgumentsProvider.java deleted file mode 100644 index 1ae9fb2af84..00000000000 --- a/src/test/java/org/opentripplanner/test/support/VariableArgumentsProvider.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.opentripplanner.test.support; - -import java.lang.reflect.Field; -import java.util.stream.Stream; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.ArgumentsProvider; -import org.junit.jupiter.params.support.AnnotationConsumer; - -/** - * This annotation processor allows you to provide a variable as the input for a JUnit {@link - * org.junit.jupiter.params.ParameterizedTest}. - * - * Check the usages of {@link VariableSource} to see examples for how to use. - */ -class VariableArgumentsProvider implements ArgumentsProvider, AnnotationConsumer { - - private String variableName; - - @Override - public Stream provideArguments(ExtensionContext context) { - return context - .getTestClass() - .map(this::getField) - .map(this::getValue) - .orElseThrow(() -> new IllegalArgumentException("Failed to load test arguments")); - } - - @Override - public void accept(VariableSource variableSource) { - variableName = variableSource.value(); - } - - private Field getField(Class clazz) { - try { - return clazz.getDeclaredField(variableName); - } catch (Exception e) { - return null; - } - } - - @SuppressWarnings("unchecked") - private Stream getValue(Field field) { - Object value = null; - var accessible = field.isAccessible(); - try { - field.setAccessible(true); - value = field.get(null); - } catch (Exception ignored) {} - - field.setAccessible(accessible); - - return value == null ? null : (Stream) value; - } -} diff --git a/src/test/java/org/opentripplanner/test/support/VariableSource.java b/src/test/java/org/opentripplanner/test/support/VariableSource.java deleted file mode 100644 index 254110cf6d2..00000000000 --- a/src/test/java/org/opentripplanner/test/support/VariableSource.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.opentripplanner.test.support; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import org.junit.jupiter.params.provider.ArgumentsSource; - -@Documented -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -@ArgumentsSource(VariableArgumentsProvider.class) -public @interface VariableSource { - /** - * The name of the static variable - */ - String value(); -} diff --git a/src/test/java/org/opentripplanner/transit/model/framework/FeedScopedIdTest.java b/src/test/java/org/opentripplanner/transit/model/framework/FeedScopedIdTest.java index 08df670ed62..a7c7c5ff058 100644 --- a/src/test/java/org/opentripplanner/transit/model/framework/FeedScopedIdTest.java +++ b/src/test/java/org/opentripplanner/transit/model/framework/FeedScopedIdTest.java @@ -3,13 +3,46 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import java.util.List; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; class FeedScopedIdTest { + private static final List TRIMET_123 = List.of(new FeedScopedId("trimet", "123")); + @Test void ofNullable() { assertEquals(new FeedScopedId("FEED", "ID"), FeedScopedId.ofNullable("FEED", "ID")); assertNull(FeedScopedId.ofNullable("FEED", null)); } + + @ParameterizedTest + @ValueSource( + strings = { "trimet:123", "trimet:123 ", "trimet:123, ", ",trimet:123 , ", " trimet:123 " } + ) + void parseList(String input) { + assertEquals(TRIMET_123, FeedScopedId.parseList(input)); + } + + @ParameterizedTest + @ValueSource( + strings = { + ",trimet:123 , ,\u200B,", + "\u200Btrimet:123", + "\u200B\u200Btri\u200Bmet:123\u200B", + "\ntrimet:123\t", + "\ntri\nmet:123\t", + } + ) + void throwExceptionForInvisibleChar(String input) { + Assertions.assertThrows( + IllegalArgumentException.class, + () -> { + FeedScopedId.parseList(input); + } + ); + } } diff --git a/src/test/java/org/opentripplanner/updater/trip/TimetableSnapshotSourceTest.java b/src/test/java/org/opentripplanner/updater/trip/TimetableSnapshotSourceTest.java index d25de6ff021..8c28290de66 100644 --- a/src/test/java/org/opentripplanner/updater/trip/TimetableSnapshotSourceTest.java +++ b/src/test/java/org/opentripplanner/updater/trip/TimetableSnapshotSourceTest.java @@ -33,6 +33,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.ConstantsForTests; import org.opentripplanner.TestOtpModel; import org.opentripplanner.framework.i18n.NonLocalizedString; @@ -40,7 +41,6 @@ import org.opentripplanner.model.PickDrop; import org.opentripplanner.model.Timetable; import org.opentripplanner.model.TimetableSnapshot; -import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.TripPattern; @@ -1090,16 +1090,18 @@ SameAssert not() { } } - static Stream purgeExpiredDataTestCases = Stream.of( - // purgeExpiredData maxSnapshotFrequency || snapshots PatternSnapshotA PatternSnapshotB - Arguments.of(Boolean.TRUE, -1, NotSame, NotSame), - Arguments.of(Boolean.FALSE, -1, NotSame, Same), - Arguments.of(Boolean.TRUE, 1000, NotSame, NotSame), - Arguments.of(Boolean.FALSE, 1000, Same, Same) - ); + static Stream purgeExpiredDataTestCases() { + return Stream.of( + // purgeExpiredData maxSnapshotFrequency || snapshots PatternSnapshotA PatternSnapshotB + Arguments.of(Boolean.TRUE, -1, NotSame, NotSame), + Arguments.of(Boolean.FALSE, -1, NotSame, Same), + Arguments.of(Boolean.TRUE, 1000, NotSame, NotSame), + Arguments.of(Boolean.FALSE, 1000, Same, Same) + ); + } @ParameterizedTest(name = "purgeExpired: {0}, maxFrequency: {1} || {2} {3}") - @VariableSource("purgeExpiredDataTestCases") + @MethodSource("purgeExpiredDataTestCases") public void testPurgeExpiredData( boolean purgeExpiredData, int maxSnapshotFrequency, diff --git a/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java b/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java index 29d03844260..ea5d86cd0e1 100644 --- a/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java +++ b/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java @@ -23,12 +23,12 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner._support.time.ZoneIds; import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.model.StopTime; import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; import org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig; -import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.FeedScopedId; @@ -326,14 +326,16 @@ public void clearOldTrips() { assertEquals(0, service.getRealtimeVehicles(pattern2).size()); } - static Stream inferenceTestCases = Stream.of( - Arguments.of("2022-04-05T15:26:04+02:00", "2022-04-05"), - Arguments.of("2022-04-06T00:26:04+02:00", "2022-04-05"), - Arguments.of("2022-04-06T10:26:04+02:00", "2022-04-06") - ); + static Stream inferenceTestCases() { + return Stream.of( + Arguments.of("2022-04-05T15:26:04+02:00", "2022-04-05"), + Arguments.of("2022-04-06T00:26:04+02:00", "2022-04-05"), + Arguments.of("2022-04-06T10:26:04+02:00", "2022-04-06") + ); + } @ParameterizedTest(name = "{0} should resolve to {1}") - @VariableSource("inferenceTestCases") + @MethodSource("inferenceTestCases") void inferServiceDayOfTripAt6(String time, String expectedDate) { var trip = TransitModelForTest.trip(tripId).build(); diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json index 557cdec8659..ea58480be8e 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json +++ b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json @@ -3,6 +3,8 @@ "plan" : { "itineraries" : [ { + "start" : "2020-02-02T11:00:00Z", + "end" : "2020-02-02T12:00:00Z", "startTime" : 1580641200000, "endTime" : 1580644800000, "generalizedCost" : 4072, @@ -16,10 +18,26 @@ "legs" : [ { "mode" : "WALK", + "start" : { + "scheduledTime" : "2020-02-02T11:00:00Z", + "estimated" : null + }, + "end" : { + "scheduledTime" : "2020-02-02T11:00:20Z", + "estimated" : null + }, "from" : { "name" : "A", "lat" : 5.0, "lon" : 8.0, + "arrival" : { + "scheduledTime" : "2020-02-02T11:00:00Z", + "estimated" : null + }, + "departure" : { + "scheduledTime" : "2020-02-02T11:00:00Z", + "estimated" : null + }, "departureTime" : 1580641200000, "arrivalTime" : 1580641200000 }, @@ -27,6 +45,14 @@ "name" : "B", "lat" : 6.0, "lon" : 8.5, + "arrival" : { + "scheduledTime" : "2020-02-02T11:00:20Z", + "estimated" : null + }, + "departure" : { + "scheduledTime" : "2020-02-02T11:00:20Z", + "estimated" : null + }, "departureTime" : 1580641220000, "arrivalTime" : 1580641220000 }, @@ -35,16 +61,45 @@ "generalizedCost" : 40, "headsign" : null, "trip" : null, + "intermediatePlaces" : null, "alerts" : [ ], "rideHailingEstimate" : null, "accessibilityScore" : null }, { "mode" : "BUS", + "start" : { + "scheduledTime" : "2020-02-02T10:51:00Z", + "estimated" : { + "time" : "2020-02-02T11:01:00Z", + "delay" : "PT10M" + } + }, + "end" : { + "scheduledTime" : "2020-02-02T11:05:00Z", + "estimated" : { + "time" : "2020-02-02T11:15:00Z", + "delay" : "PT10M" + } + }, "from" : { "name" : "B", "lat" : 6.0, "lon" : 8.5, + "arrival" : { + "scheduledTime" : "2020-02-02T10:51:00Z", + "estimated" : { + "delay" : "PT10M", + "time" : "2020-02-02T11:01:00Z" + } + }, + "departure" : { + "scheduledTime" : "2020-02-02T10:51:00Z", + "estimated" : { + "delay" : "PT10M", + "time" : "2020-02-02T11:01:00Z" + } + }, "departureTime" : 1580641260000, "arrivalTime" : 1580641260000 }, @@ -52,6 +107,20 @@ "name" : "C", "lat" : 7.0, "lon" : 9.0, + "arrival" : { + "scheduledTime" : "2020-02-02T11:05:00Z", + "estimated" : { + "delay" : "PT10M", + "time" : "2020-02-02T11:15:00Z" + } + }, + "departure" : { + "scheduledTime" : "2020-02-02T11:05:00Z", + "estimated" : { + "delay" : "PT10M", + "time" : "2020-02-02T11:15:00Z" + } + }, "departureTime" : 1580642100000, "arrivalTime" : 1580642100000 }, @@ -62,16 +131,65 @@ "trip" : { "tripHeadsign" : "Trip headsign 122" }, + "intermediatePlaces" : [ + { + "arrival" : { + "scheduledTime" : "2020-02-02T11:01:00Z", + "estimated" : { + "time" : "2020-02-02T11:11:00Z", + "delay" : "PT10M" + } + }, + "departure" : { + "scheduledTime" : "2020-02-02T11:01:00Z", + "estimated" : { + "time" : "2020-02-02T11:11:00Z", + "delay" : "PT10M" + } + }, + "stop" : { + "name" : "B" + } + } + ], "alerts" : [ ], "rideHailingEstimate" : null, "accessibilityScore" : null }, { "mode" : "RAIL", + "start" : { + "scheduledTime" : "2020-02-02T11:20:00Z", + "estimated" : { + "time" : "2020-02-02T11:30:00Z", + "delay" : "PT10M" + } + }, + "end" : { + "scheduledTime" : "2020-02-02T11:40:00Z", + "estimated" : { + "time" : "2020-02-02T11:50:00Z", + "delay" : "PT10M" + } + }, "from" : { "name" : "C", "lat" : 7.0, "lon" : 9.0, + "arrival" : { + "scheduledTime" : "2020-02-02T11:20:00Z", + "estimated" : { + "delay" : "PT10M", + "time" : "2020-02-02T11:30:00Z" + } + }, + "departure" : { + "scheduledTime" : "2020-02-02T11:20:00Z", + "estimated" : { + "delay" : "PT10M", + "time" : "2020-02-02T11:30:00Z" + } + }, "departureTime" : 1580643000000, "arrivalTime" : 1580643000000 }, @@ -79,6 +197,20 @@ "name" : "D", "lat" : 8.0, "lon" : 9.5, + "arrival" : { + "scheduledTime" : "2020-02-02T11:40:00Z", + "estimated" : { + "delay" : "PT10M", + "time" : "2020-02-02T11:50:00Z" + } + }, + "departure" : { + "scheduledTime" : "2020-02-02T11:40:00Z", + "estimated" : { + "delay" : "PT10M", + "time" : "2020-02-02T11:50:00Z" + } + }, "departureTime" : 1580644200000, "arrivalTime" : 1580644200000 }, @@ -89,6 +221,27 @@ "trip" : { "tripHeadsign" : "Trip headsign 439" }, + "intermediatePlaces" : [ + { + "arrival" : { + "scheduledTime" : "2020-02-02T11:30:00Z", + "estimated" : { + "time" : "2020-02-02T11:40:00Z", + "delay" : "PT10M" + } + }, + "departure" : { + "scheduledTime" : "2020-02-02T11:30:00Z", + "estimated" : { + "time" : "2020-02-02T11:40:00Z", + "delay" : "PT10M" + } + }, + "stop" : { + "name" : "C" + } + } + ], "alerts" : [ { "id" : "QWxlcnQ6Rjphbi1hbGVydA", @@ -115,10 +268,26 @@ }, { "mode" : "CAR", + "start" : { + "scheduledTime" : "2020-02-02T11:50:00Z", + "estimated" : null + }, + "end" : { + "scheduledTime" : "2020-02-02T12:00:00Z", + "estimated" : null + }, "from" : { "name" : "D", "lat" : 8.0, "lon" : 9.5, + "arrival" : { + "scheduledTime" : "2020-02-02T11:50:00Z", + "estimated" : null + }, + "departure" : { + "scheduledTime" : "2020-02-02T11:50:00Z", + "estimated" : null + }, "departureTime" : 1580644200000, "arrivalTime" : 1580644200000 }, @@ -126,6 +295,14 @@ "name" : "E", "lat" : 9.0, "lon" : 10.0, + "arrival" : { + "scheduledTime" : "2020-02-02T12:00:00Z", + "estimated" : null + }, + "departure" : { + "scheduledTime" : "2020-02-02T12:00:00Z", + "estimated" : null + }, "departureTime" : 1580644800000, "arrivalTime" : 1580644800000 }, @@ -134,6 +311,7 @@ "generalizedCost" : 1000, "headsign" : null, "trip" : null, + "intermediatePlaces" : null, "alerts" : [ ], "rideHailingEstimate" : { "provider" : { diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan.json b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan.json index 06fd20be150..d213443f7cd 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan.json +++ b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan.json @@ -3,47 +3,57 @@ "plan" : { "itineraries" : [ { - "startTime" : 1580641200000, - "endTime" : 1580644800000, + "start" : "2020-02-02T11:00:00Z", + "end" : "2020-02-02T12:00:00Z", "legs" : [ { "mode" : "WALK", - "startTime" : 1580641200000, - "endTime" : 1580641220000, "from" : { "name" : "A", "lat" : 5.0, "lon" : 8.0, - "departureTime" : 1580641200000, - "arrivalTime" : 1580641200000 + "departure" : { + "scheduledTime" : "2020-02-02T11:00:00Z", + "estimated" : null + } }, "to" : { "name" : "B", "lat" : 6.0, "lon" : 8.5, - "departureTime" : 1580641220000, - "arrivalTime" : 1580641220000 + "arrival" : { + "scheduledTime" : "2020-02-02T11:00:20Z", + "estimated" : null + } }, "route" : null, "legGeometry" : null }, { "mode" : "BUS", - "startTime" : 1580641260000, - "endTime" : 1580642100000, "from" : { "name" : "B", "lat" : 6.0, "lon" : 8.5, - "departureTime" : 1580641260000, - "arrivalTime" : 1580641260000 + "departure" : { + "scheduledTime" : "2020-02-02T10:51:00Z", + "estimated" : { + "time" : "2020-02-02T11:01:00Z", + "delay" : "PT10M" + } + } }, "to" : { "name" : "C", "lat" : 7.0, "lon" : 9.0, - "departureTime" : 1580642100000, - "arrivalTime" : 1580642100000 + "arrival" : { + "scheduledTime" : "2020-02-02T11:05:00Z", + "estimated" : { + "time" : "2020-02-02T11:15:00Z", + "delay" : "PT10M" + } + } }, "route" : { "gtfsId" : "F:BUS", @@ -56,21 +66,29 @@ }, { "mode" : "RAIL", - "startTime" : 1580643000000, - "endTime" : 1580644200000, "from" : { "name" : "C", "lat" : 7.0, "lon" : 9.0, - "departureTime" : 1580643000000, - "arrivalTime" : 1580643000000 + "departure" : { + "scheduledTime" : "2020-02-02T11:20:00Z", + "estimated" : { + "time" : "2020-02-02T11:30:00Z", + "delay" : "PT10M" + } + } }, "to" : { "name" : "D", "lat" : 8.0, "lon" : 9.5, - "departureTime" : 1580644200000, - "arrivalTime" : 1580644200000 + "arrival" : { + "scheduledTime" : "2020-02-02T11:40:00Z", + "estimated" : { + "time" : "2020-02-02T11:50:00Z", + "delay" : "PT10M" + } + } }, "route" : { "gtfsId" : "F:2", @@ -83,21 +101,23 @@ }, { "mode" : "CAR", - "startTime" : 1580644200000, - "endTime" : 1580644800000, "from" : { "name" : "D", "lat" : 8.0, "lon" : 9.5, - "departureTime" : 1580644200000, - "arrivalTime" : 1580644200000 + "departure" : { + "scheduledTime" : "2020-02-02T11:50:00Z", + "estimated" : null + } }, "to" : { "name" : "E", "lat" : 9.0, "lon" : 10.0, - "departureTime" : 1580644800000, - "arrivalTime" : 1580644800000 + "arrival" : { + "scheduledTime" : "2020-02-02T12:00:00Z", + "estimated" : null + } }, "route" : null, "legGeometry" : null diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql b/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql index db30d8489ef..bcd96892e84 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql +++ b/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql @@ -16,6 +16,9 @@ } ]) { itineraries { + start + end + # next two are deprecated startTime endTime generalizedCost @@ -28,10 +31,38 @@ walkTime legs { mode + start { + scheduledTime + estimated { + time + delay + } + } + end { + scheduledTime + estimated { + time + delay + } + } from { name lat lon + arrival { + scheduledTime + estimated { + delay + time + } + } + departure { + scheduledTime + estimated { + delay + time + } + } departureTime arrivalTime } @@ -39,6 +70,20 @@ name lat lon + arrival { + scheduledTime + estimated { + delay + time + } + } + departure { + scheduledTime + estimated { + delay + time + } + } departureTime arrivalTime } @@ -50,6 +95,25 @@ trip { tripHeadsign } + intermediatePlaces { + arrival { + scheduledTime + estimated { + time + delay + } + } + departure { + scheduledTime + estimated { + time + delay + } + } + stop { + name + } + } alerts { id alertHeaderText diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan.graphql b/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan.graphql index d71c991234d..39877eef0b7 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan.graphql +++ b/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan.graphql @@ -17,25 +17,33 @@ }, ]) { itineraries { - startTime - endTime + start + end legs { mode - startTime - endTime from { name lat lon - departureTime - arrivalTime + departure { + scheduledTime + estimated { + time + delay + } + } } to { name lat lon - departureTime - arrivalTime + arrival { + scheduledTime + estimated { + time + delay + } + } } route { gtfsId