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

Smarter stripPrefix #459

Merged
merged 6 commits into from
Apr 20, 2017
Merged

Smarter stripPrefix #459

merged 6 commits into from
Apr 20, 2017

Conversation

pshrmn
Copy link
Contributor

@pshrmn pshrmn commented Apr 5, 2017

Per remix-run/react-router#4866

Does a case-insensitive match and will not strip the prefix if the prefix is a partial match (e.g., given the pathname /prefix/pathname, the basename /pre will not be stripped). Being more selective about not stripping partial basename matches is potentially breaking, but I would consider it more a bug that you could do that before.

@mjackson I believe that you have stated before that you don't like directly testing non-external APIs, but it felt like overkill to test these by triggering getDOMLocation calls when creating a browser/hash history.

@mjackson
Copy link
Member

Thanks for the PR, @pshrmn. This looks great. Thanks to you I think I'm finally getting some words to use to describe to people what a basename is. "The slash-delimited portion of the pathname that is not used for routing".

As for the tests, I think they'd be better if they used public API, yes. It does feel a little heavy, but tests that cover public API are the only ones that matter.

@mjackson
Copy link
Member

We should also probably give the internal function a more descriptive name like stripBasename now that we're getting some better idea of what a basename is for.

@mjackson
Copy link
Member

Idea: we can have 2 internal functions: hasBasename(path, basename) and stripBasename(path, basename). Everywhere we're using path.indexOf(basename) we can use use hasBasename instead. And stripBasename can just use hasBasename internally to do a path.substr if needed.

Does a case-insensitive match and will not strip the prefix if the prefix is a partial match (e.g., given the pathname "/prefix/pathname", the basename "/pre" will not be stripped.
@pshrmn
Copy link
Contributor Author

pshrmn commented Apr 20, 2017

Alright, so there is a potential bug here that needs to be addressed before this is merged:

in parsePath, if the path is empty, then pathname is set to /.

let pathname = path || '/'

However, if the path has no pathname, but it includes a search or hash string, then pathname will end up being an empty string after those have been removed.

path = '?test=ing'
pathname = '?test=ing'
// remove the search string
pathname = ''

When that happens, should pathname be set to /?

@mjackson
Copy link
Member

Yes, we should never have an empty pathname. The "empty" pathname is /.

(new RegExp('^' + prefix + '(\\/|\\?|#|$)', 'i')).test(path)

export const stripBasename = (path, prefix) =>
hasBasename(path, prefix) ? path.substr(prefix.length) : path
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@@ -168,4 +169,42 @@ describeHistory('a browser history', () => {
TestSequences.HashChangeTransitionHook(history, done)
})
})

describe('basename', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️ these tests. Very easy to read.

it('strips with no pathname, but with a hash string', () => {
window.history.replaceState(null, null, '/prefix#rest')
const history = createHistory({ basename: '/prefix' })
expect(history.location.pathname).toEqual('')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

history.location.pathname should be / here.

state,
key
}
return createLocation(path, state, key)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't just default pathname to / in parsePath when it was an empty string because that would break the resolving that is done using history.location I'm not altogether satisfied with this approach because now the hash history location objects will have a key property. Maybe this isn't a big deal, but it is a side effect of the change.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a huge deal but I'd prefer it if hash history objects didn't have a key property. I think it would mislead people into thinking that we support key with hash history when we don't really.

It feels like this might be one of those changes that requires a more thorough refactoring. If that's the case, I'm happy to take it on. I've set aside some time today and tomorrow for OSS work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really hadn't thought about this before, but I just updated createLocation to only set the key property when the key value is truthy.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 That works!

export const stripPrefix = (path, prefix) =>
path.indexOf(prefix) === 0 ? path.substr(prefix.length) : path
export const hasBasename = (path, prefix) =>
(new RegExp('^' + prefix + '(\\/|\\?|#|$)', 'i')).test(path)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't work if prefix has regex special symbols in them. Like $. It should be

export const hasBasename = (path, prefix) => path.startsWith(prefix)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A basename is meant to represent full segments, which startsWith cannot enforce on its own (the basename /test should not match the pathname /testing).

The prefix probably should be passed to a function that escapes any special regexp characters. I'm sure that a PR with tests would be appreciated for this.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick reply. I realised my mistake while makinging the fix. I created this PR for escaping special regex symbols: #544

@lock lock bot locked as resolved and limited conversation to collaborators Jun 4, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants