Skip to content

Commit

Permalink
fix(types): remove trailing slash in path for nested routes
Browse files Browse the repository at this point in the history
Fix #70
  • Loading branch information
posva committed Sep 30, 2022
1 parent 8aff22f commit f0cfb36
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 10 deletions.
10 changes: 5 additions & 5 deletions playground/typed-router.d.ts
Expand Up @@ -40,7 +40,7 @@ declare module 'vue-router/auto/routes' {
'/[...path]': RouteRecordInfo<'/[...path]', '/:path(.*)', { path: ParamValue<true> }, { path: ParamValue<false> }>,
'/about': RouteRecordInfo<'/about', '/about', Record<never, never>, Record<never, never>>,
'/articles': RouteRecordInfo<'/articles', '/articles', Record<never, never>, Record<never, never>>,
'/articles/': RouteRecordInfo<'/articles/', '/articles/', Record<never, never>, Record<never, never>>,
'/articles/': RouteRecordInfo<'/articles/', '/articles', Record<never, never>, Record<never, never>>,
'/articles/[id]': RouteRecordInfo<'/articles/[id]', '/articles/:id', { id: ParamValue<true> }, { id: ParamValue<false> }>,
'/articles/[id]+': RouteRecordInfo<'/articles/[id]+', '/articles/:id+', { id: ParamValueOneOrMore<true> }, { id: ParamValueOneOrMore<false> }>,
'a rebel': RouteRecordInfo<'a rebel', '/custom-name', Record<never, never>, Record<never, never>>,
Expand All @@ -49,17 +49,17 @@ declare module 'vue-router/auto/routes' {
'/deep/nesting/works/custom-path': RouteRecordInfo<'/deep/nesting/works/custom-path', '/deep-surprise-:id(\d+)', Record<never, never>, Record<never, never>>,
'deep a rebel': RouteRecordInfo<'deep a rebel', '/deep/nesting/works/custom-name', Record<never, never>, Record<never, never>>,
'/deep/nesting/works/too': RouteRecordInfo<'/deep/nesting/works/too', '/deep/nesting/works/too', Record<never, never>, Record<never, never>>,
'/docs/:lang/': RouteRecordInfo<'/docs/:lang/', '/docs/:lang/', Record<never, never>, Record<never, never>>,
'/docs/:lang/': RouteRecordInfo<'/docs/:lang/', '/docs/:lang', Record<never, never>, Record<never, never>>,
'/docs/:lang/about': RouteRecordInfo<'/docs/:lang/about', '/docs/:lang/about', Record<never, never>, Record<never, never>>,
'the most rebel': RouteRecordInfo<'the most rebel', '/most-rebel', Record<never, never>, Record<never, never>>,
'/multiple-[a]-[b]-params': RouteRecordInfo<'/multiple-[a]-[b]-params', '/multiple-:a-:b-params', { a: ParamValue<true>, b: ParamValue<true> }, { a: ParamValue<false>, b: ParamValue<false> }>,
'/my-optional-[[slug]]': RouteRecordInfo<'/my-optional-[[slug]]', '/my-optional-:slug?', { slug?: ParamValueZeroOrOne<true> }, { slug?: ParamValueZeroOrOne<false> }>,
'/n-[[n]]/': RouteRecordInfo<'/n-[[n]]/', '/n-:n?/', { n?: ParamValueZeroOrOne<true> }, { n?: ParamValueZeroOrOne<false> }>,
'/n-[[n]]/[[more]]+/': RouteRecordInfo<'/n-[[n]]/[[more]]+/', '/n-:n?/:more*/', { n?: ParamValueZeroOrOne<true>, more?: ParamValueZeroOrMore<true> }, { n?: ParamValueZeroOrOne<false>, more?: ParamValueZeroOrMore<false> }>,
'/n-[[n]]/': RouteRecordInfo<'/n-[[n]]/', '/n-:n?', { n?: ParamValueZeroOrOne<true> }, { n?: ParamValueZeroOrOne<false> }>,
'/n-[[n]]/[[more]]+/': RouteRecordInfo<'/n-[[n]]/[[more]]+/', '/n-:n?/:more*', { n?: ParamValueZeroOrOne<true>, more?: ParamValueZeroOrMore<true> }, { n?: ParamValueZeroOrOne<false>, more?: ParamValueZeroOrMore<false> }>,
'/n-[[n]]/[[more]]+/[final]': RouteRecordInfo<'/n-[[n]]/[[more]]+/[final]', '/n-:n?/:more*/:final', { n?: ParamValueZeroOrOne<true>, more?: ParamValueZeroOrMore<true>, final: ParamValue<true> }, { n?: ParamValueZeroOrOne<false>, more?: ParamValueZeroOrMore<false>, final: ParamValue<false> }>,
'/partial-[name]': RouteRecordInfo<'/partial-[name]', '/partial-:name', { name: ParamValue<true> }, { name: ParamValue<false> }>,
'/custom-path': RouteRecordInfo<'/custom-path', '/surprise-:id(\d+)', Record<never, never>, Record<never, never>>,
'/users/': RouteRecordInfo<'/users/', '/users/', Record<never, never>, Record<never, never>>,
'/users/': RouteRecordInfo<'/users/', '/users', Record<never, never>, Record<never, never>>,
'/users/[id]': RouteRecordInfo<'/users/[id]', '/users/:id', { id: ParamValue<true> }, { id: ParamValue<false> }>,
'/users/[id].edit': RouteRecordInfo<'/users/[id].edit', '/users/:id/edit', { id: ParamValue<true> }, { id: ParamValue<false> }>,
'/users/nested.route.deep': RouteRecordInfo<'/users/nested.route.deep', '/users/nested/route/deep', Record<never, never>, Record<never, never>>,
Expand Down
49 changes: 49 additions & 0 deletions src/codegen/__snapshots__/generateRouteRecords.spec.ts.snap
Expand Up @@ -546,6 +546,55 @@ exports[`generateRouteRecord > nested children 2`] = `
]"
`;

exports[`generateRouteRecord > removes trailing slashes 1`] = `
"[
{
path: '/nested',
name: '/nested',
component: () => import('nested.vue'),
/* no props */
children: [
{
path: '',
name: '/nested/',
component: () => import('nested/index.vue'),
/* no props */
/* no children */
},
{
path: 'other',
name: '/nested/other',
component: () => import('nested/other.vue'),
/* no props */
/* no children */
}
],
},
{
path: '/users',
/* no name */
/* no component */
/* no props */
children: [
{
path: '',
name: '/users/',
component: () => import('users/index.vue'),
/* no props */
/* no children */
},
{
path: 'other',
name: '/users/other',
component: () => import('users/other.vue'),
/* no props */
/* no children */
}
],
}
]"
`;

exports[`generateRouteRecord > route block > adds meta data 1`] = `
"[
{
Expand Down
4 changes: 3 additions & 1 deletion src/codegen/generateRouteMap.spec.ts
Expand Up @@ -15,11 +15,13 @@ function formatExports(exports: string) {
describe('generateRouteNamedMap', () => {
it('works with some paths at root', () => {
const tree = createPrefixTree(DEFAULT_OPTIONS)
tree.insert('index.vue')
tree.insert('a.vue')
tree.insert('b.vue')
tree.insert('c.vue')
expect(formatExports(generateRouteNamedMap(tree))).toMatchInlineSnapshot(`
"export interface RouteNamedMap {
'/': RouteRecordInfo<'/', '/', Record<never, never>, Record<never, never>>,
'/a': RouteRecordInfo<'/a', '/a', Record<never, never>, Record<never, never>>,
'/b': RouteRecordInfo<'/b', '/b', Record<never, never>, Record<never, never>>,
'/c': RouteRecordInfo<'/c', '/c', Record<never, never>, Record<never, never>>,
Expand Down Expand Up @@ -57,7 +59,7 @@ describe('generateRouteNamedMap', () => {
tree.insert('n/[a]/[c]/other-[d].vue')
expect(formatExports(generateRouteNamedMap(tree))).toMatchInlineSnapshot(`
"export interface RouteNamedMap {
'/n/[a]/': RouteRecordInfo<'/n/[a]/', '/n/:a/', { a: ParamValue<true> }, { a: ParamValue<false> }>,
'/n/[a]/': RouteRecordInfo<'/n/[a]/', '/n/:a', { a: ParamValue<true> }, { a: ParamValue<false> }>,
'/n/[a]/[b]': RouteRecordInfo<'/n/[a]/[b]', '/n/:a/:b', { a: ParamValue<true>, b: ParamValue<true> }, { a: ParamValue<false>, b: ParamValue<false> }>,
'/n/[a]/[c]/other-[d]': RouteRecordInfo<'/n/[a]/[c]/other-[d]', '/n/:a/:c/other-:d', { a: ParamValue<true>, c: ParamValue<true>, d: ParamValue<true> }, { a: ParamValue<false>, c: ParamValue<false>, d: ParamValue<false> }>,
'/n/[a]/other': RouteRecordInfo<'/n/[a]/other', '/n/:a/other', { a: ParamValue<true> }, { a: ParamValue<false> }>,
Expand Down
10 changes: 10 additions & 0 deletions src/codegen/generateRouteRecords.spec.ts
Expand Up @@ -87,6 +87,16 @@ describe('generateRouteRecord', () => {
expect(generateRouteRecordSimple(tree)).toMatchSnapshot()
})

it('removes trailing slashes', () => {
const tree = createPrefixTree(DEFAULT_OPTIONS)
tree.insert('users/index.vue')
tree.insert('users/other.vue')
tree.insert('nested.vue')
tree.insert('nested/index.vue')
tree.insert('nested/other.vue')
expect(generateRouteRecordSimple(tree)).toMatchSnapshot()
})

it('generate static imports', () => {
const options: ResolvedOptions = {
...DEFAULT_OPTIONS,
Expand Down
24 changes: 23 additions & 1 deletion src/core/tree.spec.ts
Expand Up @@ -199,6 +199,7 @@ describe('Tree', () => {
const index = tree.children.get('index')!
expect(index.value).toMatchObject({
rawSegment: '',
// the root should have a '/' instead of '' for the autocompletion
path: '/',
})
expect(index).toBeDefined()
Expand All @@ -215,7 +216,7 @@ describe('Tree', () => {
expect(Array.from(aIndex.children.keys())).toEqual([])
expect(aIndex.value).toMatchObject({
rawSegment: '',
path: '/a/',
path: '/a',
})

tree.insert('a.vue')
Expand Down Expand Up @@ -325,4 +326,25 @@ describe('Tree', () => {
expect(node.path).toBe('/custom-child')
expect(node.fullPath).toBe('/custom-child')
})

it('removes trailing slash from path but not from name', () => {
const tree = createPrefixTree(DEFAULT_OPTIONS)
tree.insert('a/index.vue')
tree.insert('a/a.vue')
let child = tree.children.get('a')!
expect(child).toBeDefined()
expect(child.fullPath).toBe('/a')

child = tree.children.get('a')!.children.get('index')!
expect(child).toBeDefined()
expect(child.name).toBe('/a/')
expect(child.fullPath).toBe('/a')

// it stays the same with a parent component in the parent route record
tree.insert('a.vue')
child = tree.children.get('a')!.children.get('index')!
expect(child).toBeDefined()
expect(child.name).toBe('/a/')
expect(child.fullPath).toBe('/a')
})
})
4 changes: 3 additions & 1 deletion src/core/treeNodeValue.ts
Expand Up @@ -65,8 +65,10 @@ class _TreeNodeValueBase {
this.rawSegment = rawSegment
this.pathSegment = pathSegment
this.subSegments = subSegments
const parentPath = parent?.path
this.path =
!parent?.path && this.pathSegment === ''
// both the root record and the index record have a path of /
(!parentPath || parentPath === '/') && this.pathSegment === ''
? '/'
: joinPath(parent?.path || '', this.pathSegment)
this.filePaths = new Map()
Expand Down
4 changes: 2 additions & 2 deletions src/core/utils.ts
Expand Up @@ -94,8 +94,8 @@ export function joinPath(...paths: string[]): string {
for (const path of paths) {
result =
result.replace(TRAILING_SLASH_RE, '') +
'/' +
path.replace(LEADING_SLASH_RE, '')
// check path to avoid adding a trailing slash when joining an empty string
(path && '/' + path.replace(LEADING_SLASH_RE, ''))
}
return result
}
Expand Down

0 comments on commit f0cfb36

Please sign in to comment.