]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
feat(url): simple resolve relative location
authorEduardo San Martin Morote <posva13@gmail.com>
Mon, 3 Aug 2020 08:54:39 +0000 (10:54 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Mon, 3 Aug 2020 08:54:39 +0000 (10:54 +0200)
__tests__/location.spec.ts
src/location.ts

index 7e798d1361551da3a8e8f589bbf8d70bb04f2122..3d45a8badb27017e8b8951dc2491e91ee799075d 100644 (file)
@@ -5,8 +5,10 @@ import {
   stripBase,
   isSameRouteLocationParams,
   isSameRouteLocation,
+  resolveRelativePath,
 } from '../src/location'
 import { RouteLocationNormalizedLoaded } from 'src'
+import { mockWarn } from 'jest-mock-warn'
 
 describe('parseURL', () => {
   let parseURL = originalParseURL.bind(null, parseQuery)
@@ -283,3 +285,61 @@ describe('isSameRouteLocation', () => {
     ).toBe(true)
   })
 })
+
+describe('resolveRelativePath', () => {
+  mockWarn()
+  it('resolves relative direct path', () => {
+    expect(resolveRelativePath('add', '/users/posva')).toBe('/users/add')
+    expect(resolveRelativePath('add', '/users/posva/')).toBe('/users/posva/add')
+    expect(resolveRelativePath('add', '/users/posva/thing')).toBe(
+      '/users/posva/add'
+    )
+  })
+
+  it('resolves relative direct path with .', () => {
+    expect(resolveRelativePath('./add', '/users/posva')).toBe('/users/add')
+    expect(resolveRelativePath('./add', '/users/posva/')).toBe(
+      '/users/posva/add'
+    )
+    expect(resolveRelativePath('./add', '/users/posva/thing')).toBe(
+      '/users/posva/add'
+    )
+  })
+
+  it('resolves relative path with ..', () => {
+    expect(resolveRelativePath('../add', '/users/posva')).toBe('/add')
+    expect(resolveRelativePath('../add', '/users/posva/')).toBe('/users/add')
+    expect(resolveRelativePath('../add', '/users/posva/thing')).toBe(
+      '/users/add'
+    )
+  })
+
+  it('resolves multiple relative paths with ..', () => {
+    expect(resolveRelativePath('../../add', '/users/posva')).toBe('/add')
+    expect(resolveRelativePath('../../add', '/users/posva/')).toBe('/add')
+    expect(resolveRelativePath('../../add', '/users/posva/thing')).toBe('/add')
+    expect(resolveRelativePath('../../../add', '/users/posva')).toBe('/add')
+  })
+
+  it('works with the root', () => {
+    expect(resolveRelativePath('add', '/')).toBe('/add')
+    expect(resolveRelativePath('./add', '/')).toBe('/add')
+    expect(resolveRelativePath('../add', '/')).toBe('/add')
+    expect(resolveRelativePath('.././add', '/')).toBe('/add')
+    expect(resolveRelativePath('./../add', '/')).toBe('/add')
+    expect(resolveRelativePath('../../add', '/')).toBe('/add')
+    expect(resolveRelativePath('../../../add', '/')).toBe('/add')
+  })
+
+  it('ignores it location is absolute', () => {
+    expect(resolveRelativePath('/add', '/users/posva')).toBe('/add')
+  })
+
+  it('warns if from path is not absolute', () => {
+    resolveRelativePath('path', 'other')
+    resolveRelativePath('path', './other')
+    resolveRelativePath('path', '../other')
+
+    expect('Cannot resolve').toHaveBeenWarnedTimes(3)
+  })
+})
index 3d7155b49b8a28a94a5f07d8897666189346d250..3e67b36702ab853bc90d47031c81b9e57d55b2cb 100644 (file)
@@ -5,6 +5,7 @@ import {
   RouteParamValue,
 } from './types'
 import { RouteRecord } from './matcher/types'
+import { warn } from './warning'
 
 /**
  * Location object returned by {@link `parseURL`}.
@@ -186,11 +187,51 @@ function isSameRouteLocationParamsValue(
  * Check if two arrays are the same or if an array with one single entry is the
  * same as another primitive value. Used to check query and parameters
  *
- * @param a array of values
- * @param b array of values or a single value
+ * @param a array of values
+ * @param b array of values or a single value
  */
 function isEquivalentArray<T>(a: T[], b: T[] | T): boolean {
   return Array.isArray(b)
     ? a.length === b.length && a.every((value, i) => value === b[i])
     : a.length === 1 && a[0] === b
 }
+
+/**
+ * Resolves a relative path that starts with `.`.
+ *
+ * @param to - path location we are resolving
+ * @param from - currentLocation.path, should start with `/`
+ */
+export function resolveRelativePath(to: string, from: string): string {
+  if (to.startsWith('/')) return to
+  if (__DEV__ && !from.startsWith('/')) {
+    warn(
+      `Cannot resolve a relative location without an absolute path. Trying to resolve "${to}" from "${from}". It should look like "/${from}".`
+    )
+    return to
+  }
+
+  const fromSegments = from.split('/')
+  const toSegments = to.split('/')
+
+  let position = fromSegments.length - 1
+  let toPosition: number
+  let segment: string
+
+  for (toPosition = 0; toPosition < toSegments.length; toPosition++) {
+    segment = toSegments[toPosition]
+    // can't go below zero
+    if (position === 1 || segment === '.') continue
+    if (segment === '..') position--
+    // found something that is not relative path
+    else break
+  }
+
+  return (
+    fromSegments.slice(0, position).join('/') +
+    '/' +
+    toSegments
+      .slice(toPosition - (toPosition === toSegments.length ? 1 : 0))
+      .join('/')
+  )
+}