Skip to content

Commit

Permalink
fix: handle raw segments in EditableTreeNode
Browse files Browse the repository at this point in the history
  • Loading branch information
posva committed Apr 27, 2023
1 parent c89d602 commit 5695522
Show file tree
Hide file tree
Showing 4 changed files with 307 additions and 31 deletions.
81 changes: 81 additions & 0 deletions src/core/extendRoutes.spec.ts
@@ -0,0 +1,81 @@
import { expect, describe, it } from 'vitest'
import { createPrefixTree } from './tree'
import { DEFAULT_OPTIONS } from '../options'
import { EditableTreeNode } from './extendRoutes'

describe('EditableTreeNode', () => {
it('creates an editable tree node', () => {
const tree = createPrefixTree(DEFAULT_OPTIONS)
const editable = new EditableTreeNode(tree)

expect(editable.children).toEqual([])
})

it('reflects changes made on the tree', () => {
const tree = createPrefixTree(DEFAULT_OPTIONS)
const editable = new EditableTreeNode(tree)

tree.insert('foo', 'file.vue')
expect(editable.children).toHaveLength(1)
expect(editable.children[0].path).toBe('/foo')
})

it('reflects changes made on the editable tree', () => {
const tree = createPrefixTree(DEFAULT_OPTIONS)
const editable = new EditableTreeNode(tree)

editable.insert('foo', 'file.vue')
expect(tree.children.size).toBe(1)
expect(tree.children.get('foo')?.path).toBe('/foo')
})

it('can insert nested nodes', () => {
const tree = createPrefixTree(DEFAULT_OPTIONS)
const editable = new EditableTreeNode(tree)

editable.insert('foo/bar', 'file.vue')
expect(tree.children.size).toBe(1)
expect(tree.children.get('foo')?.children.size).toBe(1)
expect(tree.children.get('foo')?.children.get('bar')?.path).toBe('bar')
})

it('adds params', () => {
const tree = createPrefixTree(DEFAULT_OPTIONS)
const editable = new EditableTreeNode(tree)

editable.insert(':id', 'file.vue')
expect(tree.children.size).toBe(1)
const child = tree.children.get(':id')!
expect(child.fullPath).toBe('/:id')
expect(child.path).toBe('/:id')
expect(child.params).toEqual([
{
paramName: 'id',
modifier: '',
optional: false,
repeatable: false,
isSplat: false,
},
])
})

it('add params with modifiers', () => {
const tree = createPrefixTree(DEFAULT_OPTIONS)
const editable = new EditableTreeNode(tree)

editable.insert(':id+', 'file.vue')
expect(tree.children.size).toBe(1)
const child = tree.children.get(':id+')!
expect(child.fullPath).toBe('/:id+')
expect(child.path).toBe('/:id+')
expect(child.params).toEqual([
{
paramName: 'id',
modifier: '+',
optional: false,
repeatable: true,
isSplat: false,
},
])
})
})
11 changes: 5 additions & 6 deletions src/core/extendRoutes.ts
Expand Up @@ -33,13 +33,12 @@ export class EditableTreeNode {
/**
* Inserts a new route as a child of this route. This route cannot use `definePage()`. If it was meant to be included,
* add it to the `routesFolder` option.
*
* @param path - path segment to insert. Note this is relative to the current route. It shouldn't start with `/` unless you want the route path to be absolute.
* added at the root of the tree.
* @param filePath - file path
*/
insert(path: string, filePath: string) {
const extDotIndex = filePath.lastIndexOf('.')
const ext = filePath.slice(extDotIndex)
if (!path.endsWith(ext)) {
path += ext
}
// adapt paths as they should match a file system
let addBackLeadingSlash = false
if (path.startsWith('/')) {
Expand All @@ -49,7 +48,7 @@ export class EditableTreeNode {
// but in other places we need to instruct the path is at the root so we change it afterwards
addBackLeadingSlash = !this.node.isRoot()
}
const node = this.node.insert(path, filePath)
const node = this.node.insertParsedPath(path, filePath)
const editable = new EditableTreeNode(node)
if (addBackLeadingSlash) {
editable.path = '/' + node.path
Expand Down
61 changes: 57 additions & 4 deletions src/core/tree.ts
@@ -1,10 +1,18 @@
import type { ResolvedOptions } from '../options'
import { createTreeNodeValue, TreeRouteParam } from './treeNodeValue'
import {
createTreeNodeValue,
TreeNodeValueOptions,
TreeRouteParam,
} from './treeNodeValue'
import type { TreeNodeValue } from './treeNodeValue'
import { trimExtension } from './utils'
import { CustomRouteBlock } from './customBlock'
import { RouteMeta } from 'vue-router'

export interface TreeNodeOptions extends ResolvedOptions {
treeNodeOptions?: TreeNodeValueOptions
}

export class TreeNode {
/**
* value of the node
Expand All @@ -24,20 +32,20 @@ export class TreeNode {
/**
* Plugin options taken into account by the tree.
*/
options: ResolvedOptions
options: TreeNodeOptions

/**
* Should this page import the page info
*/
hasDefinePage: boolean = false

constructor(options: ResolvedOptions, filePath: string, parent?: TreeNode) {
constructor(options: TreeNodeOptions, filePath: string, parent?: TreeNode) {
this.options = options
this.parent = parent
this.value = createTreeNodeValue(
filePath,
parent?.value,
options.pathParser
options.treeNodeOptions
)
}

Expand Down Expand Up @@ -69,6 +77,51 @@ export class TreeNode {
return child
}

/**
* Adds a path to the tree. `path` cannot start with a `/`.
*
* @param path - path segment to insert, already parsed (e.g. users/:id)
* @param filePath - file path, defaults to path for convenience and testing
*/
insertParsedPath(path: string, filePath: string = path): TreeNode {
const slashPos = path.indexOf('/')
const segment = slashPos < 0 ? path : path.slice(0, slashPos)
const tail = slashPos < 0 ? '' : path.slice(slashPos + 1)

// TODO: allow null filePath?
const isComponent = !tail

if (!this.children.has(segment)) {
this.children.set(
segment,
new TreeNode(
{
...this.options,
// force the format to raw
treeNodeOptions: {
...this.options.pathParser,
format: 'path',
},
},
segment,
this
)
)
}
const child = this.children.get(segment)!

if (isComponent) {
// TODO: allow a way to set the view name
child.value.components.set('default', filePath)
}

if (tail) {
return child.insertParsedPath(tail, filePath)
}

return child
}

setCustomRouteBlock(path: string, routeBlock: CustomRouteBlock | undefined) {
this.value.setOverride(path, routeBlock)
}
Expand Down

0 comments on commit 5695522

Please sign in to comment.