I would like to initialize a ReactiveVar in my template onCreated method and pass it to an helper:
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import './record-infos-page.html';
import './record-infos-page.scss';
Template.Record_infos_page.onCreated(function recordInfosPageOnCreated() {
this.checkedVariablesIds = new ReactiveVar(['HkcEjZjdxbobbNCZc', 'uTmJKTfoWFFfXPMwx']);
console.log(1, this.checkedVariablesIds.get());
});
Template.Record_infos_page.helpers({
checkedVariablesIds() {
const vars = Template.instance().checkedVariablesIds.get();
console.log(2, vars);
return vars;
},
});
The console.log results is:
1 ['HkcEjZjdxbobbNCZc', 'uTmJKTfoWFFfXPMwx']
2 undefined
Why do I have undefined in the helper?
Thanks!
You can use instance.autorun to reactively get the changes of the variable within your template:
You can use this.autorun from an onCreated or onRendered callback to
reactively update the DOM or the template instance.
From Blaze documentation.
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import './record-infos-page.html';
import './record-infos-page.scss';
Template.Record_infos_page.onCreated(function() {
const instance = this;
instance.autorun(function() {
if (!instance.checkedVariablesIds) {
instance.checkedVariablesIds = new ReactiveVar(['HkcEjZjdxbobbNCZc', 'uTmJKTfoWFFfXPMwx']);
console.log(1, instance.checkedVariablesIds.get());
}
});
});
Template.Record_infos_page.helpers({
checkedVariablesIds() {
const vars = Template.instance().checkedVariablesIds.get();
console.log(2, vars);
return vars;
},
});
Related
I want to test if "onLogin" event emitted from child component will trigger "toLogin" function from parent correctly.
Login.vue
<template>
<ChildComponent
ref="child"
#onLogin="toLogin"
/>
</template>
<script>
import { useAuthStore } from "#/stores/AuthStore.js"; //import Pinia Store
import { userLogin } from "#/service/authService.js"; // import axios functions from another js file
import ChildComponent from "#/components/ChildComponent.vue";
export default {
name: "Login",
components: {
ChildComponent,
},
setup() {
const AuthStore = useAuthStore();
const toLogin = async (param) => {
try {
const res = await userLogin (param);
AuthStore.setTokens(res);
} catch (error) {
console.log(error);
}
};
}
</script>
login.spec.js
import { describe, it, expect, vi, beforeAll } from 'vitest';
import { shallowMount, flushPromises } from '#vue/test-utils';
import { createTestingPinia } from "#pinia/testing";
import Login from "#/views/user/Login.vue"
import { useAuthStore } from "#/stores/AuthStore.js";
describe('Login', () => {
let wrapper = null;
beforeAll(() => {
wrapper = shallowMount(Login, {
global: {
plugins: [createTestingPinia({ createSpy: vi.fn })],
},
});
})
it('login by emitted events', async () => {
const AuthStore = useAuthStore();
const loginParam = {
email: 'dummy#email.com',
password: '12345',
};
const spyOnLogin = vi.spyOn(wrapper.vm, 'toLogin');
const spyOnStore = vi.spyOn(AuthStore, 'setTokens');
await wrapper.vm.$refs.child.$emit('onLogin', loginParam);
await wrapper.vm.$nextTick();
await flushPromises();
expect(spyOnLogin).toHaveBeenCalledOnce(); // will not be called
expect(spyOnStore).toHaveBeenCalledOnce(); // will be called once
})
}
I expected both "spyOnLogin" and "spyOnStore" will be called once from emitted event, however, only "spyOnStore" will be called even though "spyOnStore" should only be called after "spyOnLogin" has been triggered.
The error message is:
AssertionError: expected "toLogin" to be called once
❯ src/components/__tests__:136:24
- Expected "1"
+ Received "0"
What do I fail to understand about Vitest & Vue-Test-Utils?
You shouldn't mock your toLogin method because its part of Login component which you are testing. Therefore, instead of expecting if toLogin has been called, you should check if instructions inside are working correctly.
In your case i would only test if after emit, userLogin and AuthStore.setTokens has been called.
I am converting an existing app from vue 2 to 3.
I am having troubles with accessing globalProperties in components. In some instances using this.$propertyname returns undefined
In my main.js file:
import { createApp } from 'vue'
import App from './App.vue'
import {
appPageLoader
} from '#/store/reactives'
function mount(el){
let app = createApp(App)
app.config.globalProperties.$pgLoader = appPageLoader
// This console log returns proper value
console.log('^^^', appAnnouncer, app.config.globalProperties.$pgLoader)
app.mount(el)
}
mount('#app')
appPageLoader.js
This file contains a reactive object which we can import and assign to globalProperties $pgLoader
import { reactive } from "vue";
let pgLoader = {
pageLoadAnchor: null,
setFocusToTop () {
if (this.pageLoadAnchor instanceof HTMLElement) {
this.pageLoadAnchor.focus()
} else {
console.log('Cannot setFocusToTop() - pageLoadAnchor not found!')
}
}
}
let ReactivePageLoader = reactive(pgLoader)
export default ReactivePageLoader;
app.vue
When trying to access globalProperties using this.$property, I get undefined in component:
import { getCurrentInstance } from "vue";
mounted() {
this.$pgLoader.pageLoadAnchor = this.$refs.pageLoadAnchor; <- this.$pgLoader returns undefined
getCurrentInstance().appContext.config.globalProperties.$pgLoader = this.$refs.pageLoadAnchor; <- works but cumbersome
},
I have found following DataTable code snippet by PrimeVue, that uses the new Compositon API
<script>
import { ref, onMounted } from 'vue';
import ProductService from './service/ProductService';
export default {
setup() {
onMounted(() => {
productService.value.getProductsSmall().then(data => products.value = data);
})
const products = ref();
const productService = ref(new ProductService());
^^^^^^^^^^^^^^^^^^^^^^^^^^
return { products, productService }
}
}
</script>
Does it make any sense to ref() the ProductService?
I guess it does not. Am I wrong?
I believe you are correct, the assignment to ref is unnecessary.
I thought that might have been added for consistence with the options API:
import ProductService from './service/ProductService';
export default {
data() {
return {
products: null
}
},
productService: null,
created() {
this.productService = new ProductService();
},
mounted() {
this.productService.getProductsSmall().then(data => this.products = data);
}
}
However the productService is not part of the data object, so it is not reactive, and because it is a service that doesn't hold state it doesn't need to be.
I'm trying to use CodeMirror on Vue3 and the problem occurs when I call doc.setValue().
The Problem is following:
Cursor position is broken when doc.setValue() is called
CodeMirror throws an exception when continuing editing
The exception is here.
Uncaught TypeError: Cannot read property 'height' of undefined
at lineLength (codemirror.js:1653)
at codemirror.js:5459
at LeafChunk.iterN (codemirror.js:5623)
at Doc.iterN (codemirror.js:5725)
at Doc.iter (codemirror.js:6111)
at makeChangeSingleDocInEditor (codemirror.js:5458)
at makeChangeSingleDoc (codemirror.js:5428)
at makeChangeInner (codemirror.js:5297)
at makeChange (codemirror.js:5288)
at replaceRange (codemirror.js:5502)
How should I solve this?
~~~
Versions are CodeMirror: 5.61.1, Vue.js: 3.0.11
My code is following:
index.html
<div id="app"></div>
<script src="./index.js"></script>
index.js
import { createApp } from 'vue';
import App from './App';
const app = createApp(App);
app.mount('#app');
App.vue
<template>
<div>
<button #click="click">Push Me</button>
<textarea id="codemirror"></textarea>
</div>
</template>
<script>
import CodeMirror from 'codemirror/lib/codemirror.js';
import 'codemirror/lib/codemirror.css';
// import codemirror resources
import 'codemirror/addon/mode/overlay.js';
import 'codemirror/mode/markdown/markdown.js';
import 'codemirror/mode/gfm/gfm.js';
export default {
data () {
return {
cm: null
}
},
mounted () {
this.cm = CodeMirror.fromTextArea(document.getElementById('codemirror'), {
mode: 'gfm',
lineNumbers: true,
});
},
methods: {
click (event) {
this.cm.getDoc().setValue('foo\nbar');
}
}
}
</script>
Thanks.
UPDATES
First, this problem also occurs when I used replaceRange() with multiline.
Unfortunately, I couldn't find any solution. So I tried to find another way.
My solution is recreating Codemirror instance with a textarea that has new content.
It works well.
// Remove old editor
this.cm.toTextArea();
// Get textarea
const textarea = document.getElementById('codemirror');
// Set new content
textarea.value = 'foo\nbar';
// Create new editor
this.cm = CodeMirror.fromTextArea(textarea, { /** options */ });
I found a method, you can use toRaw to get the original Object from Proxy,and this method can be also used in monaco-editor
import { toRaw } from 'vue'
import CodeMirror from 'codemirror/lib/codemirror.js';
import 'codemirror/lib/codemirror.css';
// import codemirror resources
import 'codemirror/addon/mode/overlay.js';
import 'codemirror/mode/markdown/markdown.js';
import 'codemirror/mode/gfm/gfm.js';
export default {
data () {
return {
cm: null
}
},
mounted () {
this.cm = CodeMirror.fromTextArea(document.getElementById('codemirror'), {
mode: 'gfm',
lineNumbers: true,
});
},
methods: {
click (event) {
toRaw(this.cm).setValue('foo\nbar');
}
}
}
Another way,you don't have to define cm in data, just use this.cm
data () {
return {
//cm: null
}
},
I am trying to understand how decorators work with Meteor 1.4. From what I read, this feature is supported.
Now, I am unsure how to actually implement it. From this blog, to decorate a class, I would require this code
export const TestDecorator = (target) => {
let _componentWillMount = target.componentWillMount;
target.componentWillMount = function () {
console.log("*** COMPONENT WILL MOUNT");
_componentWillMount.call(this, ...arguments);
}
return target;
}
Then use it as
import React, { Component } from 'react';
import { TestDecorator } from 'path/to/decorator.js';
#TestDecorator
export default class FooWidget extends Component {
//...
}
The code compiles, but nothing gets output when the component is being rendered.
What am I missing? How do I implement a decorator in Meteor? Is this the proper solution? What is the alternative?
Edit
I have tried this, and it still does not work
export const TestDecorator = (target) => {
console.log("*** THIS IS NOT EVEN DISPLAYED! ***");
target.prototype.componentWillMount = function () {
// ...
};
}
You are assigning your componentWillMount function to the class FooWidget instead of its prototype. Change that to target.prototype.componentWillMount = …. Besides, storing the previous componentWillMount is unnecessary in this case because it is undefined anyway.
Here is a full working example:
main.html
<head>
<title>decorators</title>
</head>
<body>
<div id="root"></div>
</body>
decorator.js
export const TestDecorator = (target) => {
console.log('Decorating…');
target.prototype.componentWillMount = function() {
console.log('Component will mount');
};
};
main.jsx
import React, { Component } from 'react';
import { render } from 'react-dom';
import { TestDecorator } from '/imports/decorator.js';
import './main.html';
#TestDecorator
class FooWidget extends Component {
render() {
return <h1>FooWidget</h1>;
}
}
Meteor.startup(function() {
render(<FooWidget/>, document.getElementById('root'));
});
.babelrc
{
"plugins": ["transform-decorators-legacy"]
}