Skip to content

Commit

Permalink
FEAT: change API and add descriptible errors
Browse files Browse the repository at this point in the history
FIX: bugs and errors
  • Loading branch information
ch3ber committed Jul 17, 2023
1 parent d9e2a80 commit 8876f1b
Show file tree
Hide file tree
Showing 16 changed files with 290 additions and 76 deletions.
3 changes: 2 additions & 1 deletion __test__/e2e/index.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Router.create({
path404: '/notFound',
renderId: '#app'
})

const router = Router.get()

const Menu = () => {
Expand Down Expand Up @@ -54,8 +55,8 @@ router.addRoute('/', async () => {

router.addRoute('/test', () => {
// console.log(getRouteInfo.get())
return foo
// document.querySelector('body')!.innerHTML = foo() as string
return foo
})

router.addRoute('/test/test', () => {
Expand Down
5 changes: 5 additions & 0 deletions __test__/mocks/routerConfigs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { RouterConfig } from '@/types'

export const routerConfigOnly404Path: RouterConfig = {
path404: '/404'
}
40 changes: 40 additions & 0 deletions __test__/rendering/application/RenderRoute.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { RenderRoute } from '@/rendering/application/RenderRoute'
import { RouteManager } from '@/routes/application/routeManager'
import { RouteI } from '@/types'
import { describe, expect, it } from 'vitest'

// const mock = vi.fn()
// vi.doMock('@/rendering/application/RenderRoute', () => ({ renderInHtmlNode: mock }))

// vi.mock('@/rendering/application/RenderRoute', async (importOriginal) => {
// const mod = await importOriginal()
// return {
// ...mod,
// renderInHtmlNode: mock
// }
// })

describe.skip('RenderRoute', () => {
describe('renderCurrentRoute', () => {
it('should render a string route', async () => {
const route: RouteI = {
path: '/',
callback: async () => () => 'template'
}
const renderRoute = new RenderRoute('#app')
const routeManager = RouteManager.getInstance()
routeManager.add(route)

window.location.replace('/#/')

const div = document.createElement('div')
div.setAttribute('id', 'app')
// document.appendChild(div)
await renderRoute.renderCurrentRoute()

const divContent = div.textContent

expect(divContent).include('template')
})
})
})
7 changes: 7 additions & 0 deletions __test__/router/application/AddRoute.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { describe, expect, it } from 'vitest'

describe('AddRoute class', () => {
it('should be true', () => {
expect(true).toBe(true)
})
})
17 changes: 17 additions & 0 deletions __test__/router/application/GetRouteParam.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { describe, expect, it } from 'vitest'
import { Router } from '@/router/infrastructure/router'
import { GetRouteParam } from '@/router/application/GetRouteParam'
import { routerConfigOnly404Path } from '../../mocks/routerConfigs'

describe('GetRouteParam class', () => {
it('should return a param as string', () => {
const router = Router.create(routerConfigOnly404Path)
router.addRoute('/foo/:id', () => {})
const getRouteParam = new GetRouteParam()

window.location.replace('/#/foo/bar')
const paramExpected = 'bar'

expect(getRouteParam.get()).toBe(paramExpected)
})
})
52 changes: 47 additions & 5 deletions __test__/router/infrastructure/router.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,57 @@
import { Router } from '@/router/infrastructure/router'
import { describe, expect, it } from 'vitest'
import { RouterConfig } from '@/types'
import { beforeEach, describe, expect, it } from 'vitest'

describe('Router test', () => {
describe('createInstance method', () => {
it('Trhow an error if path404 is not present', () => {
describe('create method', () => {
beforeEach(() => {
Router.destroy()
})

it('Should throw an error if no recive a 404 path in the config', () => {
const testFn = () => {
Router.create({})
}
const errorMsg = 'Need specific 404 HTTP Error path in Router config. To fix use: { path404: \'YOUR PATH\' }'

expect(testFn).throws(errorMsg)
const errorMsg = 'Please specific a 404 HTTP Error path in your Router config. To fix add { path404: "/example/path" } to your config.'

expect(testFn).toThrowError(errorMsg)
})

it('Sould throw an error if the user try to create another Router', () => {
const routerConfig: RouterConfig = {
path404: '/404'
}
const testFn = () => {
Router.create(routerConfig)
Router.create(routerConfig)
}
const errorMsg = 'You are trying to create another Router.'

expect(testFn).toThrowError(errorMsg)
})

it('should return a router if there is not one created', () => {
const routerConfig: RouterConfig = {
path404: '/404'
}
const router = Router.create(routerConfig)

expect(router).instanceOf(Router)
})
})

describe('get method', () => {
beforeEach(() => {
Router.destroy()
})

it('should return an error if Router doesnt have a instance', () => {
const testFn = () => {
return Router.get()
}

expect(testFn).toThrowError('First you need create a Router. To fix use: "Router.create({})"')
})
})
})
31 changes: 31 additions & 0 deletions src/rendering/application/RenderRoute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { RouterConfig } from '@/types'
import { renderTemplateInHtmlByQueryId } from '../domain/renderTemplateInHtmlByQueryId'
import { GetRouteInfo } from '@/routes/application/getRouteInfo'

export class RenderRoute {
private getRouteInfo = new GetRouteInfo()
private readonly renderId

constructor (renderId: RouterConfig['renderId']) {
this.renderId = renderId
}

async renderCurrentRoute () {
const callback = this.getRouteInfo.callback()

if (callback === undefined) {
const currentRoute = this.getRouteInfo.path()
throw new Error(`Error rendering a route. The callback for "${currentRoute}" is undefine. To fix add a callback for your route with "routerInstance.addRoute('/foo', callback)"`)
}

const template = await callback()

// check if template rendering is enable but the current route
// doesn't have a template
if (template === undefined) {
return
}

await renderTemplateInHtmlByQueryId(template, this.renderId)
}
}
6 changes: 0 additions & 6 deletions src/rendering/domain/renderInHtmlNode.ts

This file was deleted.

11 changes: 11 additions & 0 deletions src/rendering/domain/renderTemplateInHtmlByQueryId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { RouterConfig, Template } from '@types'

export const renderTemplateInHtmlByQueryId = async (template: () => Template, queryId: RouterConfig['renderId']) => {
const container = document.querySelector(`${queryId}`)

if (container === null) {
throw new Error(`Error rendering a template in the document. The query ${queryId} doesn't match with any element in the document`)
}

container.innerHTML = await template()
}
19 changes: 19 additions & 0 deletions src/router/application/AddRoute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { RouteManager } from '@/routes/application/routeManager'
import { RouteCallback, RoutePath } from '@/types'

export class AddRoute {
private routeManager = RouteManager.getInstance()

add (path: RoutePath, callback: RouteCallback) {
if (typeof path !== 'string') {
throw new Error('Error adding a route. The route path must be a type string')
}

if (path[0] !== '/') {
throw new Error('Error adding a route. All paths need start with "/". Example: /test/route or /dynamic/route/:id')
}

const route = { path, callback }
this.routeManager.add(route)
}
}
14 changes: 14 additions & 0 deletions src/router/application/ExecuteCurrentRouteCallaback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { GetRouteInfo } from '@/routes/application/getRouteInfo'

export class ExecuteCurrentRouteCallback {
private getRouteInfo = new GetRouteInfo()

async execute () {
const callback = this.getRouteInfo.callback()
if (callback === undefined) {
const currentRoute = this.getRouteInfo.path()
throw new Error(`Error rendering a route. The callback for "${currentRoute}" is undefine. To fix add a callback for your route with "routerInstance.addRoute('/foo', callback)"`)
}
await callback()
}
}
8 changes: 8 additions & 0 deletions src/router/application/GetRouteParam.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
type RouteParam = string

export class GetRouteParam {
get (): RouteParam {
const splitPath = window.location.hash.slice(1).split('/')
return splitPath[splitPath.length - 1]
}
}
59 changes: 22 additions & 37 deletions src/router/application/mountRouter.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,23 @@
import { RouterConfig, Template, RoutePath } from '@/types'
import { RouterConfig } from '@/types'

import { renderInHtmlNode } from '@/rendering/domain/renderInHtmlNode'
import { GetRouteInfo } from '@/routes/application/getRouteInfo'
import { RouteManager } from '@/routes/application/routeManager'
import { Redirect } from './redirect'
import { GetRouteInfo } from '@/routes/application/getRouteInfo'
import { RenderRoute } from '@/rendering/application/RenderRoute'
import { ExecuteCurrentRouteCallback } from './ExecuteCurrentRouteCallaback'

export class MountRouter {
private getRouteInfo = new GetRouteInfo()
private redirect = new Redirect()
private getRouteInfo = new GetRouteInfo()
private executeCurrentCallback = new ExecuteCurrentRouteCallback()
private renderRoute: RenderRoute

private path404
private renderId
private routeManager
private readonly path404
private readonly renderId

constructor (config: RouterConfig) {
this.path404 = config.path404
this.renderId = config.renderId
this.routeManager = RouteManager.getInstance()
}

/*
* render actual route into html node (renderId)
*/
private async renderRoute (path: RoutePath) {
const { callback } = this.routeManager.find(path)!
const template = await callback() as unknown
await renderInHtmlNode(template as () => Template, this.renderId as string)
this.renderRoute = new RenderRoute(this.renderId)
}

/*
Expand All @@ -34,21 +26,11 @@ export class MountRouter {
* change event occours
*/
async mount (): Promise<void> {
// set route to /#/
// set windows.location.href to /#/
window.location.hash = '/'

// mount router
window.addEventListener('hashchange', (e) => this.onHashChange(e))

// render indexRoute
if (this.renderId === undefined) {
const routeInfo = this.getRouteInfo.get()
const callback = routeInfo.callback!
await callback()
return
}

await this.renderRoute('/')
window.addEventListener('hashchange', async (e) => await this.onHashChange(e))
}

/*
Expand All @@ -57,20 +39,23 @@ export class MountRouter {
*/
private async onHashChange (event: HashChangeEvent): Promise<void> {
event.preventDefault()
const path = this.getRouteInfo.path()
// get the current user path
const currentUserPath = this.getRouteInfo.path()

if (!this.getRouteInfo.isValidRoute(path)) {
// validate if the path exist in the app,
// if not exist redirect the user to the 404 page
const theCurrentPathExist = this.getRouteInfo.isValidRoute(currentUserPath)
if (!theCurrentPathExist) {
this.redirect.to(this.path404)
return
}

if (this.renderId === undefined) {
const routeInfo = this.getRouteInfo.get()
const callback = routeInfo.callback!
await callback()
const templateRenderingIsDisable = this.renderId === undefined
if (templateRenderingIsDisable) {
await this.executeCurrentCallback.execute()
return
}

await this.renderRoute(path)
await this.renderRoute.renderCurrentRoute()
}
}
Loading

0 comments on commit 8876f1b

Please sign in to comment.