diff --git a/.env b/.env index 6c3f8d8d..7a7901ba 100644 --- a/.env +++ b/.env @@ -1,9 +1,5 @@ SKIP_PREFLIGHT_CHECK=true -REACT_APP_VALHALLA_URL=https://valhalla1.openstreetmap.de -REACT_APP_NOMINATIM_URL=https://nominatim.openstreetmap.org -REACT_APP_TILE_SERVER_URL="https://tile.openstreetmap.org/{z}/{x}/{y}.png" -REACT_APP_CENTER_COORDS="52.51831,13.393707" - -# uncomment this variable to set boundaries on the leaflet map -# southwest corner, northeast corner -REACT_APP_MAX_BOUNDS="-180,-90,180,90" +VITE_VALHALLA_URL=https://valhalla1.openstreetmap.de +VITE_NOMINATIM_URL=https://nominatim.openstreetmap.org +VITE_TILE_SERVER_URL="https://tile.openstreetmap.org/{z}/{x}/{y}.png" +VITE_CENTER_COORDS="52.51831,13.393707" diff --git a/.gitignore b/.gitignore index e5eef68f..d89f8cf8 100755 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,12 @@ node_modules/ /blob-report/ /playwright/.cache/ tsconfig.tsbuildinfo + +# tanstack router +dist +dist-ssr +*.local +count.txt +.nitro +.tanstack +.wrangler diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 00000000..f0d24755 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npm run typecheck && npx lint-staged diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..00b5278e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "files.watcherExclude": { + "**/routeTree.gen.ts": true + }, + "search.exclude": { + "**/routeTree.gen.ts": true + }, + "files.readonlyInclude": { + "**/routeTree.gen.ts": true + } +} diff --git a/components.json b/components.json new file mode 100644 index 00000000..8f00892f --- /dev/null +++ b/components.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/index.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "registries": {} +} diff --git a/e2e/homepage.spec.ts b/e2e/homepage.spec.ts index 0385c2fb..420a586a 100644 --- a/e2e/homepage.spec.ts +++ b/e2e/homepage.spec.ts @@ -30,13 +30,12 @@ test('has default elements in the page', async ({ page }) => { await expect(page.getByTestId('show-hide-settings-btn')).toBeVisible(); - await expect(page.getByRole('button', { name: '1' })).toBeVisible(); - await expect(page.getByRole('button', { name: '2' })).toBeVisible(); + expect(await page.getByText('Select a waypoint...').count()).toBe(2); await expect(page.getByTestId('add-waypoint-button')).toBeVisible(); await expect(page.getByTestId('reset-waypoints-button')).toBeVisible(); - await expect(page.getByText('Nonspecific timeNonspecific')).toBeVisible(); + await expect(page.getByText('Non-specific time')).toBeVisible(); await expect(page.getByTestId('date-time-picker')).toBeVisible(); await expect( @@ -45,19 +44,16 @@ test('has default elements in the page', async ({ page }) => { await page.getByTestId('isochrones-tab-button').click(); - await expect( - page.getByRole('textbox', { name: 'Hit enter for search...' }) - ).toBeVisible(); - await page.getByTestId('reset-center-button').click(); + await page.getByTestId('remove-waypoint-button').click(); - await expect(page.getByText(/^Settings$/)).toBeVisible(); + await expect(page.getByText(/^Isochrones Settings$/)).toBeVisible(); - await expect(page.getByText('Maximum Rangemins10')).toBeVisible(); - await expect(page.getByText('Interval Stepmins10')).toBeVisible(); - await expect(page.getByText('Denoise0.1')).toBeVisible(); - await expect(page.getByText('Generalizemeters0')).toBeVisible(); + await page.getByText(/^Isochrones Settings$/).click(); - // await expect(page.getByRole('button', { name: 'Layers' })).toBeVisible(); + await expect(page.getByText('Maximum Range', { exact: true })).toBeVisible(); + await expect(page.getByText('Interval Step', { exact: true })).toBeVisible(); + await expect(page.getByText('Denoise', { exact: true })).toBeVisible(); + await expect(page.getByText('Generalize', { exact: true })).toBeVisible(); await expect(page.getByRole('button', { name: 'Zoom in' })).toBeVisible(); await expect(page.getByRole('button', { name: 'Zoom out' })).toBeVisible(); diff --git a/e2e/map.spec.ts b/e2e/map.spec.ts index f0e91f05..000ba144 100644 --- a/e2e/map.spec.ts +++ b/e2e/map.spec.ts @@ -137,10 +137,8 @@ test.describe('Map interactions with right context menu', () => { await expect(page.getByLabel('Map marker').getByRole('img')).toBeVisible(); await expect( - page - .getByTestId('waypoint-input-0') - .getByRole('textbox', { name: 'Hit enter for search...' }) - ).toHaveValue('Unter den Linden, Mitte, Berlin, Germany'); + page.getByTestId('waypoint-input-0').getByText('Unter den Linden, Mitte,') + ).toBeVisible(); }); test('should make Nominatim request when clicking "Directions to here"', async ({ @@ -201,10 +199,8 @@ test.describe('Map interactions with right context menu', () => { await expect(page.getByLabel('Map marker').getByRole('img')).toBeVisible(); await expect( - page - .getByTestId('waypoint-input-1') - .getByRole('textbox', { name: 'Hit enter for search...' }) - ).toHaveValue('Unter den Linden, Mitte, Berlin, Germany'); + page.getByTestId('waypoint-input-1').getByText('Unter den Linden, Mitte,') + ).toBeVisible(); }); test('should make Nominatim request when clicking "Add as via point"', async ({ @@ -241,10 +237,8 @@ test.describe('Map interactions with right context menu', () => { await expect(page.getByLabel('Map marker').getByRole('img')).toBeVisible(); await expect( - page - .getByTestId('waypoint-input-1') - .getByRole('textbox', { name: 'Hit enter for search...' }) - ).toHaveValue('Unter den Linden, Mitte, Berlin, Germany'); + page.getByTestId('waypoint-input-1').getByText('Unter den Linden, Mitte,') + ).toBeVisible(); }); test('should add multiple via points', async ({ page }) => { @@ -272,16 +266,12 @@ test.describe('Map interactions with right context menu', () => { ).toBeVisible(); await expect( - page - .getByTestId('waypoint-input-1') - .getByRole('textbox', { name: 'Hit enter for search...' }) - ).toHaveValue('Unter den Linden, Mitte, Berlin, Germany'); + page.getByTestId('waypoint-input-1').getByText('Unter den Linden, Mitte,') + ).toBeVisible(); await expect( - page - .getByTestId('waypoint-input-2') - .getByRole('textbox', { name: 'Hit enter for search...' }) - ).toHaveValue('Unter den Linden, Mitte, Berlin, Germany'); + page.getByTestId('waypoint-input-2').getByText('Unter den Linden, Mitte,') + ).toBeVisible(); }); test('should handle at least 9 waypoints', async ({ page }) => { @@ -398,7 +388,7 @@ test.describe('Map interactions with right context menu', () => { ).toBeVisible(); await expect( - page.locator('div').filter({ hasText: /^Directions$/ }) + page.getByRole('heading', { level: 3, name: 'Directions' }) ).toBeVisible(); await expect( @@ -566,8 +556,6 @@ test.describe('Map interactions with left context menu', () => { await page.getByTestId('dd-copy-button').click(); - await expect(page.getByText('copied')).toBeVisible(); - const clipboardContent = await page.evaluate(() => navigator.clipboard.readText() ); @@ -638,7 +626,7 @@ https: test.describe('Left drawer', () => { await page.getByTestId('add-waypoint-button').click(); - await expect(page.getByRole('button', { name: '3' })).toBeVisible(); + await expect(page.getByRole('button', { name: '3' }).first()).toBeVisible(); // Remove waypoint await page.getByTestId('reset-waypoints-button').click(); @@ -652,11 +640,8 @@ https: test.describe('Left drawer', () => { await setupNominatimMock(page); const searchRequests = await setupSearchMock(page); - const searchBox = page - .getByTestId('waypoint-input-0') - .getByRole('textbox', { name: 'Hit enter for search...' }); - - await searchBox.click(); + await page.getByTestId('waypoint-input-0').click(); + const searchBox = page.getByPlaceholder('Hit enter for search'); await searchBox.fill('Unter den Linden'); await searchBox.press('Enter'); @@ -679,14 +664,10 @@ https: test.describe('Left drawer', () => { const searchRequests = await setupSearchMock(page); const routeRequests = await setupRouteMock(page); - // Add "from" waypoint - const firstSearchBox = page - .getByTestId('waypoint-input-0') - .getByRole('textbox', { name: 'Hit enter for search...' }); - - await firstSearchBox.click(); - await firstSearchBox.fill('Unter den Linden'); - await firstSearchBox.press('Enter'); + await page.getByTestId('waypoint-input-0').click(); + const searchBox = page.getByPlaceholder('Hit enter for search'); + await searchBox.fill('Unter den Linden'); + await searchBox.press('Enter'); const firstSearchResult = page.getByTestId('search-result'); @@ -698,15 +679,14 @@ https: test.describe('Left drawer', () => { ).toBeVisible(); // Add "to" waypoint - const secondSearchBox = page - .getByTestId('waypoint-input-1') - .getByRole('textbox', { name: 'Hit enter for search...' }); - - await secondSearchBox.click(); + await page.getByTestId('waypoint-input-1').click(); + const secondSearchBox = page.getByPlaceholder('Hit enter for search'); await secondSearchBox.fill('Unter den Linden'); await secondSearchBox.press('Enter'); - const secondSearchResult = page.getByTestId('search-result').nth(1); + const secondSearchResult = page + .getByTestId('search-result') + .getByText('Unter den Linden, Mitte,'); await expect(secondSearchResult).toBeVisible(); await secondSearchResult.click(); @@ -745,49 +725,27 @@ https: test.describe('Left drawer', () => { ).toBeVisible(); await expect( - page - .getByTestId('waypoint-input-1') - .getByRole('textbox', { name: 'Hit enter for search...' }) + page.getByTestId('waypoint-input-1').getByText('Unter den Linden, Mitte,') + // .getByRole('textbox', { name: 'Hit enter for search...' }) ).toBeVisible(); await expect( - page - .getByTestId('waypoint-input-2') - .getByRole('textbox', { name: 'Hit enter for search...' }) + page.getByTestId('waypoint-input-2').getByText('Unter den Linden, Mitte,') ).toBeVisible(); - await expect( - page - .getByTestId('waypoint-input-1') - .getByRole('textbox', { name: 'Hit enter for search...' }) - ).toHaveValue('Unter den Linden, Mitte, Berlin, Germany'); - - await expect( - page - .getByTestId('waypoint-input-2') - .getByRole('textbox', { name: 'Hit enter for search...' }) - ).toHaveValue('Unter den Linden, Mitte, Berlin, Germany'); - - // Remove waypoint 3 await page.getByTestId('remove-waypoint-button').nth(2).click(); await expect( - page - .getByTestId('waypoint-input-2') - .getByRole('textbox', { name: 'Hit enter for search...' }) + page.getByTestId('waypoint-input-2').getByText('Unter den Linden, Mitte,') ).not.toBeVisible(); // Remove waypoint (should just clear text without removing actual element) - await page.getByTestId('remove-waypoint-button').nth(1).click(); await expect( - page - .getByTestId('waypoint-input-1') - .getByRole('textbox', { name: 'Hit enter for search...' }) + page.getByTestId('waypoint-input-1').getByText('Select a waypoint...') ).toBeVisible(); + await expect( - page - .getByTestId('waypoint-input-1') - .getByRole('textbox', { name: 'Hit enter for search...' }) - ).toHaveValue(''); + page.getByTestId('waypoint-input-0').getByText('Select a waypoint...') + ).toBeVisible(); }); test('should send the route request again when user changed profile', async ({ diff --git a/eslint.config.js b/eslint.config.js index d6282107..d7a063c6 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -6,8 +6,10 @@ import importPlugin from 'eslint-plugin-import'; import jsxA11y from 'eslint-plugin-jsx-a11y'; import tseslint from 'typescript-eslint'; import checkFile from 'eslint-plugin-check-file'; +import eslintPluginTanstackQuery from '@tanstack/eslint-plugin-query'; +import pluginRouter from '@tanstack/eslint-plugin-router'; -export default defineConfig( +export default defineConfig([ { ignores: [ 'node_modules', @@ -46,7 +48,6 @@ export default defineConfig( 'import/no-anonymous-default-export': 'warn', 'react/no-unknown-property': 'off', 'react/react-in-jsx-scope': 'off', - 'react/prop-types': 'off', 'jsx-a11y/alt-text': [ 'warn', { @@ -75,8 +76,10 @@ export default defineConfig( ...react.configs.flat['jsx-runtime'], }, { - ...reactHooks.configs['recommended-latest'], + ...reactHooks.configs.flat['recommended-latest'], }, + ...eslintPluginTanstackQuery.configs['flat/recommended'], + ...pluginRouter.configs['flat/recommended'], { linterOptions: { reportUnusedDisableDirectives: true, @@ -120,4 +123,4 @@ export default defineConfig( 'check-file/filename-naming-convention': 'off', }, } -); +]); diff --git a/index.html b/index.html index 9fa88894..da2638e2 100644 --- a/index.html +++ b/index.html @@ -2,40 +2,16 @@
- - + - - - + + +