feat (Multiple Route Matches): Allow multiple route matches via a shouldHandle property#12
Conversation
bcd1674 to
c158f42
Compare
c158f42 to
f97b438
Compare
quintondang
left a comment
There was a problem hiding this comment.
thank you so much for doing this @jamesopti! Mostly super minor questions. overall really good
| return new Promise((res, rej) => { | ||
| if (routes.length === 0) { | ||
| rej('No routes match'); | ||
| } else if (routes.length === 1) { |
There was a problem hiding this comment.
do we care about the case if someone accidentally adds a singular route with a shouldHandle fn? i.e. if they thought their route was a match but wasn't, or if they wanted to gate it anyways even if its a singular route. Otherwise this would fail silently and take all users to the route
There was a problem hiding this comment.
Hmmmmm, thats an interesting catch!
I added this as a shortcut, but I think you're probably right.
shouldHandle should be respected as a secondary matching criteria if specified, even if there is only one match.
Will update.
| .catch(res => { | ||
| promiseToStateMap[index] = { state: "rejected", res, index }; | ||
| }) | ||
| .then(compute, compute); |
There was a problem hiding this comment.
we want to compute twice? or am I misunderstanding this
There was a problem hiding this comment.
No, this just means we invoke compute on resolve or on reject of the promise.
Signature: .then(<onFulfilled>, <onRejected>)
This is a safer alternative/fallback to .finally(<onFulfilled or rejected>) because .finally is only supported starting in Chrome 63.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally
There was a problem hiding this comment.
ohhh right thanks for the explanation! Did not realize that about finally.
| * This is useful to control test timing where our | ||
| * router resolves routes using Promises (micro-task timing) | ||
| * More on micro-tasks vs macro-tasks: | ||
| * https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/ |
There was a problem hiding this comment.
NP! See here for where Nikhil and I learned about this timing behavior.
https://github.com/optimizely/client-js/blob/devel/src/plugins/change_attribute/index.js#L175-L196
| ]); | ||
| }); | ||
|
|
||
| it('should execute the first route in the list', () => { |
There was a problem hiding this comment.
update this to second route in the list
| .then(() => { | ||
| sinon.assert.calledOnce(handlerSpy2) | ||
| sinon.assert.notCalled(handlerSpy1) | ||
| sinon.assert.callOrder(shouldHandleSpy2, shouldHandleSpy1, handlerSpy2) |
| .then(() => { | ||
| sinon.assert.calledOnce(onRouteCompleteSpy) | ||
| sinon.assert.callOrder(spy1, spy2, onRouteCompleteSpy) | ||
| var args = onRouteCompleteSpy.firstCall.args[0] |
There was a problem hiding this comment.
super nit: update these vars to consts but wont block on this
jamesopti
left a comment
There was a problem hiding this comment.
Thanks for the quick review @quintondang !
Updated for the single route case and added some more tests.
| return new Promise((res, rej) => { | ||
| if (routes.length === 0) { | ||
| rej('No routes match'); | ||
| } else if (routes.length === 1) { |
There was a problem hiding this comment.
Hmmmmm, thats an interesting catch!
I added this as a shortcut, but I think you're probably right.
shouldHandle should be respected as a secondary matching criteria if specified, even if there is only one match.
Will update.
| .catch(res => { | ||
| promiseToStateMap[index] = { state: "rejected", res, index }; | ||
| }) | ||
| .then(compute, compute); |
There was a problem hiding this comment.
No, this just means we invoke compute on resolve or on reject of the promise.
Signature: .then(<onFulfilled>, <onRejected>)
This is a safer alternative/fallback to .finally(<onFulfilled or rejected>) because .finally is only supported starting in Chrome 63.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally
| * This is useful to control test timing where our | ||
| * router resolves routes using Promises (micro-task timing) | ||
| * More on micro-tasks vs macro-tasks: | ||
| * https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/ |
There was a problem hiding this comment.
NP! See here for where Nikhil and I learned about this timing behavior.
https://github.com/optimizely/client-js/blob/devel/src/plugins/change_attribute/index.js#L175-L196
0c65ead to
443d5a5
Compare
443d5a5 to
d40ae0d
Compare
| ).then(({index}) => res(routes[index])); | ||
| } | ||
| }); | ||
| return utils |
There was a problem hiding this comment.
Yup. Theres a test for that too.
PromiseOrderedFirst will iterate through the (empty) list of promises and since it wont find any pending or resolved ones, it will reject on first attempt.
Summary
Enable multiple routes to match via the
matchregex property.Use
shouldHandleto break the tie.shouldHandleis an optional property of a route which expects a function which returns aPromiseIf that Promise resolves without explicitly passing
false, the route will be considered a match.The
PromiseOrderedFirstutility is written to ensure deterministic behavior for a given set of matched routes regardless of how long any given match'sshouldHandlepromise takes to resolve.Multiple Match Example Use Cases
Case 1 - All matches use shouldHandle
If
data.typeis basketball, the first route will be usedIf
data.typeis football, the second route will be usedElse the catchall route will be used
Case 2 - Only some matches use shouldHandle
If
data.typeis basketball, the first route will be usedElse the second route will be used
Case 3 - More than one shouldHandle matches
The first route will always be used.
Test Plan
Unit tests added to cover the following scenarios:
List of route matches that look like:
<no shouldHandle>,<no shouldHandle>, ]<shouldHandle rejected immediately>,<shouldHandle resolved immediately>]<shouldHandle rejected after delay>,<shouldHandle resolved immediately>]<shouldHandle resolved after delay>,<shouldHandle resolved immediately>]