How to mock values of constant in tested function - react-native

I have a problem with mocking constant in my test. I have a file with app configuration. There are just keys with some values nothing more.
appConfig.js
//imports
...
export const CASHING_AMOUNT_LIMIT = 50;
export const CASHING_DELETE_AMOUNT = 25;
...
and this is the reducer that I want to test:
reducer.js
import {
CASHING_AMOUNT_LIMIT,
CASHING_DELETE_AMOUNT,
} from '../appConfig';
...
export const reducer = handleActions({
[REQUEST_DATA]: (state, action) => {
if (payload.count >= CASHING_AMOUNT_LIMIT) {
// do something with data if the condition is true
}
return {
...someState,
};
},
...
In my test, I want to change a value for CASHING_AMOUNT_LIMIT and check if reducer returns current store. And I don't know how to mock this variable in reducer.js. Here is my test:
...//imports
const mockValues = {
CASHING_AMOUNT_LIMIT: 10,
CASHING_DELETE_AMOUNT: 5,
};
jest.mock('../../appConfig.js', () => mockValues);
const {
CASHING_AMOUNT_LIMIT,
CASHING_DELETE_AMOUNT,
} = require('../../appConfig.js');
...
it('MY awesome test ', () => {
expect(CASHING_AMOUNT_LIMIT).toBe(10);
expect(CASHING_DELETE_AMOUNT).toBe(5);
// HERE is all ok CASHING_AMOUNT_LIMIT = 10 and the second variable is 5
// tests are OK
// ....
expect(storeWithCache.dispatch(requestFunction({ test: 'XX'})))
.toEqual(myStore);
...
In the end I use dispatch which call my reducer action and function in reducer.js it runs OK... but with old value for CASHING_AMOUNT_LIMIT it is still 50 (as in appConfig.js) and I need to set 10
Can somebody help me with mocking CASHING_AMOUNT_LIMIT in reducer.js?

This part
jest.mock('../../appConfig.js', () => mockValues);
Needs to be outside the describe block, under the imports.

I have a solution we have all mock in one file testenv.js and it is importing globally I put jest.mock('../../appConfig.js') there and it works!

Related

How to reset all values to initial state in vue3

In vue2, we can use Object.assign(this.$data, this.$options.data()) to reset all values
But how can we do the same thing (one line coding) in vue3, assuming my setup() is ....
setup(props, context) {
let a = ref('value1')
let b = ref('value2')
let c = ref('value3')
function reset() {
Object.assign(????, ????) or ???? <-- ????
}
}
Object.assign(this.$data, this.$options.data()) is a workaround that relies on options API internals.
It's a good practice to have reusable function for scenarios where initial state needs to be accessed more than once:
const getInitialData = () => ({ a: 1, ... });
It can be called both in data and setup.
Component state or its part can be expressed as single object. In this case it's handled the same way as Vue 2 state:
const formData = reactive(getInitialData());
...
Object.assign(formData, getInitialData());
Otherwise a.value, etc need to be assigned with respective initial values, either manually or with helper function:
let data = getInitialData();
for (let [key, ref] of Object.entries({ a, b, ... }))
ref.value = data[key];

i18n won't translate correctly when inside array or object in React Native

I'm trying to use i18n-js to translate some strings into other languages. If I have my code in normal code, it works. Ex:
//Displays "Something" (no quotes) where I want it
<Text> translate("Something"); </Text>
But if I put it inside an array or object, then call it later, it stops working and shows a missing message instead of the text I want translated. Ex:
const messages = {
something: translate("Something"),
// other translations...
}
// later on
// Displays "[missing "en.Something" translation]" (no quotes) where I want it
<Text> messages.something </Text>
The following is my code for my translate function, as well as the config for i18n. I'm using lodash-memoize, but that is unrelated to the issue. I've already checked that the text being passed to i18n.t() is the same (including type) no matter if it's in normal code or in the array, but it still doesn't return the correct thing. I have some error checking written up to avoid getting the missing message on screen, but that still doesn't fix the issue that it can't find the translation.
export const translationGetters = ({
en: () => require('./translations/en.json'),
es: () => require('./translations/es.json')
});
export const translate = memoize(
(key, config) => {
text = i18n.t(key, config)
return text
},
(key, config) => (config ? key + JSON.stringify(config) : key)
);
export const setI18nConfig = () => {
// fallback if no available language fits
const fallback = { languageTag: "en", isRTL: false };
const { languageTag, isRTL } =
RNLocalize.findBestAvailableLanguage(Object.keys(translationGetters)) ||
fallback;
// clear translation cache
translate.cache.clear();
// update layout direction
I18nManager.forceRTL(isRTL);
// set i18n-js config
i18n.translations = { [languageTag]: translationGetters[languageTag]() };
i18n.locale = languageTag;
};
I have no idea where to go on this. Any advice would be appreciated!
Same problem here, workaround is to return array/object from inside a function:
Don't work
export const translations = [i18.t('path')]
Works
export function getTranslations() {
const translations = [i18.t('path')]
return translations
}

Must there be a type assertion in non-initial reducers?

Since the type of of a reducer is defined as
export type Reducer<T> = (state: T | undefined) => T | undefined;
In my reducers that are not the initial reducer, I must declare
state = state as State
Am I missing something, or is this considered a minor inconvenience, please?
Non-initial reducers can be typed (in TypeScript) as (state: T) => T and these will be compatible with the Reducer<T> type found in the library. Here is an example from my codebase, the first snippet is an initial reducer that needs to treat the case for undefined:
const initReducer$ = xs.of(function initReducer(prev?: State): State {
if (prev) {
return prev;
} else {
return {
selfFeedId: '',
currentTab: 0,
};
}
});
This second snippet is a non-initial reducer where I am sure the previous state is not undefined:
const setSelfFeedId$ = ssbSource.selfFeedId$.map(
selfFeedId =>
function setSelfFeedId(prev: State): State {
return {...prev, selfFeedId};
},
);
Notice that when these streams are merged, the resulting type can be Stream<Reducer<State>> with no casting involved:
const reducer$: Stream<Reducer<State>> = xs.merge(initReducer$, setSelfFeedId$);

Sinon stub withArgs ignores extra arguments

My production code looks like:
exports.convertWord = number => { /* some logic here */ }
exports.methodUnderTest = () => {
return exports.convertWord(1);
}
Test code:
const mockConvertToWord = sinon.stub();
mockConvertToWord.withArgs(1).returns('one');
fileUnderTest.convertWord = mockConvertToWord;
const result = fileUnderTest.methodUnderTest();
expect(result).toBeEqual('one');
Test above is green. I expect my test will break if I change prod code to this:
exports.convertWord = number => { /* some logic here */ }
exports.methodUnderTest = () => {
return exports.convertWord(1, 'another arg');
}
but it's not. Sinon works fine even when I pass extra params which I didn't point in withArgs method. How can I tell sinon to return value only when method has been called with exact number of params?
stub
One way to do this is to use stub.callsFake(fakeFunction):
mockConvertToWord.callsFake((...args) => args.length === 1 && args[0] === 1 ? 'one' : undefined);
An alternative approach with a stub is to use a sinon.assert to make sure the stub was called with the epected arguments as noted by #deerawan.
mock
Another approach is to use a mock:
const mock = sinon.mock(fileUnderTest);
mock.expects('convertWord').withExactArgs(1).returns("one");
const result = fileUnderTest.methodUnderTest();
expect(result).toBeEqual('one');
mock.verify();
Another alternative, perhaps you can try to check the call of convertToWord like
...
expect(result).toBeEqual('one');
// check the function
sinon.assert.alwaysCalledWithExactly(mockConvertToWord, '1');
Ref:
https://sinonjs.org/releases/v6.3.4/assertions/#sinonassertalwayscalledwithexactlyspy-arg1-arg2-
Hope it helps

Crash with simple history push

just trying come silly stuff and playing around with Cycle.js. and running into problem. Basically I just have a button. When you click it it's suppose to navigate the location to a random hash and display it. Almost like a stupid router w/o predefined routes. Ie. routes are dynamic. Again this isn't anything practical I am just messing with some stuff and trying to learn Cycle.js. But the code below crashes after I click "Add" button. However the location is updated. If I actually just navigate to "#/asdf" it displays the correct content with "Hash: #/asdf". Not sure why the flow is crashing with error:
render-dom.js:242 TypeError: Cannot read property 'subscribe' of undefined(…)
import Rx from 'rx';
import Cycle from '#cycle/core';
import { div, p, button, makeDOMDriver } from '#cycle/dom';
import { createHashHistory } from 'history';
import ranomdstring from 'randomstring';
const history = createHashHistory({ queryKey: false });
function CreateButton({ DOM }) {
const create$ = DOM.select('.create-button').events('click')
.map(() => {
return ranomdstring.generate(10);
}).startWith(null);
const vtree$ = create$.map(rs => rs ?
history.push(`/${rs}`) :
button('.create-button .btn .btn-default', 'Add')
);
return { DOM: vtree$ };
}
function main(sources) {
const hash = location.hash;
const DOM = sources.DOM;
const vtree$ = hash ?
Rx.Observable.of(
div([
p(`Hash: ${hash}`)
])
) :
CreateButton({ DOM }).DOM;
return {
DOM: vtree$
};
}
Cycle.run(main, {
DOM: makeDOMDriver('#main-container')
});
Thank you for the help
I would further suggest using #cycle/history to do your route changing
(Only showing relevant parts)
import {makeHistoryDriver} from '#cycle/history'
import {createHashHistory} from 'history'
function main(sources) {
...
return {history: Rx.Observable.just('/some/route') } // a stream of urls
}
const history = createHashHistory({ queryKey: false })
Cycle.run(main, {
DOM: makeDOMDriver('#main-container'),
history: makeHistoryDriver(history),
})
On your function CreateButton you are mapping your clicks to history.push() instead of mapping it to a vtree which causes the error:
function CreateButton({ DOM }) {
...
const vtree$ = create$.map(rs => rs
? history.push(`/${rs}`) // <-- not a vtree
: button('.create-button .btn .btn-default', 'Add')
);
...
}
Instead you could use the do operator to perform the hashchange:
function CreateButton({ DOM }) {
const create$ =
...
.do(history.push(`/${rs}`)); // <-- here
const vtree$ = Observable.of(
button('.create-button .btn .btn-default', 'Add')
);
...
}
However in functional programming you should not perform side effects on you app logic, every function must remain pure. Instead, all side effects should be handled by drivers. To learn more take a look at the drivers section on Cycle's documentation
To see a working driver jump at the end of the message.
Moreover on your main function you were not using streams to render your vtree. It would have not been reactive to locationHash changes because vtree$ = hash ? ... : ... is only evaluated once on app bootstrapping (when the main function is evaluated and "wires" every streams together).
An improvement will be to declare your main's vtree$ as following while keeping the same logic:
const vtree$ = hash$.map((hash) => hash ? ... : ...)
Here is a complete solution with a small locationHash driver:
import Rx from 'rx';
import Cycle from '#cycle/core';
import { div, p, button, makeDOMDriver } from '#cycle/dom';
import { createHashHistory } from 'history';
import randomstring from 'randomstring';
function makeLocationHashDriver (params) {
const history = createHashHistory(params);
return (routeChange$) => {
routeChange$
.filter(hash => {
const currentHash = location.hash.replace(/^#?\//g, '')
return hash && hash !== currentHash
})
.subscribe(hash => history.push(`/${hash}`));
return Rx.Observable.fromEvent(window, 'hashchange')
.startWith({})
.map(_ => location.hash);
}
}
function CreateButton({ DOM }) {
const create$ = DOM.select('.create-button').events('click')
.map(() => randomstring.generate(10))
.startWith(null);
const vtree$ = Rx.Observable.of(
button('.create-button .btn .btn-default', 'Add')
);
return { DOM: vtree$, routeChange$: create$ };
}
function main({ DOM, hash }) {
const button = CreateButton({ DOM })
const vtree$ = hash.map(hash => hash
? Rx.Observable.of(
div([
p(`Hash: ${hash}`)
])
)
: button.DOM
)
return {
DOM: vtree$,
hash: button.routeChange$
};
}
Cycle.run(main, {
DOM: makeDOMDriver('#main-container'),
hash: makeLocationHashDriver({ queryKey: false })
});
PS: there is a typo in your randomstring function name, I fixed it in my example.