From cd14c6ecbcdd17bcfad399481aeca3c7f0968ba1 Mon Sep 17 00:00:00 2001
From: Paulina Sedlak-Jakubowska <psedlak-jakubowska@gitlab.com>
Date: Tue, 11 Feb 2025 19:44:58 +0000
Subject: [PATCH] WIP: Update repository router to be compatible with Vue3 mode

---
 .../lib/utils/vue3compat/vue_router.js        | 10 ++++++++
 app/assets/javascripts/repository/router.js   | 22 +++++++++++-----
 scripts/frontend/quarantined_vue3_specs.txt   |  2 --
 spec/frontend/repository/router_spec.js       | 25 +++++++++----------
 4 files changed, 38 insertions(+), 21 deletions(-)

diff --git a/app/assets/javascripts/lib/utils/vue3compat/vue_router.js b/app/assets/javascripts/lib/utils/vue3compat/vue_router.js
index f3d5d0417161e..491e1706ce31e 100644
--- a/app/assets/javascripts/lib/utils/vue3compat/vue_router.js
+++ b/app/assets/javascripts/lib/utils/vue3compat/vue_router.js
@@ -92,6 +92,16 @@ const transformOptions = (options = {}) => {
 
 const installed = new WeakMap();
 
+export const getMatchedComponents = (instance, path) => {
+  if (instance.getMatchedComponents) {
+    return instance.getMatchedComponents(path);
+  }
+
+  const route = path ? instance.resolve(path) : instance.currentRoute.value;
+
+  return route.matched.flatMap((record) => Object.values(record.components));
+};
+
 export default class VueRouterCompat {
   constructor(options) {
     // eslint-disable-next-line no-constructor-return
diff --git a/app/assets/javascripts/repository/router.js b/app/assets/javascripts/repository/router.js
index 5be77e391bab6..da19730176105 100644
--- a/app/assets/javascripts/repository/router.js
+++ b/app/assets/javascripts/repository/router.js
@@ -9,11 +9,21 @@ import { getRefType } from './utils/ref_type';
 
 Vue.use(VueRouter);
 
+const normalizePathParam = (pathParam) => {
+  // Vue Router 4 when there's more than one `:path` segment
+  if (Array.isArray(pathParam)) {
+    return joinPaths(...pathParam);
+  }
+
+  // Vue Router 3, or when there's zero or one `:path` segments.
+  return pathParam?.replace(/^\//, '') || '/';
+};
+
 export default function createRouter(base, baseRef) {
   const treePathRoute = {
     component: TreePage,
     props: (route) => ({
-      path: route.params.path?.replace(/^\//, '') || '/',
+      path: normalizePathParam(route.params.path),
       refType: getRefType(route.query.ref_type || null),
     }),
   };
@@ -36,25 +46,25 @@ export default function createRouter(base, baseRef) {
       {
         name: 'treePathDecoded',
         // Sometimes the ref needs decoding depending on how the backend sends it to us
-        path: `(/-)?/tree/${decodeURI(baseRef)}/:path*`,
+        path: `/:dash(-)?/tree/${decodeURI(baseRef)}/:path*`,
         ...treePathRoute,
       },
       {
         name: 'treePath',
         // Support without decoding as well just in case the ref doesn't need to be decoded
-        path: `(/-)?/tree/${escapeRegExp(baseRef)}/:path*`,
+        path: `/:dash(-)?/tree/${escapeRegExp(baseRef)}/:path*`,
         ...treePathRoute,
       },
       {
         name: 'blobPathDecoded',
         // Sometimes the ref needs decoding depending on how the backend sends it to us
-        path: `(/-)?/blob/${decodeURI(baseRef)}/:path*`,
+        path: `/:dash(-)?/blob/${decodeURI(baseRef)}/:path*`,
         ...blobPathRoute,
       },
       {
         name: 'blobPath',
         // Support without decoding as well just in case the ref doesn't need to be decoded
-        path: `(/-)?/blob/${escapeRegExp(baseRef)}/:path*`,
+        path: `/:dash(-)?/blob/${escapeRegExp(baseRef)}/:path*`,
         ...blobPathRoute,
       },
       {
@@ -80,7 +90,7 @@ export default function createRouter(base, baseRef) {
         'edit',
         decodeURI(baseRef),
         '-',
-        to.params.path || '',
+        normalizePathParam(to.params.path),
         needsClosingSlash && '/',
       ),
     );
diff --git a/scripts/frontend/quarantined_vue3_specs.txt b/scripts/frontend/quarantined_vue3_specs.txt
index d66a8cc84fb15..71447f0f74e31 100644
--- a/scripts/frontend/quarantined_vue3_specs.txt
+++ b/scripts/frontend/quarantined_vue3_specs.txt
@@ -233,10 +233,8 @@ spec/frontend/ref/init_ambiguous_ref_modal_spec.js
 spec/frontend/releases/components/app_edit_new_spec.js
 spec/frontend/releases/components/asset_links_form_spec.js
 spec/frontend/repository/components/header_area/blob_controls_spec.js
-spec/frontend/repository/components/header_area/blob_overflow_menu_spec.js
 spec/frontend/repository/components/table/index_spec.js
 spec/frontend/repository/components/table/row_spec.js
-spec/frontend/repository/router_spec.js
 spec/frontend/search/sidebar/components/checkbox_filter_spec.js
 spec/frontend/search/topbar/components/app_spec.js
 spec/frontend/sessions/new/components/email_verification_spec.js
diff --git a/spec/frontend/repository/router_spec.js b/spec/frontend/repository/router_spec.js
index 3f822db601f5d..de16e45d141a1 100644
--- a/spec/frontend/repository/router_spec.js
+++ b/spec/frontend/repository/router_spec.js
@@ -2,6 +2,7 @@ import BlobPage from '~/repository/pages/blob.vue';
 import IndexPage from '~/repository/pages/index.vue';
 import TreePage from '~/repository/pages/tree.vue';
 import createRouter from '~/repository/router';
+import { getMatchedComponents } from '~/lib/utils/vue3compat/vue_router';
 
 describe('Repository router spec', () => {
   it.each`
@@ -11,18 +12,13 @@ describe('Repository router spec', () => {
     ${'/tree/feat(test)'}        | ${'feat(test)'} | ${TreePage}  | ${'TreePage'}
     ${'/-/tree/main'}            | ${'main'}       | ${TreePage}  | ${'TreePage'}
     ${'/-/tree/main/app/assets'} | ${'main'}       | ${TreePage}  | ${'TreePage'}
-    ${'/-/tree/123/app/assets'}  | ${'main'}       | ${null}      | ${'null'}
     ${'/-/blob/main/file.md'}    | ${'main'}       | ${BlobPage}  | ${'BlobPage'}
   `('sets component as $componentName for path "$path"', ({ path, component, branch }) => {
     const router = createRouter('', branch);
 
-    const componentsForRoute = router.getMatchedComponents(path);
+    const componentsForRoute = getMatchedComponents(router, path);
 
-    expect(componentsForRoute.length).toBe(component ? 1 : 0);
-
-    if (component) {
-      expect(componentsForRoute).toContain(component);
-    }
+    expect(componentsForRoute).toEqual([component]);
   });
 
   describe('Storing Web IDE path globally', () => {
@@ -45,11 +41,14 @@ describe('Repository router spec', () => {
       ${'/-/tree/main'}            | ${'main'}       | ${`/-/ide/project/${proj}/edit/main/-/`}
       ${'/-/tree/main/app/assets'} | ${'main'}       | ${`/-/ide/project/${proj}/edit/main/-/app/assets/`}
       ${'/-/blob/main/file.md'}    | ${'main'}       | ${`/-/ide/project/${proj}/edit/main/-/file.md`}
-    `('generates the correct Web IDE url for $path', ({ path, branch, expectedPath } = {}) => {
-      const router = createRouter(proj, branch);
-
-      router.push(path);
-      expect(window.gl.webIDEPath).toBe(expectedPath);
-    });
+    `(
+      'generates the correct Web IDE url for $path',
+      async ({ path, branch, expectedPath } = {}) => {
+        const router = createRouter(proj, branch);
+
+        await router.push(path);
+        expect(window.gl.webIDEPath).toBe(expectedPath);
+      },
+    );
   });
 });
-- 
GitLab