StencilJS unit testing keyboard event? - reactjs

I want to write unit tests for my custom web-components in stencilJs but have no idea how to do it the right way. Here's what I did so far!
.tsx
...
valueFormat(event: Event): void {
const val = (event.target as HTMLInputElement).value;
const format = Number.parseInt(val, 10);
const newVal = format.toLocaleString(undefined, {
minimumFractionDigits: 2,
});
this.value = newVal;
}
.spec.tsx
it('should format value', async () => {
const comp = new MyComponent();
const spy = jest.spyOn(comp, 'valueFormat');
comp.myInputEvent.emit();
expect(spy).toHaveBeenCalled();
});
I want to test the case, when I type a number in the input field that it format it. So my valueFormat() method, I spying on should be called when a Keyboard event is firing. I hope you can help me out!

If you want to test it with Event in mind, I would strongly recommend you to use newSpecPage(https://stenciljs.com/docs/unit-testing) - as this will allow you to construct your component DOM in memory and allow you to test its logic (so you can easily trigger event like click, keyboard or trigger input value change which I assume where your valueFormat() method get called/binded?)
Another approach is to move formatting logic to separate function which takes just input value as an argument like:
formatInputValue(value: string) {
const format = Number.parseInt(value, 10);
const newVal = format.toLocaleString(undefined, {
minimumFractionDigits: 2,
});
return newVal;
}
then you could easily unit test this method by simply constructing component and then calling the method with whatever the value you want to test with (this is useful if you want to test edge cases like null, empty value, non numeric value etc.)
Personally I wouldn't bother creating function as conversion logic seem to be simple - also one advantage of doing testing via DOM (using newSpecPage()) is that if you ever want to change your formatting logic, amount of test code you need to update could be quite small, meaning your test code is bit more maintainable (again just my personal opinion, it's all depends on how complex the formatting logic or the expected input be)

Related

Should I create a context for something that is mostly useCallback or simply create a hook?

I'm just doing a bit of refactoring and I was wondering if I have a bunch of useCallback calls that I want to group together, is it better do it as a simple hook that I would reuse in a few places?
The result would be
interface IUtils {
something(req: Something) : Result;
somethingElse(req: SomethingElse) : Result;
// etc...
}
So a plain hooks example would be:
export function useUtils() : IUtils {
// there's more but basically for this example I am just using one.
// to narrow the focus down, the `use` methods on this
// block are mostly getting data from existing contexts
// and they themselves do not have any `useEffect`
const authenticatedClient = useAuthenticatedClient();
// this is a method that takes some of the common context stuff like client
// or userProfile etc from above and provides a simpler API for
// the hook users so they don't have to manually create those calls anymore
const something = useCallback((req:SomethingRequest)=> doSomething(authenticatedClient), [authenticatedClient]
// there are a few of the above too.
return {
something
}
}
The other option was to create a context similar to the above
const UtilsContext = createContext<IUtils>({ something: noop });
export UtilsProvider({children}:PropsWithChildren<{}>) : JSX.Element {
const authenticatedClient = useAuthenticatedClient();
const something = useCallback((req:SomethingRequest)=> doSomething(authenticatedClient), [authenticatedClient]
const contextValue = useMemo({something}, [something]);
return <UtilsContext.Provider value={contextValue}>{children}</UtilsContext.Provider>
}
The performance difference between the two approaches are not really visible (since I can only test it in the device) even on the debugger and I am not sure how to even set it up on set up on jsben.ch.
Having it as just a simple hook is easier I find because I don't have to deal with adding yet another component to the tree, but even if I use it in a number of places I don't see any visible improvement but the devices could be so fast that it's moot. But what's the best practice in this situation?

Using lodash/set on a React.useState object

when using React.useState, I can't mutate the object before using the setState method.
for example:
const [buffer, setBuffer] = React.useState(props.someObject);
function handleChange(field: string, data: someObject) {
const update = lodash.set(buffer, field, data);
setBuffer(update);
}
const update never gets the updated data at the path of field. If field is something like "path/path/value" just using setBuffer isn't simple without the help of lodash.set
I know I can do const update = lodash.set(lodash.cloneDeep(buffer), field, data); but that is expensive, especially when we're talking about handleChange being called on every user interaction with a form.
Any alternative approaches or insights into why updates on React.useState objects don't work is much appreciated!

How to chain useState() method from React hook

Is it possible to chain a React hook? If so, how?
A typical application of a hook would look like this:
const [inv, updateInventory] = useState([])
a = ["cheese", "bread", "apples"]
b = a.filter(isDairy)
updateInventory(b)
We can also do this, but it's not chained:
const [inv, updateInventory] = useState([])
a = ["cheese", "bread", "apples"]
updateInventory(a.filter(isDairy))
What I want is a chained hook in a functional style:
const [inv, updateInventory] = useState([])
a = ["cheese", "bread", "apples"]
a.filter(isDairy).updateInventory()
Can a hook can be modified to take state from this?
Proper usage would be:
updateInventory([...a, "cheddar"].quicksort().filter("cheese"))
But if you really want that chaining, look into how to edit the array prototype.
This is really not recommended, as that method will then be available on all arrays.
I think the underlying problem is you're not clear on what's actually happening with method chaining and possibly with hooks. The specific question:
Can a hook can be modified to take state from this?
doesn't really make sense. So let's break down why then come back at the end to how you could approach this.
For method chaining, let's try a simple example using two methods, .filter and .map, that have two important properties:
They actually return arrays (unlike .push, which returns the new length of the array); and
They actually exist on arrays (unlike .quicksort, which exists on neither an array nor the integer you were calling it on).
function isDairy(item) {
return ["cheese", "milk"].includes(item);
}
function getPrice(item) {
return { bread: 0.58, cheese: 0.80, apples: 0.47, milk: 1.01 }[item];
}
const inStock = ["bread", "cheese", "apples"];
inStock
.filter(isDairy)
.map((item) => ({ item, price: getPrice(item) }));
// => [{ item: "cheese", price: 0.8 }]
There's nothing particularly special happening here, each method you're calling returns a new array on which you can also call any method an array has. You could assign the intermediate steps and get the same result:
const filteredStock = stock.filter(isDairy);
// => ["cheese"]
const pricedFilteredStock = filteredStock.map((item) => ({ item, price: getPrice(item) }));
// => [{ item: "cheese", price: 0.8 }]
It is not the case that:
these are standalone functions (like in e.g. Python where you map(callable, iterable)); or
that the item.name syntax is doing anything beyond just accessing a property named name on the item.
If I tried to use the filter method as a standalone function:
filter(isDairy, inStock);
that would be a ReferenceError, or if I defined another function and tried to access it as if it was a prop on an array:
function allUppercase() {
return this.map((item) => item.toUpperCase());
}
inStock.allUppercase();
it would be a TypeError (because isStock.allUppercase is undefined and undefined isn't callable).
Note you could do allUppercase.bind(inStock)() (or the neater allUppercase.call(inStock)), though; JavaScript does have a means of setting this for a function.
When you use the useState hook, you're calling a function that returns an array containing two objects, and destructuring that array to two local variables:
const [thing, setThing] = useState(initialValue);
is equivalent to:
const result = useState(initialValue);
const thing = result[0];
const setThing = result[1];
The thing, setThing naming is just a convention; really, we're accessing those two objects (current value and setter function) by position. They don't have names of their own, you can do const [foo, bar] = useState("baz") (but... don't).
As the setter is a function you might be wondering whether you can use setThing.bind here, but if setThing is written to use this (I didn't look into the implementation, as it's not directly relevant), it's not going to be happy if you change what this is!
So this comes together when you try to do:
const [basket, setBasket] = useState([]);
// ^^^^^^^^^
inStock.filter(...).map(...).setBasket();
// ^^^^^^^^^
As with the example above, this is a TypeError because setBasket doesn't exist on the array returned by .map. The fact that the same "word" setBasket appears twice is totally irrelevant as far as JavaScript is concerned; one is a local variable and the other is a prop on an array, there's no connection between them.
.map(...) returns a new array, one that we didn't already have a reference to, so the only way to make this work is to ensure all arrays have a setBasket method, which means patching the prototype (as covered in adding custom functions into Array.prototype):
Object.defineProperty(Array.prototype, "setBasket", {
value () {
setBasket(this);
},
});
One problem here is that the function setBasket is accessed via a closure, so it needs to happen inside the component where the hook is defined, so it's going to get defined every time the component is rendered (or you're going to useEffect), which is a problem because you can't redefine that method as written...
But let's ignore that because the bigger problem is that every array in your app now has that method, even in contexts where it's not relevant. If you have multiple state hooks, as seems likely in any non-trivial app, your arrays are gaining lots of methods globally that are only for use in small local scopes.
A more feasible approach is to add a generic method that can be used to apply any hook (in fact any function) to an array:
Object.defineProperty(Array.prototype, "andCall", {
value (func) {
return func(this);
},
});
This can be added once, globally, and used to apply whatever hook is relevant:
inStock.filter(...).map(...).andCall(setBasket);
Note that if you're using TypeScript, you'd also have to add the definition to the global array type, e.g.:
declare global {
interface Array<T> {
andCall<S>(func: (arr: Array<T>) => S): S;
}
}

On which level should I start/stop writing tests?

So I started to use react-testing-library and I really like the idea to test user actions, not implementation details.
What I'm really struggling with is where to start writing test ? To be more precise: on which level in the component tree I should start writing my tests?
Let's take the following code es an example:
OrderCreatePage
function CreateOrderPage() {
const [stepOneFrom, setStepOneForm] = useState({});
const [stepTwoFrom, setStepTwoForm] = useState({});
const [stepThreeFrom, setStepThreeForm] = useState({});
const [step, setStep] = useState(1);
const previousStep = () => {
setStep(prev => prev - 1);
}
const nextStep = () => {
setStep(prev => prev + 1);
}
const createOrder = () => {
endpoint.createOrder({
stepOneForm,
stepTwoForm,
stepThreeForm
});
}
return (
<div>
{step === 1 &&
<StepOne
form={stepOneForm}
onNextStep={nextStep}
onFormChange={setStepOneForm}
/>
}
{step === 2 &&
<StepTwo
form={stepTwoForm}
onNextStep={nextStep}
onPreviousStep={previousStep}
onFormChange={setStepTwoForm}
/>
}
{step === 2 &&
<StepTwo
form={stepTwoForm}
onPreviousStep={previousStep}
onCreateOrder={createOrder}
onFormChange={setStepTwoForm}
/>
}
</div>
);
}
StepOne
function StepOne(props) {
const isValidForm() => {
return // do some checks on props.form
}
const handleNextClick = () => {
if(isValidForm()){
props.onNextStep();
}
}
return (
<div>
<ArticlesForm form={props.form} onFormChange={props.onFormChange}/> // StepTwo and StepThree e.g. have DeliveryForm and PaymentForm
<button onClick={props.nextStep}>Next</button>
</div>
);
}
For StepTwo and StepThree, just imagine them to be similar to StepOne.
ArticleForm in the above example is declaring all the input fields etc. and is updating the form values.
Think of all components to be much more complex and don't take this example to serious. In general there are 3 level to write the tests (from lowest to highest level)
Form-level aka ArticleForm: test if form is updated properly
Step-level aka StepOne: test step independently from other steps, ensure that you are only allowed to go to next step when form is valid
Page-level aka OrderCreatePage: test transitions of steps (including validation checks) and check if you can create the order
So where to start testing here?
If you write test for ArticleForm then you don't test validation, thus you need to write tests for StepOne. If you already wrote test for ArticleForm you would need to basically copy the logic of filling the input fields from that test which make ArticleFrom tests kind of useless. Okay let's skip ArticleForm tests then.
For the transitions you need to test OrderCreatePage now. This time you need to copy the logic of entering valid/invalid data from StepOne (and StepTwo, StepThree) tests, otherwise you are not able to proceed/check transitions.
So just remove the StepOne (and StepTwo, StepThree) tests.
But this result in a big test file for OrderCreatePage and that's not anywhere close to a unit test any more.
I would really appreciate any help here, because I always end up with this kind of thoughts..
Well there is no formula for writing good and efficient tests, some measure coverage and then you should start testing the form-level to reach a good coverage percentage.
I like the approach of the author of the library you are using (react-testing-library) https://kentcdodds.com/blog/write-tests
Write tests. Not too many. Mostly integration.
With your example I would try to look at it not from the code perspective, but from a user perspective :
happy flow : User should be able to complete all steps and submits the form (some mocking might be necessary there)
edge case: User should not be able to create order if not all fields are completed / step not validated etc.
In general there are many right answers to this question so keep an open mind, experiment and learn, there is no silver bullet here

Get current value of Animated.Value, React-native

I'm trying to animate View with interpolate. I'd like to get a current value of my Animated.Value, but don't know how. I didn't understand how to do it with React-native docs.
this.state = {
translateAnim: new Animated.Value(0)
}
DeviceEventEmitter.addListener('Accelerometer', function (data) {
console.log(this.state.translateAnim);
// returns an object, but I need a value in current moment
}
I find out, how to get a value:
this.state.translateAnim.addListener(({value}) => this._value = value);
EDIT
to log a value I do the following:
console.log(this.state.translateAnim._value)
This also works for me...
const headerHeight = new Animated.Value(0);
After some manipulation....
console.log(headerHeight.__getValue())
It feels hackish but it gets the job done...
For the people with typescript.
console.log((this.state.translateAnim as any)._value);
It worked for me to full tsc as any.
Number.parseInt(JSON.stringify(translateAnim))
It works on React Hook
edit: CAUTION - MAY CAUSE SEVERE PERFORMANCE ISSUES. I have not been able to figure out why, but if you use this for 30+ simultaneous animations your framerate will slow to a crawl. It seems like it must be a bug in react-native with Animated.Value addListener as I don't see anything wrong with my code, it only sets a listener which sets a ref which should be instantaneous.
Here's a hook I came up with to do it without resorting to accessing private internal values.
/**
* Since there's no (official) way to read an Animated.Value synchronously this is the best solution I could come up with
* to have access to an up-to-date copy of the latest value without sacrificing performance.
*
* #param animatedValue the Animated.Value to track
* #param initial Optional initial value if you know it to initialize the latest value ref before the animated value listener fires for the first time
*
* returns a ref with the latest value of the Animated.Value and a boolean ref indicating if a value has been received yet
*/
const useAnimatedLatestValueRef = (animatedValue: Animated.Value, initial?: number) => {
//If we're given an initial value then we can pretend we've received a value from the listener already
const latestValueRef = useRef(initial ?? 0)
const initialized = useRef(typeof initial == "number")
useEffect(() => {
const id = animatedValue.addListener((v) => {
//Store the latest animated value
latestValueRef.current = v.value
//Indicate that we've recieved a value
initialized.current = true
})
//Return a deregister function to clean up
return () => animatedValue.removeListener(id)
//Note that the behavior here isn't 100% correct if the animatedValue changes -- the returned ref
//may refer to the previous animatedValue's latest value until the new listener returns a value
}, [animatedValue])
return [latestValueRef, initialized] as const
}
It seems like private property. But works for me. Helpful for debugging, but wouldn't recommend using it in production.
translateAnim._value
I actually found another way to get the value (not sure if it is a recommended way, but it works).
Use JSON.stringify() on the animated value and use Number on the result to convert it into Number.
E.g,
const animatedVal = new Animated.Value(0);
const jsanimated = JSON.stringify(animatedVal);
const finalResult = Number(jsanimated)

Resources