Skip to content

Support providing a fake Browser.Navigation.Key in tests - #243

Open
jfmengels wants to merge 1 commit into
elm-explorations:masterfrom
jfmengels:navigation-key
Open

Support providing a fake Browser.Navigation.Key in tests#243
jfmengels wants to merge 1 commit into
elm-explorations:masterfrom
jfmengels:navigation-key

Conversation

@jfmengels

@jfmengels jfmengels commented Apr 29, 2025

Copy link
Copy Markdown
Contributor

Closes #24

Adds Test.withNavigationKey which provide a Browser.Navigation.Key to tests.

This functions is designed such that the key can't be used in production code in a way that will have a noticeable effect (one could generate a test in production code, but they can't really do anything with a test).

Without this, the workaround is to define a wrapper type

type Key
  = RealKey Browser.Navigation.Key
  | TestKey

as well as wrapper functions for Browser.Navigation functions (such as pushUrl) that accept this type, where they'll do nothing if the key is not a real key.


Implemention-wise, the key is a simple function with a side-effect and no input (source).
I've introduced a new test key which is a plain noop function, which should be equivalent in all "noticeable" ways (even through Debug.log).

Releasing this functionality will lock the implementation of elm/browser's key to such a function, at least until it releases a new major version.

@jfmengels
jfmengels marked this pull request as ready for review April 29, 2025 18:46
Comment thread elm.json Outdated
],
"elm-version": "0.19.0 <= v < 0.20.0",
"dependencies": {
"elm/browser": "1.0.2 <= v < 2.0.0",

@Janiczek Janiczek Apr 30, 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I wonder whether this is as low as we can go, or whether 1.0.1 or 1.0.2 have something important we want to encourage adoption of.

(We did that with bytes 1.0.8 I think, where there was something substantial... but I can't remember the details)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It looks like Browser.Navigation.Key used to be an object, not a function:

elm/browser@544138a

That commit is dated Jul 30, 2018 and 1.0.1 is the first tag after that: https://github.com/elm/browser/tags

So we could potentially lower this to 1.0.1 (after double-checking that stuff actually works), but I don't think there is any gain doing this in practice, since 1.0.2 was released in late 2019.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've lowered it to 1.0.1, that seems to work fine. In practice I believe we could lower it to 1.0.0 without any noticeable effect, except that Debug.log would show something slightly different just for 1.0.0. I think 1.0.1 is fine but happy to change it.

Comment thread src/Test.elm Outdated
Comment thread src/Elm/Kernel/Test.js
}
}

function _Test_navigationKey() {}

@lydell lydell Aug 17, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Here’s an idea I just got:

Suggested change
function _Test_navigationKey() {}
function _Test_navigationKey() {
throw new Error('This is a test Browser.Navigation.Key that must not be used for real!');
}

And we could expose it like so:

-- module Test

{-| Exposed to users of the package! -}
navigationKey : Browser.Navigation.Key
navigationKey =
		Elm.Kernel.Test.navigationKey

Then there is no need for new testWithKey and fuzzWithKey functions.

If someone were to try to use it for real in an app, it would compile. But at runtime, it would run a history method and then throw an error, breaking the app. (We should verify that it breaks the app enough, though.)

https://github.com/elm/browser/blob/1.0.2/src/Elm/Kernel/Browser.js#L190-L212

Alternatively, instead of throwing an error we could do something drastic like replacing the whole document.body with an error message.

Not sure if this is good or not, but it is tempting.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Interesting idea!

Maybe we could make it fail (throw) unless something was set by the test runner (ex: globalThis.ELM_TESTING === true), though all test runners would have to change something which is painful unless there's already something we can check for.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Oh, is it possible to execute the key function in tests? Or why would we need a guard?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I am not sure what you mean. The guard is meant is to prevent people using this value in application (non-test) code.

Alternatively, we could have a design like this, resembling the original testWithKey of this PR, but with a value that allows you to build any "dangerous" value.

-- Names to be bikeshedded
testWithData : String -> (MasterKey -> Expectation) -> Test

test "title" <|
  \masterKey ->
    let navigationKey = Test.navigationKey masterKey
    in
    -- ...

That way, if new values like Navigation.Key come up later in Elm's public API, we won't have to add new variants of test, we will only have to introduce a new function:

-- Test.elm
newResource : MasterKey -> Resource

But it does mean we will need to solve the problem of all the test/fuzz variants that blocked this original PR.


People can easily recreate testWithKey with this API as well:

testWithKey title fn =
  Test.testWithData title (Test.navigationKey >> fn)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The guard is meant is to prevent people using this value in application (non-test) code.

How? I didn’t understand what globalThis.ELM_TESTING === true is supposed to do. (I made a guess, but just confused you instead.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The idea was to do something like

function createNavigationKey() {
  if (globalThis.ELM_TESTING !== true) {
    throw new Error("This is only meant to be used in test code!");
  }
  return dummyNavigationKey;
}

and have the test runners define this global constant somewhere so that this check would pass: globalThis.ELM_TESTING = true;.

That said, with the proposal in my previous comment, this is unnecessary.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Here’s one more idea for how to provide the key without making new test and fuzz functions:

tests =
	withKey <| \navKey ->
        describe "my tests"
            [
                -- tests…
            ]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah... that would work quite well!

withKey : (Navigation.Key -> Test) -> Test

What do you think @Janiczek?

@jfmengels
jfmengels force-pushed the navigation-key branch 3 times, most recently from 679a2ac to 1b7de65 Compare August 22, 2026 12:28
@jfmengels

Copy link
Copy Markdown
Contributor Author

Alright, I've simplified this PR to just adding Test.withNavigationKey. It's a lot simpler and flexible.

Previous attempt can (for now) be found at master...jfmengels:test:navigation-key-old

Comment thread src/Test.elm Outdated
@lydell

lydell commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

This probably closes #24

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Anything with Browser.Navigation.Key cannot be tested

3 participants