How to avoid [Vue warn] for custom directive? - javascript

I created a custom directive and it's working good, but when I run the mocha test for the component where I use this custom directive I receive this warning message [Vue warn]: Failed to resolve directive: scroll-text, tell me please how to fix that
test file:
import { shallowMount } from "#vue/test-utils"
import { scrollText } from "z-common/services"
import ZSourcesList from "./ZSourcesList"
Vue.use(scrollText)
const stubs = [
"z-text-field",
"v-progress-circular",
"v-icon",
"z-btn"
]
describe("ZSourcesList.vue", () => {
const sources = []
for (let i = 0; i < 20; i++) {
sources.push({
field: "source",
// format numbers to get 2 diggit number with leading zero 1 -> 01
value: `cluster-${i.toLocaleString('en-US', { minimumIntegerDigits: 2, useGrouping: false })}`,
__typename: "SuggestV2Result"
})
}
it("displays 'No matching sources found' if there are no sources", () => {
const wrapper = shallowMount(ZSourcesList, {
mocks: {
$apollo: {
queries: {
suggestions: {
loading: false,
},
},
},
},
stubs,
sync: false,
data() {
return {
suggestions: [],
}
},
})
expect(wrapper.find(".notification .z-note")).to.exist
})
})

Try registering your custom directive on a local vue instance and then mounting to that local vue instance.
import { shallowMount, createLocalVue } from "#vue/test-utils"
import { scrollText } from "z-common/services"
import ZSourcesList from "./ZSourcesList"
const localVue = createLocalVue()
localVue.use(scrollText) // Register the plugin to local vue
const stubs = [
"z-text-field",
"v-progress-circular",
"v-icon",
"z-btn"
]
describe("ZSourcesList.vue", () => {
const sources = []
for (let i = 0; i < 20; i++) {
sources.push({
field: "source",
// format numbers to get 2 diggit number with leading zero 1 -> 01
value: `cluster-${i.toLocaleString('en-US', { minimumIntegerDigits: 2, useGrouping: false })}`,
__typename: "SuggestV2Result"
})
}
it("displays 'No matching sources found' if there are no sources", () => {
const wrapper = shallowMount(ZSourcesList, {
mocks: {
$apollo: {
queries: {
suggestions: {
loading: false,
},
},
},
},
localVue, // Mount this component to localVue
stubs,
sync: false,
data() {
return {
suggestions: [],
}
},
})
expect(wrapper.find(".notification .z-note")).to.exist
})
})
Using a local vue instance instead of the global in test cases will also prevent polluting the global vue instance and help to prevent side effects in other test cases.

Related

Snapshot test using vue is failing with "TypeError: Cannot read property of undefined"

The component itself works on the page with no errors.
So does storybook, the only issue is the unit test.
import { mount } from '../../vue';
import { createLocalVue } from '#vue/test-utils';
import Vuex from 'vuex';
import SessionGymChart from '#/components/SessionGymChart.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
const defaultprops = {
endDateOveride: '',
totalHours: 0,
sessionsTotal: 1,
isLoading: false,
hasTooMuchData: false,
swapChart: false,
chartData: {}
};
describe('SessionGymChart', () => {
let store;
beforeEach(() => {
store = new Vuex.Store({
state: {
user: { billing: true },
chartData: {
days: 10,
sessions: [
{
id: '324365egdfgfd',
sessionReference: '056343',
dateStarted: '2022-08-26T16:23:14.909Z',
dateEnded: '2022-08-26T16:23:22.000Z',
userId: 'dfgdfgdfg545645',
userDisplayName: 'Tester'
}
]
}
},
mutations: {},
actions: {}
});
});
Is there anything obvious I'm doing wrong?
There is a computed property where it breaks on the component. It seems like it can't access the sessions data (saying undefined) and I'm not sure why as it is inside defaultprops.
This is where it shows to be breaking in the component, at a computed property and this shows on the snapshot error.
gymSeries() {
const { sessions } = this.chartData ? this.chartData : {};
> if (sessions.length === 0) return {};
^
else {
const sessionsUsage = sessions.map(x => {
return {
Any help would be much appreciated!
This actually returns undefined if this.chartData is undefined:
const { sessions } = this.chartData ? this.chartData : {};
The problem there is that you're destructuring chartData, and you expect the return value of the ternary statement to be an object that contains a sessions property.
If chartData is undefined, that results in:
const { sessions } = {};
You can fix that by adding an empty sessions property:
const { sessions } = this.chartData ? this.chartData : { sessions: {} };
Or, shorter, using optional chaining:
const sessions = this.chartData?.sessions || {};

Quasar QSelect is not opening when performing AJAX call

I have been trying to create a simple auto complete using Quasar's select but I'm not sure if this is a bug or if I'm doing something wrong.
Problem
Whenever I click the QSelect component, it doesn't show the dropdown where I can pick the options from.
video of the problem
As soon as I click on the QSelect component, I make a request to fetch a list of 50 tags, then I populate the tags to my QSelect but the dropdown doesn't show.
Code
import type { PropType } from "vue";
import { defineComponent, h, ref } from "vue";
import type { TagCodec } from "#/services/api/resources/tags/codec";
import { list } from "#/services/api/resources/tags/actions";
import { QSelect } from "quasar";
export const TagAutoComplete = defineComponent({
name: "TagAutoComplete",
props: {
modelValue: { type: Array as PropType<TagCodec[]> },
},
emits: ["update:modelValue"],
setup(props, context) {
const loading = ref(false);
const tags = ref<TagCodec[]>([]);
// eslint-disable-next-line #typescript-eslint/ban-types
const onFilterTest = (val: string, doneFn: (update: Function) => void) => {
const parameters = val === "" ? {} : { title: val };
doneFn(async () => {
loading.value = true;
const response = await list(parameters);
if (val) {
const needle = val.toLowerCase();
tags.value = response.data.data.filter(
(tag) => tag.title.toLowerCase().indexOf(needle) > -1
);
} else {
tags.value = response.data.data;
}
loading.value = false;
});
};
const onInput = (values: TagCodec[]) => {
context.emit("update:modelValue", values);
};
return function render() {
return h(QSelect, {
modelValue: props.modelValue,
multiple: true,
options: tags.value,
dense: true,
optionLabel: "title",
optionValue: "id",
outlined: true,
useInput: true,
useChips: true,
placeholder: "Start typing to search",
onFilter: onFilterTest,
"onUpdate:modelValue": onInput,
loading: loading.value,
});
};
},
});
What I have tried
I have tried to use the several props that is available for the component but nothing seemed to work.
My understanding is that whenever we want to create an AJAX request using QSelect we should use the onFilter event emitted by QSelect and handle the case from there.
Questions
Is this the way to create a Quasar AJAX Autocomplete? (I have tried to search online but all the answers are in Quasar's forums that are currently returning BAD GATEWAY)
What am I doing wrong that it is not displaying the dropdown as soon as I click on the QSelect?
It seems updateFn may not allow being async. Shift the async action a level up to solve the issue.
const onFilterTest = async (val, update /* abort */) => {
const parameters = val === '' ? {} : { title: val };
loading.value = true;
const response = await list(parameters);
let list = response.data.data;
if (val) {
const needle = val.toLowerCase();
list = response.data.data.filter((x) => x.title.toLowerCase()
.includes(needle));
}
update(() => {
tags.value = list;
loading.value = false;
});
};
I tested it by the following code and mocked values.
// import type { PropType } from 'vue';
import { defineComponent, h, ref } from 'vue';
// import type { TagCodec } from "#/services/api/resources/tags/codec";
// import { list } from "#/services/api/resources/tags/actions";
import { QSelect } from 'quasar';
export const TagAutoComplete = defineComponent({
name: 'TagAutoComplete',
props: {
modelValue: { type: [] },
},
emits: ['update:modelValue'],
setup(props, context) {
const loading = ref(false);
const tags = ref([]);
const onFilterTest = async (val, update /* abort */) => {
// const parameters = val === '' ? {} : { title: val };
loading.value = true;
const response = await new Promise((resolve) => {
setTimeout(() => {
resolve({
data: {
data: [
{
id: 1,
title: 'Vue',
},
{
id: 2,
title: 'Vuex',
},
{
id: 3,
title: 'Nuxt',
},
{
id: 4,
title: 'SSR',
},
],
},
});
}, 3000);
});
let list = response.data.data;
if (val) {
const needle = val.toLowerCase();
list = response.data.data.filter((x) => x.title.toLowerCase()
.includes(needle));
}
update(() => {
tags.value = list;
loading.value = false;
});
};
const onInput = (values) => {
context.emit('update:modelValue', values);
};
return function render() {
return h(QSelect, {
modelValue: props.modelValue,
multiple: true,
options: tags.value,
dense: true,
optionLabel: 'title',
optionValue: 'id',
outlined: true,
useInput: true,
useChips: true,
placeholder: 'Start typing to search',
onFilter: onFilterTest,
'onUpdate:modelValue': onInput,
loading: loading.value,
});
};
},
});

NGXS - Testing Lazy Selectors failure

we are using ngxs and we do have some lazy selectors defined in separated files from the state definition
export class SectionSelectors {
#Selector([CatalogState])
static ById(state: CatalogModel) {
return function getSectionById(id: number): Section {
const selectedSection: Section = state.sections[id];
return selectedSection;
};
}
}
And we have test cases like
import { TestBed } from '#angular/core/testing';
import { Section } from '#miq-catalog/catalog';
import { NgxsModule, Store } from '#ngxs/store';
import { CatalogModel, CatalogState } from './catalog.state';
import { SectionSelectors } from './section.selectors';
describe('SectionSelectors', () => {
it('should select the section by id', () => {
const one: Section = { sectionId: 1, title: '', columns: [] };
const two: Section = { sectionId: 2, title: '', columns: [] };
const state: CatalogModel = {
catalog: [],
sections: { 1: one, 2: two },
columns: {},
catalogLoaded: true,
};
const selectionFunction = SectionSelectors.ById(state);
const result = selectionFunction(1);
expect(result).toBeDefined();
expect(result).toBe(one);
expect(result.sectionId).toBe(1);
const result2 = selectionFunction(2);
expect(result2).toBeDefined();
expect(result2).toBe(two);
expect(result2.sectionId).toBe(2);
});
});
We are passing the state to the selector however we are getting the next error
An error was thrown in afterAll
Uncaught ReferenceError: Cannot access 'CatalogState' before initialization
ReferenceError: Cannot access 'CatalogState' before initialization
I noticed that if I move these selector to the CatalogState (where the #State definition is) the problem is fixed. But this is forcing us to put all selectors there and we think it's good to have them scoped on their own related files so we don't "pollute" with mixed selectors.
Is there a way we can fix the test case? Does someone already faced this Lazy Selector testing before?
As complementary info this is how our State looks like
#State({
name: 'Catalog',
defaults: {
catalogLoaded: false,
columns: {},
sections: {},
catalog: [],
},
})
export class CatalogState {
constructor(private store: Store) {}
#Action(RetrieveCatalogInfo)
#Action(ChangeColumnConfig)
#Action(ClearCatalog)
public executeAction(ctx: StateContext<CatalogModel>, params: ExecutableAction<CatalogModel>) {
return params.execute({ ctx, store: this.store });
}
}
This should not be a problem with the latest version of NGXS (since v3.6.1).

Testing component - Error in mounted hook: "TypeError: Cannot read property 'dispatch' of undefined"

I am trying to write a simple test for my vue component. Since the vue component makes an async call on mount and updates the vuex store, dispatch is called during mount, which breaks my existing unit tests. Any idea how to overcome this? Since I am mocking table data, I don't need the mounted() function to be called when running the tests.
MyTable.spec.js
const wrapper = shallowMount(MyTable, {
propsData: {
tableData: [
{
"product_id":10826345236,
"name":"T-Shirt"
}
],
columns: ['product_id', 'name'],
headings: ['Product ID', 'Name'],
actionType: 'loadProducts'
}
});
...
MyTable.vue
...
data() {
return {
options: {
...
}
};
},
methods: {
getHeadings() {
let headings = {};
this.columns.map((key, i) => headings[key] = this.headings[i]);
return headings;
},
setColumnClasses() {
let classes = {};
this.columns.map((key) => classes[key] = key);
return classes;
},
loadRecords(actionType) {
this.$store.dispatch(actionType);
}
},
props: {
tableData: {
type: Array,
required: true
},
columns: {
type: Array,
required: true
},
actionType: {
type: String,
required: true
},
headings: {
type: Array,
required: true
},
...
},
mounted() {
this.loadRecords(this.actionType);
}
You are getting this error message because Vue (when mounted) is expecting that the this.$store is defined, and while it might be within your application, you are not importing it, nor are you mocking it.
Here is your test function code you provided:
const wrapper = shallowMount(MyTable, {
propsData: {
tableData: [
{
"product_id":10826345236,
"name":"T-Shirt"
}
],
columns: ['product_id', 'name'],
headings: ['Product ID', 'Name'],
actionType: 'loadProducts'
}
});
Here is what you need to add:
import store from '../path/to/store.js';
import { createLocalVue, shallowMount } from '#vue/test-utils';
// You will want to create a local Vue instance for testing purposes: https://vue-test-utils.vuejs.org/api/#createlocalvue
const localVue = createLocalVue();
// This tells the local Vue instance to use Vuex for the store mechanism.
localVue.use(Vuex);
const wrapper = shallowMount(MyTable, {
localVue, // Bind the local Vue instance when we shallow-mount the component.
store, // Bind the store so all of the methods are available when invoked.
propsData: {
tableData: [
{
"product_id":10826345236,
"name":"T-Shirt"
}
],
columns: ['product_id', 'name'],
headings: ['Product ID', 'Name'],
actionType: 'loadProducts'
}
});

I use an array as parameter and I receive an observer

I'm need this array as a parameter but when I try to use it, I only receive a observer. How to use the information of the array instead of the observer?
When I console.log in the function that retrieves the information from the DB it shows the data as it should.
I don't know what else to do, I've already tried other ways to try to solve this but I wasn't sucessfull.
<script>
import { ClientTable } from 'vue-tables-2';
import Vue from 'vue';
import Multiselect from 'vue-multiselect';
import options from './../../../commons/helpers/grid.config';
import edit from './edit';
Vue.use(ClientTable, options, false, 'bootstrap4', 'default');
Vue.component('multiselect', Multiselect);
export default {
name: 'Reporting',
removable: false,
components: {
edit,
},
showLoading: true,
data() {
return {
selected: null,
columns: ['period', 'costCenter', 'hours', 'actions'],
reportingsList: [],
periods: [],
totalHours: 0,
options: {
sortable: [],
columnsClasses: {
actions: 'action-column text-center',
period: 'period-column',
costCenter: 'costCenter-Column',
hours: 'hours-column',
},
},
};
},
mounted() {
this.getAll();
},
methods: {
getTotalHours(reportings) {
let result = 0;
for (let i = 0, length = reportings.length; i < length; i += 1) {
result += reportings[i].hours;
}
console.log(result); //eslint-disable-line
console.log(this.reportingsList); //eslint-disable-line
this.totalHours = result;
},
getAll() {
const url = 'reportings/getAll';
this.$http().get(url).then((response) => {
this.reportingsList = response.data;
console.log(this.reportingsList.length);//eslint-disable-line
});
this.getTotalHours(this.reportingsList);
}
</script>
It's asynchronous code, this.getTotalHours will be execute before this.$http.get finish.
You need to chain .then
this.$http().get(url).then((response) => {
this.reportingsList = response.data;
console.log(this.reportingsList.length);//eslint-disable-line
return response.data
}).then(() => {
this.getTotalHours(this.reportingsList);
});

Categories