How do one test a custom primitives?
Hi,
I made an extension atop createStore for arrays as I use them a lot, and to provide some useful api for mutating it correctly. (I found general store use for this a little messy)
The question is - how do I test if reactivity is preserved?
More specifically, I have this test, which currently fails because the createEffect does not trigger and I don't quite know whether the test is wrong, or the implementation is (it could be both):
The test
A subset of the code:
33 Replies
I wrote testEffect in @solidjs/testing-library for that explicit purpose.
Ah brilliant! I can see it triggering once on addition now, so thats great.
It does feel a little wierd to provide an untracked lambda like that to make an explicit Accessor-like. But is that the way to do it with stores if you want to package it like this?
The same, since you just use an effect in which you can evaluate the different stages of state in your store.
Not quite sure I follow you there.
I believe what is returned as "T[]" is a proxy, even though it looks like an ordinary value - which is why stores work in the first place. What exactly do you mean by using an effect (I assume you mean createEffect) when trying to wrap it like an Accessor?
Like in this example I wrote in our testing docs: https://docs.solidjs.com/guides/testing#testing-effects just without the function call. If you access items within a store, the corresponding signals will be tracked by the proxy.
Man... You know how - when you actually take the time to understand how it works, it makes a lot more sense?
One thing tho: It doesn't seem like createEffect runs once on "mount" when within a returned testEffect in contrast to how createEffect behaves in components - is this intentional?
Since subscription is lazy, the first run is required to subscribe to all the signals inside the tracking scope.
Well, maybe you can answer this:
Will invoking a StoreSetter function (as returned by createStore) always cause a reactive update, or only if top-level object has changed?
I can't exactly determine whether it relies on object equality like React from neither the examples nor the docs. (Object equality as in - different memory addresses, meaning youd always have to allocate new memory to cause an update)
Specifically https://docs.solidjs.com/concepts/stores under "Modifying objects" doesn't exactly make it clear whether it just does the reallocation for you in that scenario, or if it doesn't rely on object equality in the first place
There is an equality comparison before the reactivity is triggered - the exception is for signals if you use the
{ equals: false }
option.
Since stores lazily create signals for items within them that are accessed through the proxy, the usual equality check applies.So what I'm hearing you say, is that the object equality check, is not on the top-level object or array, but rather each element or field?
each accessed leaf of the object tree.
or node
Well that's... actually pretty neat, but I'd never had guessed that from the docs. Any way to turn on a top-level check?
Like in a case where I know I want a reactive update, even though no "leaves" have been mutated?
The root of a store is always treated as immutable.
only the nodes are reactive.
Darn. Oh well, - as for the arborial behaviour, wouldn't that break on sort operations on arrays? Since the leaves arents reallocated, but reordered. Something you would want an update for
yes, you need to create a new array from the sorted one or use reconcile.
setStore('array', [...unsortedArray.sort()])
Hang on, wouldn't that also mean, that since the top-level node, is never checked (concidered immutable), that if you intend to sort an array and react to that, it has to be encapsulated in some other top-level node?
So that it can be concidered a leaf?
Or, reversely, encapsulate the store in a signal, so that you can replace the entire thing and react to that instead.
using a top-level array will also work, but then, you need produce: setStore(produce((s) => s.sort());
Would optionally enabling root-node checks be a good candidate for a PR?
One last question as for how store trees work: How deep does it go? It seems like values are only tracked one layer down from root.
Meaning if you had a store of an object with an array (so the entire array can be reactive), and objects in that array, the array itself is tracked, but no fields in each object is?
As long as it is arrays and objects, it goes as deep as the original object goes. It doesn't do other class instances, though, and treats them as leaves.
If you want a one-level static store, there is
@solid-primitives/static-store
, a community primitive. It is a bit more performant than store, but has less features, obv.That is not what I'm experiencing. Take the nested if statement here, currently I'm reallocating - which I shouldn't have to if reactivity extended arbitrarily, but it fails to cause an update regardless of what I do aside from reallocating everything:
Admittedly, writing tests for this have been... difficult, so the issue might not be the current implementation:
^ fail by time out
Does mutator actually mutate prev[i]?
No, it returns a new object.
but you mutate inside the setStore function, which is plain wrong.
That's optional. You can mutate the existing or replace it. Admittedly, that fails too for object replace
You need produce to mutate.
Or use the setStore patterns to select what you want to change.
So you would better iterate outside of setStore and only call setStore(i, ...) to mutate the store. Maybe batch the whole thing.
Maybe wrap the whole thing in
batch(() => { for ... })
so the changes will occur all at once.I'd like to do that too, but the proxy doesn't seem to update synchronously, so I've been unable to retrieve the most recent state of the array. I'll try a rewrite using the proxy, but I've had nothing but issues
It doesn't update if you do it like you did.
because mutation is not supported by setStore unless you use produce()
At least there's no reactive tracking otherwise.
Ahh. So its an ouroboros
?
I did not catch that reference.
A snake biting its own tail. - The proxy doesn't work, because I mutate in setStore (and wrongly so), and accessing current state of the proxy outside of setStore doesn't work, because I mutate in setStore...
As for "non-change" - it could be an in-place change, like changing a sub-field on the object. Reallocating should make sure that in-place changes still trigger updates.
That's one of the use cases where a top level array store is not ideal.
because otherwise if an array node changes, you get a reactive update for the indices.
There are a few cases where arrays + stores are a less than ideal combination.
If you need to splice an item, for example
Not sure I follow. Isn't that exactly what you'd want. If there is a change to some object at index i, you would want an update for that index?
Also, with a purpose build primitive, maybe some of those less ideal cases can be remedied? Sure you can't for the generic store, but if you know you're dealing with a top-level array, you could maybe mitigate the mayhem?
Unfortunately, parts of these issues are due to Arrays, not to stores - and For only iterates over non-sparse arrays.
I'm still trying to wrap my head around other concepts. I've tried a linked list once.
Yeah well I'm still trying to wrap my head around JS it seems... What is going on here even?
Object equavalence fails, string version fails, weak object equivalence fails ("==")...
I'm at a loss. Damn.
Nvm, its just stores, not JS. Object equivalence is lost because all values are proxied. Is there a way to retrieve the underlying value so that one can use object equivalence?
you can use
unwrap