Skip to content

Commit 0ccbc1e

Browse files
committedFeb 28, 2020
fix(link): non active repeatable params
1 parent 353d34e commit 0ccbc1e

File tree

7 files changed

+144
-36
lines changed

7 files changed

+144
-36
lines changed
 

‎__tests__/RouterLink.spec.ts

+46
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,34 @@ const locations: Record<
7171
name: undefined,
7272
},
7373
},
74+
repeatedParams2: {
75+
string: '/p/1/2',
76+
normalized: {
77+
fullPath: '/p/1/2',
78+
path: '/p/1/2',
79+
params: { p: ['1', '2'] },
80+
meta: {},
81+
query: {},
82+
hash: '',
83+
matched: [records.home],
84+
redirectedFrom: undefined,
85+
name: undefined,
86+
},
87+
},
88+
repeatedParams3: {
89+
string: '/p/1/2/3',
90+
normalized: {
91+
fullPath: '/p/1/2/3',
92+
path: '/p/1/2/3',
93+
params: { p: ['1', '2', '3'] },
94+
meta: {},
95+
query: {},
96+
hash: '',
97+
matched: [records.home],
98+
redirectedFrom: undefined,
99+
name: undefined,
100+
},
101+
},
74102
}
75103

76104
describe('RouterLink', () => {
@@ -143,6 +171,24 @@ describe('RouterLink', () => {
143171
expect(el.querySelector('a')!.className).toContain('router-link-active')
144172
})
145173

174+
it('is not active with more repeated params', () => {
175+
const { el } = factory(
176+
locations.repeatedParams2.normalized,
177+
{ to: locations.repeatedParams3.string },
178+
locations.repeatedParams3.normalized
179+
)
180+
expect(el.querySelector('a')!.className).toBe('')
181+
})
182+
183+
it('is not active with partial repeated params', () => {
184+
const { el } = factory(
185+
locations.repeatedParams3.normalized,
186+
{ to: locations.repeatedParams2.string },
187+
locations.repeatedParams2.normalized
188+
)
189+
expect(el.querySelector('a')!.className).toBe('')
190+
})
191+
146192
it.todo('can be active as an alias')
147193
it.todo('can be exact-active as an alias')
148194
it.todo('is active when a child is active')

‎__tests__/router.spec.ts

+46
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,52 @@ describe('Router', () => {
382382
})
383383
})
384384

385+
it('can reroute to a replaced route with the same component', async () => {
386+
const { router } = await newRouter()
387+
router.addRoute({
388+
path: '/new/foo',
389+
component: components.Foo,
390+
name: 'new',
391+
})
392+
// navigate to the route we just added
393+
await router.replace({ name: 'new' })
394+
// replace it
395+
router.addRoute({
396+
path: '/new/bar',
397+
component: components.Foo,
398+
name: 'new',
399+
})
400+
// navigate again
401+
await router.replace({ name: 'new' })
402+
expect(router.currentRoute.value).toMatchObject({
403+
path: '/new/bar',
404+
name: 'new',
405+
})
406+
})
407+
408+
it('can reroute to child', async () => {
409+
const { router } = await newRouter()
410+
router.addRoute({
411+
path: '/new',
412+
component: components.Foo,
413+
children: [],
414+
name: 'new',
415+
})
416+
// navigate to the route we just added
417+
await router.replace('/new/child')
418+
// replace it
419+
router.addRoute('new', {
420+
path: 'child',
421+
component: components.Bar,
422+
name: 'new-child',
423+
})
424+
// navigate again
425+
await router.replace('/new/child')
426+
expect(router.currentRoute.value).toMatchObject({
427+
name: 'new-child',
428+
})
429+
})
430+
385431
it('can reroute when adding a new route', async () => {
386432
const { router } = await newRouter()
387433
await router.push('/p/p')

‎playground/App.vue

+9
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,15 @@
106106
>/docs/é</router-link
107107
>
108108
</li>
109+
<li>
110+
<router-link to="/rep">/rep</router-link>
111+
</li>
112+
<li>
113+
<router-link to="/rep/a">/rep/a</router-link>
114+
</li>
115+
<li>
116+
<router-link to="/rep/a/b">/rep/a/b</router-link>
117+
</li>
109118
</ul>
110119
<!-- <transition
111120
name="fade"

‎src/components/Link.ts

+23-31
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@ import {
99
unref,
1010
} from 'vue'
1111
import { RouteLocation, RouteLocationNormalized, Immutable } from '../types'
12-
import { isSameLocationObject } from '../utils'
12+
import { isSameLocationObject, isSameRouteRecord } from '../utils'
1313
import { routerKey } from '../injectKeys'
14-
import { RouteRecordNormalized } from '../matcher/types'
1514

1615
type VueUseOptions<T> = {
1716
[k in keyof T]: Ref<T[k]> | T[k]
@@ -25,35 +24,6 @@ interface LinkProps {
2524

2625
type UseLinkOptions = VueUseOptions<LinkProps>
2726

28-
function isSameRouteRecord(
29-
a: Immutable<RouteRecordNormalized>,
30-
b: Immutable<RouteRecordNormalized>
31-
): boolean {
32-
// TODO: handle aliases
33-
return a === b
34-
}
35-
36-
function includesParams(
37-
outter: Immutable<RouteLocationNormalized['params']>,
38-
inner: Immutable<RouteLocationNormalized['params']>
39-
): boolean {
40-
for (let key in inner) {
41-
let innerValue = inner[key]
42-
let outterValue = outter[key]
43-
if (typeof innerValue === 'string') {
44-
if (innerValue !== outterValue) return false
45-
} else {
46-
if (
47-
!Array.isArray(outterValue) ||
48-
innerValue.some((value, i) => value !== outterValue[i])
49-
)
50-
return false
51-
}
52-
}
53-
54-
return true
55-
}
56-
5727
export function useLink(props: UseLinkOptions) {
5828
const router = inject(routerKey)!
5929

@@ -147,3 +117,25 @@ function guardEvent(e: MouseEvent) {
147117

148118
return true
149119
}
120+
121+
function includesParams(
122+
outter: Immutable<RouteLocationNormalized['params']>,
123+
inner: Immutable<RouteLocationNormalized['params']>
124+
): boolean {
125+
for (let key in inner) {
126+
let innerValue = inner[key]
127+
let outterValue = outter[key]
128+
if (typeof innerValue === 'string') {
129+
if (innerValue !== outterValue) return false
130+
} else {
131+
if (
132+
!Array.isArray(outterValue) ||
133+
outterValue.length !== innerValue.length ||
134+
innerValue.some((value, i) => value !== outterValue[i])
135+
)
136+
return false
137+
}
138+
}
139+
140+
return true
141+
}

‎src/router.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
guardToPromiseFn,
2929
isSameLocationObject,
3030
applyToParams,
31+
isSameRouteRecord,
3132
} from './utils'
3233
import { useCallbacks } from './utils/callbacks'
3334
import { encodeParam, decode } from './utils/encoding'
@@ -177,7 +178,7 @@ export function createRouter({
177178
path: matchedRoute.path,
178179
}),
179180
hash: location.hash || '',
180-
query: normalizeQuery(location.query || {}),
181+
query: normalizeQuery(location.query),
181182
...matchedRoute,
182183
redirectedFrom: undefined,
183184
}
@@ -197,7 +198,6 @@ export function createRouter({
197198
const force: boolean | undefined = to.force
198199

199200
// TODO: should we throw an error as the navigation was aborted
200-
// TODO: needs a proper check because order in query could be different
201201
if (!force && isSameLocation(from, toLocation)) return from
202202

203203
toLocation.redirectedFrom = redirectedFrom
@@ -543,6 +543,8 @@ function isSameLocation(
543543
a.name === b.name &&
544544
a.path === b.path &&
545545
a.hash === b.hash &&
546-
isSameLocationObject(a.query, b.query)
546+
isSameLocationObject(a.query, b.query) &&
547+
a.matched.length === b.matched.length &&
548+
a.matched.every((record, i) => isSameRouteRecord(record, b.matched[i]))
547549
)
548550
}

‎src/utils/index.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ export function applyToParams(
5151
return newParams
5252
}
5353

54+
export function isSameRouteRecord(
55+
a: Immutable<RouteRecordNormalized>,
56+
b: Immutable<RouteRecordNormalized>
57+
): boolean {
58+
// TODO: handle aliases
59+
return a === b
60+
}
61+
5462
export function isSameLocationObject(
5563
a: Immutable<RouteLocationNormalized['query']>,
5664
b: Immutable<RouteLocationNormalized['query']>
@@ -97,6 +105,9 @@ function isSameLocationObjectValue(
97105
if (typeof a !== typeof b) return false
98106
// both a and b are arrays
99107
if (Array.isArray(a))
100-
return a.every((value, i) => value === (b as LocationQueryValue[])[i])
108+
return (
109+
a.length === (b as any[]).length &&
110+
a.every((value, i) => value === (b as LocationQueryValue[])[i])
111+
)
101112
return a === b
102113
}

‎src/utils/query.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ export function stringifyQuery(query: LocationQueryRaw): string {
8282
* null in arrays
8383
* @param query
8484
*/
85-
export function normalizeQuery(query: LocationQueryRaw): LocationQuery {
85+
export function normalizeQuery(
86+
query: LocationQueryRaw | undefined
87+
): LocationQuery {
8688
const normalizedQuery: LocationQuery = {}
8789

8890
for (let key in query) {

0 commit comments

Comments
 (0)