]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
refactor: use named exports
authorEduardo San Martin Morote <posva13@gmail.com>
Thu, 11 Apr 2019 13:09:23 +0000 (15:09 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Thu, 11 Apr 2019 13:09:23 +0000 (15:09 +0200)
__tests__/html5.spec.ts
__tests__/router.spec.ts [new file with mode: 0644]
explorations/html5.ts
package.json
src/history/abstract.ts [new file with mode: 0644]
src/history/base.ts
src/history/html5.ts
src/index.ts
src/router.ts [new file with mode: 0644]

index bd05fa0854f83b0fa92a45bdf8c7616372687c17..65c4ecb6c35160b76052d1f8485d9df3c124e087 100644 (file)
@@ -1,4 +1,4 @@
-import History from '../src/history/html5'
+import { HTML5History } from '../src/history/html5'
 import { JSDOM } from 'jsdom'
 
 describe('History HTMl5', () => {
@@ -18,7 +18,7 @@ describe('History HTMl5', () => {
   })
 
   it('can be instantiated', () => {
-    const history = new History()
+    const history = new HTML5History()
     expect(history.location).toBe('/')
   })
 })
diff --git a/__tests__/router.spec.ts b/__tests__/router.spec.ts
new file mode 100644 (file)
index 0000000..b27e1ff
--- /dev/null
@@ -0,0 +1,32 @@
+import { HTML5History } from '../src/history/html5'
+import { Router } from '../src/router'
+import { JSDOM } from 'jsdom'
+
+describe('Router', () => {
+  beforeAll(() => {
+    // TODO: move to utils for tests that need DOM
+    const dom = new JSDOM(
+      `<!DOCTYPE html><html><head></head><body></body></html>`,
+      {
+        url: 'https://example.org/',
+        referrer: 'https://example.com/',
+        contentType: 'text/html',
+      }
+    )
+
+    // @ts-ignore
+    global.window = dom.window
+  })
+
+  it('can be instantiated', () => {
+    const history = new HTML5History()
+    const router = new Router({ history, routes: [] })
+    expect(router.currentRoute).toEqual({
+      fullPath: '/',
+      hash: '',
+      params: {},
+      path: '/',
+      query: {},
+    })
+  })
+})
index fa922ca68b839c1dccbcac4f59c206e061e7cace..9af1d6f5dece664a777bd09d9cbc0e783de1ece2 100644 (file)
@@ -1,5 +1,4 @@
-import HTML5History from '../src/history/html5'
-import { Router } from '../src'
+import { Router, HTML5History } from '../src'
 
 const component = null
 
index 2de6cf191cddbffdf8d8de538c19634317aa8c99..8062749bee22f4f26ce6c960480371a03f3f2348 100644 (file)
@@ -6,6 +6,7 @@
   "license": "MIT",
   "scripts": {
     "test:unit": "jest --coverage",
+    "test:unit:watch": "yarn run test:unit --watchAll",
     "dev": "webpack-dev-server --mode=development"
   },
   "devDependencies": {
diff --git a/src/history/abstract.ts b/src/history/abstract.ts
new file mode 100644 (file)
index 0000000..d21ee4c
--- /dev/null
@@ -0,0 +1,166 @@
+import consola from 'consola'
+import { BaseHistory } from './base'
+import {
+  HistoryLocation,
+  NavigationCallback,
+  HistoryState,
+  NavigationType,
+  HistoryURL,
+} from './base'
+
+const cs = consola.withTag('html5')
+
+type PopStateListener = (this: Window, ev: PopStateEvent) => any
+
+export class AbstractHistory extends BaseHistory {
+  private history = window.history
+  private _popStateListeners: PopStateListener[] = []
+  private _listeners: NavigationCallback[] = []
+  private _teardowns: Array<() => void> = []
+
+  constructor() {
+    super()
+  }
+
+  // TODO: is this necessary
+  ensureLocation() {
+    const to = buildFullPath()
+    cs.log('ensureLocation', to)
+    this.history.replaceState(
+      {
+        _back: null,
+        _current: to,
+        _forward: null,
+      },
+      '',
+      to
+    )
+    this.location = to
+  }
+
+  replace(to: HistoryLocation) {
+    if (to === this.location) return
+    cs.info('replace', this.location, to)
+    this.history.replaceState(
+      {
+        // TODO: this should be user's responsibility
+        // _replacedState: this.history.state || null,
+        _back: this.location,
+        _current: to,
+        _forward: null,
+        _replaced: true,
+      },
+      '',
+      to
+    )
+    this.location = to
+  }
+
+  push(to: HistoryLocation, data?: HistoryState) {
+    // replace current entry state to add the forward value
+    this.history.replaceState(
+      {
+        ...this.history.state,
+        _forward: to,
+      },
+      ''
+    )
+    // TODO: compare current location to prevent navigation
+    // NEW NOTE: I think it shouldn't be history responsibility to check that
+    // if (to === this.location) return
+    const state = {
+      _back: this.location,
+      _current: to,
+      _forward: null,
+      ...data,
+    }
+    cs.info('push', this.location, '->', to, 'with state', state)
+    this.history.pushState(state, '', to)
+    this.location = to
+  }
+
+  listen(callback: NavigationCallback) {
+    // state is the same as history.state
+    const handler: PopStateListener = ({ state }) => {
+      cs.log(this)
+      cs.info('popstate fired', {
+        state,
+        location: this.location,
+      })
+      const from = this.location
+      // we have the state from the old entry, not the current one being removed
+      // TODO: correctly parse pathname
+      this.location = state ? state._current : buildFullPath
+      callback(this.location, from, {
+        type:
+          from === state._forward
+            ? NavigationType.back
+            : NavigationType.forward,
+      })
+    }
+
+    // settup the listener and prepare teardown callbacks
+    this._popStateListeners.push(handler)
+    this._listeners.push(callback)
+    window.addEventListener('popstate', handler)
+
+    const teardown = () => {
+      this._popStateListeners.splice(
+        this._popStateListeners.indexOf(handler),
+        1
+      )
+      this._listeners.splice(this._listeners.indexOf(callback), 1)
+      window.removeEventListener('popstate', handler)
+    }
+
+    this._teardowns.push(teardown)
+    return teardown
+  }
+
+  parseURL(location: string): HistoryURL {
+    let path = '',
+      search: HistoryURL['search'] = {},
+      searchString = '',
+      hash = ''
+
+    // Could use URL and URLSearchParams but IE 11 doesn't support it
+    const searchPos = location.indexOf('?')
+    const hashPos = location.indexOf(location, searchPos > -1 ? searchPos : 0)
+    if (searchPos > -1) {
+      path = location.slice(0, searchPos)
+      searchString = location.slice(
+        searchPos + 1,
+        hashPos > -1 ? hashPos : location.length - 1
+      )
+
+      // TODO: properly do this in a util function
+      search = searchString.split('&').reduce((search, entry) => {
+        const [key, value] = entry.split('=')
+        search[key] = value
+        return search
+      }, search)
+    }
+
+    if (hashPos > -1) {
+      path = path || location.slice(0, hashPos)
+      hash = location.slice(hashPos, location.length - 1)
+    }
+
+    path = path || location
+
+    return {
+      path,
+      // TODO: transform searchString
+      search,
+      hash,
+    }
+  }
+
+  destroy() {
+    for (const teardown of this._teardowns) teardown()
+    this._teardowns = []
+  }
+}
+
+const buildFullPath = () =>
+  window.location.pathname + window.location.search + window.location.hash
index 007749e31b500e06f077b3eac01d109c577b2ef8..8c39633645004ce971ef340832dee37ebb06ecd3 100644 (file)
@@ -37,7 +37,7 @@ export interface NavigationCallback {
 
 export type RemoveListener = () => void
 
-export default abstract class BaseHistory {
+export abstract class BaseHistory {
   // previousState: object
   location: HistoryLocation = START
   base: string = ''
index 8214e01ae1142ec0be5bbffd86ceb264f97f31c8..90d69a921c0d2e1b04781a2ba21c88a2dad2668c 100644 (file)
@@ -1,5 +1,5 @@
 import consola from 'consola'
-import BaseHistory from './base'
+import { BaseHistory } from './base'
 import {
   HistoryLocation,
   NavigationCallback,
@@ -12,7 +12,7 @@ const cs = consola.withTag('html5')
 
 type PopStateListener = (this: Window, ev: PopStateEvent) => any
 
-export default class HTML5History extends BaseHistory {
+export class HTML5History extends BaseHistory {
   private history = window.history
   private _popStateListeners: PopStateListener[] = []
   private _listeners: NavigationCallback[] = []
index 61adf9065dd4a3feb23d5af3072e5314c3e91a24..61e7511ba6e90f557a6a1072410789783b292e57 100644 (file)
@@ -1,40 +1,4 @@
-import BaseHistory from './history/base'
-import { RouterMatcher } from './matcher'
-import {
-  RouterLocation,
-  RouteRecord,
-  START_LOCATION_NORMALIZED,
-  RouterLocationNormalized,
-} from './types/index'
+import { Router } from './router'
+import { HTML5History } from './history/html5'
 
-interface RouterOptions {
-  history: BaseHistory
-  routes: RouteRecord[]
-}
-
-export class Router {
-  protected history: BaseHistory
-  private matcher: RouterMatcher
-  currentRoute: RouterLocationNormalized = START_LOCATION_NORMALIZED
-
-  constructor(options: RouterOptions) {
-    this.history = options.history
-    this.history.ensureLocation()
-
-    this.matcher = new RouterMatcher(options.routes)
-  }
-
-  /**
-   * Trigger a navigation, should resolve all guards first
-   * @param to Where to go
-   */
-  push(to: RouterLocation) {
-    // TODO: resolve URL
-    const location = this.matcher.resolve(to, this.currentRoute)
-    // TODO: call hooks, guards
-    this.history.push(location.fullPath)
-    this.currentRoute = location
-  }
-
-  getRouteRecord(location: RouterLocation) {}
-}
+export { Router, HTML5History }
diff --git a/src/router.ts b/src/router.ts
new file mode 100644 (file)
index 0000000..52d985f
--- /dev/null
@@ -0,0 +1,40 @@
+import { BaseHistory } from './history/base'
+import { RouterMatcher } from './matcher'
+import {
+  RouterLocation,
+  RouteRecord,
+  START_LOCATION_NORMALIZED,
+  RouterLocationNormalized,
+} from './types/index'
+
+interface RouterOptions {
+  history: BaseHistory
+  routes: RouteRecord[]
+}
+
+export class Router {
+  protected history: BaseHistory
+  private matcher: RouterMatcher
+  currentRoute: RouterLocationNormalized = START_LOCATION_NORMALIZED
+
+  constructor(options: RouterOptions) {
+    this.history = options.history
+    this.history.ensureLocation()
+
+    this.matcher = new RouterMatcher(options.routes)
+  }
+
+  /**
+   * Trigger a navigation, should resolve all guards first
+   * @param to Where to go
+   */
+  push(to: RouterLocation) {
+    // TODO: resolve URL
+    const location = this.matcher.resolve(to, this.currentRoute)
+    // TODO: call hooks, guards
+    this.history.push(location.fullPath)
+    this.currentRoute = location
+  }
+
+  getRouteRecord(location: RouterLocation) {}
+}