I have a vue function that returns an array of strings
1 selectStyles (): string[] {
2 const baseStyles = ['selected-label', 'px-4']
3 const placeholderStyle = this.internalValue?.length === 0 ? 'text-gray-400' : 'text-black'
4
5 return [...baseStyles, placeholderStyle]
6 },
And I have three jest tests cases testing for
if internalValue has a value and it's therefore its length is !0
if internalValue is an empty array and therefore its length is 0
if internalValue is undefined, undefined === 0 is false and therefore second condition is assigned
And yet codecov says that line 3 is a partial hit?
Any idea why?
I read this great response concerning an if statement in python and the results of that, but I don't think it answers my question.
Here are my test cases:
it('selectStyles', () => {
expect(
Select.computed.selectStyles.call({
internalValue: []
})
).toEqual(['selected-label', 'px-4', 'text-gray-400'])
expect(
Select.computed.selectStyles.call({
internalValue: ['some opt selected']
})
).toEqual(['selected-label', 'px-4', 'text-black'])
expect(
Select.computed.selectStyles.call({
internalValue: undefined, // unlikely
})
).toEqual(['selected-label', 'px-4', 'text-black'])
})
Tyia!
You can use Nullish coalescing operator (??) to check if internalValue is undefined or not as this operator returns its right-hand side operand when its left-hand side operand is null or undefined.
// Here if this.internalValue is undefined we are assigning it with an empty array.
this.internalValue = this.internalValue ?? []
Your logic will be like this :
selectStyles (): string[] {
const baseStyles = ['selected-label', 'px-4']
this.internalValue = this.internalValue ?? []
const placeholderStyle = this.internalValue?.length === 0 ? 'text-gray-400' : 'text-black'
return [...baseStyles, placeholderStyle]
}
I just gave my thought as per your problem statement. You can do modifications as per the actual code you have.
Related
expect.to.throw returns a Proxy to the thrown Error, so I can use with.property in order to check some properties of the error.
I attach a details object on my custom errors but I can't test for them, since the with.property compares only using strict equals.
Can I compare this property using deep equal somehow?
Example:
class DocNotFoundError extends Error {
constructor(message, docId) {
super(message)
this.details = { docId }
}
}
const getDoc = id => {
throw new DocNotFoundError('errors.docNotFound', id)
}
const docNotFound = expect(() => getDoc('01234567890')).to.throw('DocNotFoundError')
docNotFound.with.property('details', { docId: '01234567890' }) // fails
The error will fail similar to
AssertionError: expected { Object (error, ...) } to have property 'details' of { Object (docId) }, but got { Object (docId) }
+ expected - actual
I assume this is due to it only checks for reference equality and not deep object equality.
First of all there is a typo in the question: DocNotFoundError should not be in quotes.
I managed to get it working with docNotFound.to.have.deep.property('details', { docId: '01234567890' }), so yes you should perform deep comparison to check if objects have keys with same values.
Source 1
Source 2
I have a computed property that brings back the data "V" into a select dropdown and splits it at the commas and sorts it. All is well.
I also have a check box that allows the user to display records where the date has passed. This info is shown by selecting a checkbox. That works okay too.
So what I'm trying to do is switch between two different functions depending on whether the checkbox is checked.
So what I'd like to say (although the syntax is incorrect) is this:
if (checkboxX === true) const metaVFees = this.resultfilteredResults
else (checkboxX === false) const metaVFees = this.results
and work it into this computed property. Can anyone help with this please?
uniqueSubjects() {
const metaVFees = this.resultfilteredResults // <--- This should switch
.filter((result) => result.metaData && result.metaData.V)
.map((item) => item.metaData.V)
.filter((subject, i, arr) => arr.indexOf(subject) === i);
// Split multiple subjects in strings and store in an array
let subjects = [];
metaVFees.forEach((item) => {
const splitArr = item.split(", ");
subjects = subjects.concat(splitArr);
});
return subjects
.sort()
.filter((subjects, i, arr) => arr.indexOf(subjects) === i);
},
There's several ways to address this, but the easiest is probably JavaScript's Conditional (Ternary) Operator:
[conditional] ? [expression if true] : [expression if false]
It would look like this in your case:
this.metaVFees ? this.resultfilteredResults : this.results
This statement says that if this.metaVFees is true, "execute" this.resultfilteredResults, and if false, then this.results (I say execute because you can use full statements/ expressions with this operator, but we only need single values here).
We can drop the ternary expression straight into your computed property
(I like the parenthesis for clarity, but they aren't actually required since there's a line break after it here):
...
const metaVFees = (this.metaVFees ? this.resultfilteredResults : this.results)
.filter((result) => result.metaData && result.metaData.V)
.map((item) => item.metaData.V)
.filter((subject, i, arr) => arr.indexOf(subject) === i);
...
And here's a short snippet showing this kind of expression in action:
new Vue({
el: '#app',
data() { return {
flag: false,
values1: [1, 2, 3, 4, 5],
values2: [6, 7, 8, 9, 10],
}},
computed: {
ternaryComputed() {
return this.flag ? this.values1 : this.values2
.filter(num => num % 2 == 0); // Filter out odd numbers
},
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app">
<div>{{ `Computed: ${ternaryComputed}` }}</div><br>
<button #click="flag = !flag">Swap Array</button><br><br>
<div>{{`(this.flag ? this.values1 : this.values2) = [${(this.flag ? this.values1 : this.values2)}]`}}</div>
</div>
My code like this :
<template>
...
...
</template>
<script>
export default {
...
methods: {
...mapActions([
'getDataDoctor',
'getDataSchedule'
]),
async visibilityChanged(isVisible, entry, item) {
let param = {
hospitalId: item.hospital_id,
doctorId: item.doctor_id
}
await this.getDataSchedule(param)
let data = this.dataDoctor
for (let key in data) {
this.$set(data[key].find(e => e.doctor_id === item.doctor_id && e.hospital_id === item.hospital_id), 'schedule', this.dataSchedule.schedule)
}
}
},
computed: {
...mapState({
dataDoctor: state => state.dataStore.dataDoctor,
dataSchedule: state => state.dataStore.dataSchedule
}),
},
}
</script>
If I console.log(this.dataDoctor), the result like this :
Or you can see the this.dataDoctor this : https://pastebin.com/yGjsTBjX
this.dataDoctor is state from vuex store. I want to add new value there. The name is schedule
I do like that. But there exist error like this :
Cannot set reactive property on undefined, null, or primitive value: undefined
Uncaught (in promise) TypeError: Cannot use 'in' operator to search for 'schedule' in undefined
How can I solve this problem?
This bit looks wrong:
for (let key in data) {
this.$set(data[key].find(e => e.doctor_id === item.doctor_id && e.hospital_id === item.hospital_id), 'schedule', this.dataSchedule.schedule)
}
From the sample data you posted this will only find something for (at most) one value of key. For the other values of key the find will return undefined and the $set will fail.
Try this:
for (const key in data) {
const entry = data[key].find(e => e.doctor_id === item.doctor_id && e.hospital_id === item.hospital_id)
console.log(`key is ${key}:`, entry)
if (entry) {
this.$set(entry, 'schedule', this.dataSchedule.schedule)
}
}
Of course it is also possible that none of the entries will match. The console logging I've included should help you to identify that scenario too.
I should add that it is usually regarded as best practice to mutate store state within a store mutation rather than directly inside a component.
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$);
I already initialize the data.
data () {
return {
current_product: {},
current_ID: '',
}
}
Then, I fetch data from a REST API on lifecycle created hook.
created () {
var skuID = this.$store.state.selected_productSKU.productSKU_ID
axios.get(`http://localhost:8081/api/products/${skuID}`)
.then(response => {
this.current_ID = response.data.product_ID
this.current_product = response.data
})
.catch(e => {
alert(e)
})
}
And finally, I use computed property to get some value
// THIS JUST RETURN ['XL', 'M']
focusedProduct_SKUS_NoDupSizes () {
var newArr = this.current_product.product_SKU.filter((sku, index, self) =>
index === self.findIndex(t => (
t.productSKU_size === sku.productSKU_size
))
)
var x = newArr.map(a => a.productSKU_size)
return x
}
The vue instance show expected result
But if i call {{ focusedProduct_SKUS_NoDupSizes }} in template.
It doesn't rendered.
The browser return error Error in render: "TypeError: Cannot read property 'filter' of undefined"
What is happening? My first guess is the computed property using the initial structure of current_product which is {} empty object. But isn't that how to initialize an object?
Because of:
computed:
// ...
focusedProduct_SKUS_NoDupSizes () {
var newArr = this.current_product.product_SKU.filter((sku, index, self) =>
^^^^^^^^^^^
You should initialize product_SKU with an empty array:
data () {
return {
current_product: {product_SKU: []}, // changed here
current_ID: '',
}
}
This is needed because the computed property will be executed right away, even before your Ajax gets a chance to return.
Declare it as empty so the computed doesn't throw an error. When the Ajax fulfills, it will recompute automatically.
Even though the Ajax is started at the created(), it won't return before the computed is executed for the first time. More details about this here.