Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Made old StateContext values Idempotent #257

Merged
merged 25 commits into from
Jul 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e8a2c63
Passed the current context to navigateLink
grahammendick Jun 29, 2019
43ca0f0
Renamed for clarity and consistency
grahammendick Jun 29, 2019
d10651c
Added typings and matched signature
grahammendick Jun 29, 2019
e5eeccd
Added void return type
grahammendick Jun 29, 2019
98e6b22
Copied over latest navigation typings
grahammendick Jun 29, 2019
c142aee
Tested state context override
grahammendick Jun 29, 2019
02b23f4
Tested suspend with overridden context
grahammendick Jun 29, 2019
43a9b5c
Tested on before with overridden context
grahammendick Jun 29, 2019
ccc1300
Tested unload called on overridden state
grahammendick Jun 29, 2019
77c039c
Tested navigate overrides context
grahammendick Jun 30, 2019
2885bb9
Corrected type
grahammendick Jun 30, 2019
344970e
Added more override context asserts
grahammendick Jun 30, 2019
a91599a
Renamed test for consistency
grahammendick Jun 30, 2019
b244c0f
Added more state context asserts
grahammendick Jun 30, 2019
c644417
Added more state context asserts
grahammendick Jun 30, 2019
583dd5f
Added more state context asserts
grahammendick Jun 30, 2019
d907b8c
Made test scenario more realistic
grahammendick Jun 30, 2019
454ed8b
Made test scenario more realistic
grahammendick Jun 30, 2019
4d852d3
Made test scenario more realistic
grahammendick Jun 30, 2019
c085a5b
Made test scenario more realistic
grahammendick Jun 30, 2019
fbae147
Fixed data key name
grahammendick Jun 30, 2019
69b8178
Tested refresh link overrides context
grahammendick Jun 30, 2019
1b72737
Tested navigation back link overrides context
grahammendick Jun 30, 2019
44cfd91
Changed optimistic lock to context
grahammendick Jun 30, 2019
de3affd
Prefixed unused var to avoid warning
grahammendick Jun 30, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 17 additions & 16 deletions Navigation/src/StateNavigator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ class StateNavigator {
return !(<StateNavigator> stateInfos).stateHandler;
};

private createStateContext(state: State, data: any, crumbs: Crumb[], url: string, asyncData: any, history: boolean): StateContext {
private createStateContext(state: State, data: any, crumbs: Crumb[], url: string, asyncData: any, history: boolean, currentContext: StateContext): StateContext {
var stateContext = new StateContext();
stateContext.oldState = this.stateContext.state;
stateContext.oldData = this.stateContext.data;
stateContext.oldUrl = this.stateContext.url;
stateContext.oldState = currentContext.state;
stateContext.oldData = currentContext.data;
stateContext.oldUrl = currentContext.url;
stateContext.state = state;
stateContext.url = url;
stateContext.asyncData = asyncData;
Expand Down Expand Up @@ -116,31 +116,32 @@ class StateNavigator {
}

navigateLink(url: string, historyAction: 'add' | 'replace' | 'none' = 'add', history = false,
suspendNavigation: (stateContext: StateContext, resumeNavigation: () => void) => void = (_, resumeNavigation) => resumeNavigation()) {
suspendNavigation: (stateContext: StateContext, resumeNavigation: () => void) => void = (_, resumeNavigation) => resumeNavigation(),
currentContext = this.stateContext) {
if (history && this.stateContext.url === url)
return;
var oldUrl = this.stateContext.url;
var context = this.stateContext;
var { state, data, crumbs } = this.parseLink(url);
for (var id in this.onBeforeNavigateCache.handlers) {
var handler = this.onBeforeNavigateCache.handlers[id];
if (oldUrl !== this.stateContext.url || !handler(state, data, url, history, this.stateContext))
if (context !== this.stateContext || !handler(state, data, url, history, currentContext))
return;
}
var navigateContinuation = (asyncData?: any) => {
var stateContext = this.createStateContext(state, data, crumbs, url, asyncData, history);
if (oldUrl === this.stateContext.url) {
suspendNavigation(stateContext, () => {
if (oldUrl === this.stateContext.url)
this.resumeNavigation(stateContext, historyAction);
var nextContext = this.createStateContext(state, data, crumbs, url, asyncData, history, currentContext);
if (context === this.stateContext) {
suspendNavigation(nextContext, () => {
if (context === this.stateContext)
this.resumeNavigation(nextContext, historyAction);
});
}
};
var unloadContinuation = () => {
if (oldUrl === this.stateContext.url)
if (context === this.stateContext)
state.navigating(data, url, navigateContinuation, history);
};
if (this.stateContext.state)
this.stateContext.state.unloading(state, data, url, unloadContinuation, history);
if (currentContext.state)
currentContext.state.unloading(state, data, url, unloadContinuation, history);
else
state.navigating(data, url, navigateContinuation, history);
}
Expand All @@ -153,7 +154,7 @@ class StateNavigator {
state.navigated(this.stateContext.data, asyncData);
for (var id in this.onNavigateCache.handlers) {
if (url === this.stateContext.url)
this.onNavigateCache.handlers[id](oldState, state, data, asyncData, stateContext);
this.onNavigateCache.handlers[id](oldState, state, data, asyncData, stateContext);
}
if (url === this.stateContext.url) {
if (historyAction !== 'none')
Expand Down
118 changes: 118 additions & 0 deletions Navigation/test/NavigationTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5422,4 +5422,122 @@ describe('Navigation', function () {
assert.equal(stateNavigator.stateContext.data.z, 'c');
});
});

describe('Context Override', function() {
it('should navigate', function() {
var stateNavigator = new StateNavigator([
{ key: 's0', route: 'r0' },
{ key: 's1', route: 'r1' },
{ key: 's2', route: 'r2', trackCrumbTrail: true }
]);
stateNavigator.navigate('s0', {x: 'a'});
var stateContext = stateNavigator.stateContext;
stateNavigator.navigate('s1', {y: 'b'});
var oldStateNavigator = new StateNavigator(stateNavigator);
oldStateNavigator.stateContext = stateContext;
var link = oldStateNavigator.getNavigationLink('s2', {z: 'c'});
stateNavigator.navigateLink(link, undefined, undefined, undefined, stateContext);
assert.equal(stateNavigator.stateContext.url, '/r2?z=c&crumb=%2Fr0%3Fx%3Da');
assert.equal(stateNavigator.stateContext.state, stateNavigator.states['s2']);
assert.equal(stateNavigator.stateContext.data.z, 'c');
assert.equal(stateNavigator.stateContext.oldUrl, '/r0?x=a');
assert.equal(stateNavigator.stateContext.oldState, stateNavigator.states['s0']);
assert.equal(stateNavigator.stateContext.oldData.x, 'a');
assert.equal(stateNavigator.stateContext.previousUrl, '/r0?x=a');
assert.equal(stateNavigator.stateContext.previousState, stateNavigator.states['s0']);
assert.equal(stateNavigator.stateContext.previousData.x, 'a');
assert.equal(stateNavigator.stateContext.crumbs.length, 1);
assert.equal(stateNavigator.stateContext.crumbs[0].url, '/r0?x=a');
assert.equal(stateNavigator.stateContext.crumbs[0].state, stateNavigator.states['s0']);
assert.equal(stateNavigator.stateContext.crumbs[0].data.x, 'a');
});
});

describe('Suspend Context Override', function() {
it('should navigate', function() {
var stateNavigator = new StateNavigator([
{ key: 's0', route: 'r0' },
{ key: 's1', route: 'r1' },
{ key: 's2', route: 'r2', trackCrumbTrail: true }
]);
stateNavigator.navigate('s0', {x: 'a'});
var stateContext = stateNavigator.stateContext;
stateNavigator.navigate('s1', {y: 'b'});
var oldStateNavigator = new StateNavigator(stateNavigator);
oldStateNavigator.stateContext = stateContext;
var link = oldStateNavigator.getNavigationLink('s2', {z: 'c'});
stateNavigator.navigateLink(link, undefined, undefined, (nextContext, resume) => {
assert.equal(nextContext.url, '/r2?z=c&crumb=%2Fr0%3Fx%3Da');
assert.equal(nextContext.state, stateNavigator.states['s2']);
assert.equal(nextContext.data.z, 'c');
assert.equal(nextContext.oldUrl, '/r0?x=a');
assert.equal(nextContext.oldState, stateNavigator.states['s0']);
assert.equal(nextContext.oldData.x, 'a');
assert.equal(nextContext.previousUrl, '/r0?x=a');
assert.equal(nextContext.previousState, stateNavigator.states['s0']);
assert.equal(nextContext.previousData.x, 'a');
assert.equal(nextContext.crumbs.length, 1);
assert.equal(nextContext.crumbs[0].url, '/r0?x=a');
assert.equal(nextContext.crumbs[0].state, stateNavigator.states['s0']);
assert.equal(nextContext.crumbs[0].data.x, 'a');
resume();
}, stateContext);
});
});

describe('On Before Navigate Context Override', function() {
it('should call onBeforeNavigate listener', function() {
var stateNavigator = new StateNavigator([
{ key: 's0', route: 'r0' },
{ key: 's1', route: 'r1' },
{ key: 's2', route: 'r2', trackCrumbTrail: true }
]);
stateNavigator.navigate('s0', {x: 'a'});
var stateContext = stateNavigator.stateContext;
stateNavigator.navigate('s1', {y: 'b'});
stateNavigator.onBeforeNavigate((state, data, url, _history, currentContext) => {
assert.equal(currentContext.url, '/r0?x=a');
assert.equal(currentContext.state, stateNavigator.states['s0']);
assert.equal(currentContext.data.x, 'a');
var {state: nextState, data: nextData, crumbs} = stateNavigator.parseLink(link);
assert.equal(url, '/r2?z=c&crumb=%2Fr0%3Fx%3Da');
assert.equal(state, stateNavigator.states['s2']);
assert.equal(data.z, 'c');
assert.equal(nextState, stateNavigator.states['s2']);
assert.equal(nextData.z, 'c');
assert.equal(crumbs.length, 1);
assert.equal(crumbs[0].url, '/r0?x=a');
assert.equal(crumbs[0].state, stateNavigator.states['s0']);
assert.equal(crumbs[0].data.x, 'a');
return true;
});
var oldStateNavigator = new StateNavigator(stateNavigator);
oldStateNavigator.stateContext = stateContext;
var link = oldStateNavigator.getNavigationLink('s2', {z: 'c'});
stateNavigator.navigateLink(link, undefined, undefined, undefined, stateContext);
});
});

describe('Unloading Context Override', function() {
it('should unload overridden State', function() {
var stateNavigator = new StateNavigator([
{ key: 's0', route: 'r0' },
{ key: 's1', route: 'r1' },
{ key: 's2', route: 'r2' }
]);
stateNavigator.navigate('s0', {x: 'a'});
var stateContext = stateNavigator.stateContext;
stateNavigator.navigate('s1', {y: 'b'});
var unloaded = false;
stateNavigator.states['s0'].unloading = (_state, _data, _url, unload) => {
unloaded = true;
unload();
}
var oldStateNavigator = new StateNavigator(stateNavigator);
oldStateNavigator.stateContext = stateContext;
var link = oldStateNavigator.getNavigationLink('s2', {z: 'c'});
stateNavigator.navigateLink(link, undefined, undefined, undefined, stateContext);
assert.equal(unloaded, true);
});
});
});
4 changes: 3 additions & 1 deletion Navigation/test/node_modules/@types/navigation.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 7 additions & 6 deletions NavigationReact/src/AsyncStateNavigator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,31 +21,32 @@ class AsyncStateNavigator extends StateNavigator {
var url = this.getNavigationLink(stateKey, navigationData);
if (url == null)
throw new Error('Invalid route data, a mandatory route parameter has not been supplied a value');
this.navigateLink(url, historyAction, false, undefined, defer);
this.navigateLink(url, historyAction, false, undefined, undefined, defer);
}

navigateBack(distance: number, historyAction?: 'add' | 'replace' | 'none', defer?: boolean) {
var url = this.getNavigationBackLink(distance);
this.navigateLink(url, historyAction, false, undefined, defer);
this.navigateLink(url, historyAction, false, undefined, undefined, defer);
}

refresh(navigationData?: any, historyAction?: 'add' | 'replace' | 'none', defer?: boolean) {
var url = this.getRefreshLink(navigationData);
if (url == null)
throw new Error('Invalid route data, a mandatory route parameter has not been supplied a value');
this.navigateLink(url, historyAction, false, undefined, defer);
this.navigateLink(url, historyAction, false, undefined, undefined, defer);
}

navigateLink(url: string, historyAction: 'add' | 'replace' | 'none' = 'add', history = false,
suspendNavigation?: (stateContext: StateContext, resumeNavigation: () => void) => void, defer = false) {
suspendNavigation?: (stateContext: StateContext, resumeNavigation: () => void) => void,
currentContext = this.stateContext, defer = false) {
if (!suspendNavigation)
suspendNavigation = (stateContext, resumeNavigation) => resumeNavigation();
suspendNavigation = (_stateContext, resumeNavigation) => resumeNavigation();
this.stateNavigator.navigateLink(url, historyAction, history, (stateContext, resumeNavigation) => {
suspendNavigation(stateContext, () => {
var asyncNavigator = new AsyncStateNavigator(this.navigationHandler, this.stateNavigator, stateContext);
this.suspendNavigation(asyncNavigator, resumeNavigation, defer);
})
});
}, currentContext);
}

private suspendNavigation(asyncNavigator: AsyncStateNavigator, resumeNavigation: () => void, defer: boolean) {
Expand Down
4 changes: 3 additions & 1 deletion NavigationReact/src/node_modules/@types/navigation.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 53 additions & 1 deletion NavigationReact/test/NavigationBackLinkTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -562,12 +562,64 @@ describe('NavigationBackLinkTest', function () {
</NavigationHandler>,
container
);
var div = container.querySelector<HTMLAnchorElement>('div');
var div = container.querySelector<HTMLDivElement>('div');
Simulate.click(div);
assert.equal(stateNavigator.stateContext.state, stateNavigator.states['s0']);
})
});

describe('Old Context Navigation Back Link', function () {
it('should update', function(){
var stateNavigator = new StateNavigator([
{ key: 's0', route: 'r0' },
{ key: 's1', route: 'r1', trackCrumbTrail: true },
{ key: 's2', route: 'r2', trackCrumbTrail: true },
]);
class FirstContext extends React.Component {
static contextType = NavigationContext;
private mountContext: any;
constructor(props, context) {
super(props, context);
this.mountContext = this.context;
}
render() {
return (
<NavigationContext.Provider value={this.mountContext}>
{this.props.children}
</NavigationContext.Provider>
);
}
}
stateNavigator.navigate('s0', {x: 'a'});
stateNavigator.navigate('s1', {y: 'b'});
var container = document.createElement('div');
ReactDOM.render(
<NavigationHandler stateNavigator={stateNavigator}>
<FirstContext>
<NavigationBackLink
distance={1}>
link text
</NavigationBackLink>
</FirstContext>
</NavigationHandler>,
container
);
stateNavigator.navigate('s2', {z: 'c'});
var link = container.querySelector<HTMLAnchorElement>('a');
Simulate.click(link);
assert.equal(stateNavigator.stateContext.url, '/r0?x=a');
assert.equal(stateNavigator.stateContext.state, stateNavigator.states['s0']);
assert.equal(stateNavigator.stateContext.data.x, 'a');
assert.equal(stateNavigator.stateContext.oldUrl, '/r1?y=b&crumb=%2Fr0%3Fx%3Da');
assert.equal(stateNavigator.stateContext.oldState, stateNavigator.states['s1']);
assert.equal(stateNavigator.stateContext.oldData.y, 'b');
assert.equal(stateNavigator.stateContext.previousUrl, undefined);
assert.equal(stateNavigator.stateContext.previousState, undefined);
assert.equal(Object.keys(stateNavigator.stateContext.previousData).length, 0);
assert.equal(stateNavigator.stateContext.crumbs.length, 0);
})
});

/*describe('Click Deferred Navigation Back Link', function () {
it('should navigate async', function(done){
var stateNavigator = new StateNavigator([
Expand Down
Loading