]"
`;
+exports[`generateRouteRecord > drops the wrapper when its last child is deleted 1`] = `
+"[
+ {
+ path: '/home',
+ /* internal name: '/home' */
+ /* no component */
+ children: [
+ {
+ path: 'user',
+ name: '/home/user',
+ component: () => import('home/user.vue'),
+ /* no children */
+ }
+ ],
+ }
+]"
+`;
+
exports[`generateRouteRecord > encodes special characters in path segments 1`] = `
"[
{
expect(generateRouteRecordSimple(tree)).toContain("path: '/nested'")
})
+ it('skips nested lone _parent files', () => {
+ const tree = new PrefixTree(DEFAULT_OPTIONS)
+ tree.insert('users/index', 'users/index.vue')
+ tree.insert('users/settings/_parent', 'users/settings/_parent.vue')
+
+ const routes = generateRouteRecordSimple(tree)
+
+ expect(routes).toContain("path: '/users'")
+ expect(routes).not.toContain("path: '/users/settings'")
+ })
+
+ // regression test for https://github.com/vuejs/router/issues/2641
+ // when beforeWriteFiles removes the only child of a wrapper node, the
+ // wrapper must disappear too instead of emitting an empty route record
+ it('drops the wrapper when its last child is deleted', () => {
+ const tree = new PrefixTree(DEFAULT_OPTIONS)
+ tree.insert('home/user', 'home/user.vue')
+ const leaf = tree.insert('home/component/foo', 'home/component/foo.vue')
+ leaf.delete()
+
+ expect(generateRouteRecordSimple(tree)).toMatchSnapshot()
+ })
+
it('works with some paths at root', () => {
const tree = new PrefixTree(DEFAULT_OPTIONS)
tree.insert('a', 'a.vue')
expect(tree.children.get('foo')?.path).toBe('/foo')
})
+ it('removes parent when deleting last child of a non-matchable node', () => {
+ const tree = new PrefixTree(RESOLVED_OPTIONS)
+
+ const editable = new EditableTreeNode(tree)
+ editable.insert('about', 'about.vue')
+ const toDelete = editable.insert('a/b/c', 'a/b/c.vue')
+
+ expect(tree.children.size).toBe(2)
+ toDelete.delete()
+ expect(tree.children.size).toBe(1)
+ expect(tree.children.has('a')).toBe(false)
+ expect(tree.children.has('about')).toBe(true)
+ // repeatedly deleting a node should not throw
+ expect(() => toDelete.delete()).not.toThrow()
+ })
+
it('keeps nested routes flat', () => {
const tree = new PrefixTree(RESOLVED_OPTIONS)
const editable = new EditableTreeNode(tree)
expect(tree.children.size).toBe(1)
})
+ it('removes parent when deleting last child of a non-matchable node', () => {
+ const tree = new PrefixTree(RESOLVED_OPTIONS)
+ const abc = tree.insert('a/b/c', 'a/b/c.vue')
+ expect(tree.children.has('a')).toBe(true)
+ abc.delete()
+ expect(tree.children.has('a')).toBe(false)
+ })
+
it('handles multiple params', () => {
const tree = new PrefixTree(RESOLVED_OPTIONS)
tree.insert('[a]-[b]', '[a]-[b].vue')
return Array.from(this.getChildrenDeep()).sort(TreeNode.compare)
}
+ /**
+ * Delete a child node. If the child node has no more children and no
+ * components, it will be deleted as well. This is used to recursively delete
+ * empty nodes after removing a route.
+ *
+ * @param child - child node to delete
+ */
+ protected deleteChild(child: TreeNode): void {
+ this.children.delete(child.value.rawSegment)
+ // recursively delete empty parents
+ if (!this.isRoot() && !this.isMatchable() && this.children.size === 0) {
+ this.delete()
+ }
+ }
+
/**
* Delete and detach itself from the tree.
*/
- delete() {
- if (!this.parent) {
+ delete(): void {
+ if (this.isRoot()) {
throw new Error('Cannot delete the root node.')
}
- this.parent.children.delete(this.value.rawSegment)
- // clear link to parent
+ this.parent?.deleteChild(this)
+ // clear link to parent so a repeated delete() is a no-op
this.parent = undefined
}