-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(history): impl history linked list
- Loading branch information
Showing
8 changed files
with
242 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,12 @@ | ||
/** | ||
* This file is used to record all **symbolic constants**. | ||
*/ | ||
|
||
/** | ||
* the root router-view layout | ||
*/ | ||
export const DEFAULT_LAYOUT = 'LMainWithFooter' | ||
/** | ||
* the max value of history records and should be greater than 1 | ||
*/ | ||
export const RECORD_MAX_VAL = 20 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,14 @@ | ||
import Vue from 'vue' | ||
import Vuex from 'vuex' | ||
import modules from './modules' | ||
|
||
Vue.use(Vuex) | ||
|
||
export default new Vuex.Store({ | ||
export type RootState = {} | ||
|
||
export default new Vuex.Store<RootState>({ | ||
state: {}, | ||
mutations: {}, | ||
actions: {}, | ||
modules: {} | ||
modules | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import { Module } from 'vuex' | ||
import { Route } from 'vue-router' | ||
import invariant from 'tiny-invariant' | ||
import { RootState } from '../index' | ||
import { RECORD_MAX_VAL } from '../../constants' | ||
|
||
export type RecordItem = { | ||
name?: string | ||
fullPath: string | ||
next: RecordItem | null | ||
} | ||
|
||
interface RecordItemConstructor { | ||
(this: RecordItem, route: Route, next?: RecordItem): void | ||
new (route: Route, next?: RecordItem): RecordItem | ||
} | ||
|
||
const RecordItem = function(this: RecordItem, route: Route, next?: RecordItem) { | ||
if (route.name) this.name = route.name | ||
this.fullPath = route.fullPath | ||
this.next = next || null | ||
} as RecordItemConstructor | ||
|
||
const history: Module<Record<'recordHead', RecordItem | null>, RootState> = { | ||
namespaced: true, | ||
|
||
state: { | ||
recordHead: null | ||
}, | ||
|
||
mutations: { | ||
append(state, route: Route) { | ||
if (!state.recordHead) { | ||
state.recordHead = new RecordItem(route) | ||
return | ||
} | ||
|
||
let current: RecordItem | null = state.recordHead | ||
let size = 1 | ||
invariant( | ||
RECORD_MAX_VAL > 1, | ||
'[module/history]: RECORD_MAX_VAL should be greater then 1' | ||
) | ||
while (current.next) { | ||
++size | ||
current = current.next | ||
} | ||
// drop the head when exceed | ||
if (size === RECORD_MAX_VAL) { | ||
state.recordHead = state.recordHead.next | ||
} | ||
// a new tail element of linked list | ||
current.next = new RecordItem(route) | ||
}, | ||
|
||
prepend(state, route: Route) { | ||
if (!state.recordHead) { | ||
state.recordHead = new RecordItem(route) | ||
return | ||
} | ||
|
||
let current: RecordItem | null = state.recordHead | ||
let size = 1 | ||
invariant( | ||
RECORD_MAX_VAL > 1, | ||
'[module/history]: RECORD_MAX_VAL should be greater then 1' | ||
) | ||
|
||
state.recordHead = new RecordItem(route, state.recordHead) | ||
|
||
while (current.next) { | ||
++size | ||
if (size === RECORD_MAX_VAL) { | ||
// drop the tail element | ||
current.next = null | ||
break | ||
} | ||
current = current.next | ||
} | ||
} | ||
} | ||
} | ||
|
||
export default history |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import history from './history' | ||
|
||
export default { | ||
history | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
import Vue from 'vue' | ||
import Vuex, { Store } from 'vuex' | ||
import moduleHistory, { RecordItem } from '../../../src/store/modules/history' | ||
import { RECORD_MAX_VAL } from '../../../src/constants' | ||
|
||
Vue.use(Vuex) | ||
|
||
describe('Vuex history module', () => { | ||
let store: Store<Record<string, any>> | ||
|
||
beforeEach(() => { | ||
store = new Vuex.Store<Record<string, any>>({ | ||
modules: { | ||
history: Object.assign(moduleHistory, { | ||
// make state clear | ||
state: { | ||
recordHead: null | ||
} | ||
}) | ||
} | ||
}) | ||
}) | ||
|
||
it('Should append a history record', () => { | ||
const mockRoute = '/mock?query=append' | ||
store.commit('history/append', { | ||
fullPath: mockRoute | ||
}) | ||
const { recordHead } = store.state.history | ||
expect(recordHead.fullPath).toEqual(mockRoute) | ||
expect(recordHead.next).toBeNull() | ||
}) | ||
|
||
it('Should append multiple history records', () => { | ||
const mockFirstRoute = '/mock?query=prepend-one' | ||
const mockSecondRoute = '/mock?query=prepend-two' | ||
|
||
store.commit('history/append', { | ||
fullPath: mockFirstRoute | ||
}) | ||
store.commit('history/append', { | ||
fullPath: mockSecondRoute | ||
}) | ||
const { recordHead } = store.state.history | ||
expect(recordHead.fullPath).toEqual(mockFirstRoute) | ||
expect(recordHead.next).toBeDefined() | ||
expect(recordHead.next.fullPath).toEqual(mockSecondRoute) | ||
expect(recordHead.next.next).toBeNull() | ||
}) | ||
|
||
it('Should append a history record until exceed', () => { | ||
const actualOffset = 5 | ||
new Array(RECORD_MAX_VAL + actualOffset) | ||
.fill(null) | ||
.forEach((item, index) => { | ||
store.commit('history/append', { | ||
fullPath: `/mock?query=${index}` | ||
}) | ||
}) | ||
|
||
const { recordHead } = store.state.history | ||
expect(recordHead.fullPath).toEqual(`/mock?query=${actualOffset}`) | ||
expect(recordHead.next).toBeDefined() | ||
|
||
let current: RecordItem = recordHead | ||
let size = 1 | ||
while (current.next) { | ||
++size | ||
current = current.next | ||
} | ||
expect(size).toEqual(RECORD_MAX_VAL) | ||
expect(current.fullPath).toEqual( | ||
`/mock?query=${RECORD_MAX_VAL + actualOffset - 1}` | ||
) | ||
expect(current.next).toBeNull() | ||
}) | ||
|
||
it('Should prepend a history record', () => { | ||
const mockRoute = '/mock?query=prepend' | ||
store.commit('history/prepend', { | ||
fullPath: mockRoute | ||
}) | ||
const { recordHead } = store.state.history | ||
expect(recordHead.fullPath).toEqual(mockRoute) | ||
expect(recordHead.next).toBeNull() | ||
}) | ||
|
||
it('Should prepend multiple history records', () => { | ||
const mockFirstRoute = '/mock?query=prepend-one' | ||
const mockSecondRoute = '/mock?query=prepend-two' | ||
|
||
store.commit('history/prepend', { | ||
fullPath: mockFirstRoute | ||
}) | ||
store.commit('history/prepend', { | ||
fullPath: mockSecondRoute | ||
}) | ||
const { recordHead } = store.state.history | ||
expect(recordHead.fullPath).toEqual(mockSecondRoute) | ||
expect(recordHead.next).toBeDefined() | ||
expect(recordHead.next.fullPath).toEqual(mockFirstRoute) | ||
expect(recordHead.next.next).toBeNull() | ||
}) | ||
|
||
it('Should prepend a history record until exceed', () => { | ||
const actualOffset = 5 | ||
new Array(RECORD_MAX_VAL + actualOffset) | ||
.fill(null) | ||
.forEach((item, index) => { | ||
store.commit('history/prepend', { | ||
fullPath: `/mock?query=${index}` | ||
}) | ||
}) | ||
|
||
const { recordHead } = store.state.history | ||
expect(recordHead.fullPath).toEqual( | ||
`/mock?query=${RECORD_MAX_VAL + actualOffset - 1}` | ||
) | ||
expect(recordHead.next).toBeDefined() | ||
|
||
let current: RecordItem = recordHead | ||
let size = 1 | ||
while (current.next) { | ||
++size | ||
current = current.next | ||
} | ||
expect(size).toEqual(RECORD_MAX_VAL) | ||
expect(current.fullPath).toEqual(`/mock?query=${actualOffset}`) | ||
expect(current.next).toBeNull() | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters