Stenciljs autocomplete the unit testing files when the tsx files are changed - stenciljs

Is there a way to autocomplete the .spec.tsx file so that it matches the changes made in the .tsx file or every time it has to be done by hand. To be more precise, I would like to autocomplete expect(page.root).toEqualHtml(RIGHT_HERE).
Here is an example:
import { newSpecPage } from '#stencil/core/testing';
import { MyComp} from './mapc-events';
describe('my-component-name', () => {
it('renders', async () => {
const page = await newSpecPage({
components: [MyComp],
html: `<my-component></my-component>`,
autoApplyChanges: true
});
expect(page.root).toEqualHtml(`
<my-component>
<mock:shadow-root>
<slot></slot>
</mock:shadow-root>
</my-component>
`);
});
});

Unfortunately that's not possible at this time, as toEqualHtml accepts a string argument (I.E. it's just as acceptable to TypeScript's type checker to pass it 'HelloWorld' or any other invalid HTML). There's no great way for a development environment to recognize a change in the TSX of a stencil component and reflect that properly in the string provided to toEqualHtml

Related

How to create dynamic components in vueJS

I am new to vueJS and am trying to load components dynamically. I searched on the web and experimented with many suggestions but am still not able to get it to work.
Scenario: I want to have a 'shell' component that acts as a container for swapping in and out other components based on user's selection. The file names of these components will be served up from a database
Here's the shell component:
<template>
<keep-alive>
<component :is='compName'></component>
</keep-alive>
</template>
<script>
props: ['vueFile'],
export default ({
data() {
compName: ()=> import('DefaultPage.vue')
},
watch: {
compName() {
return ()=> import(this.vueFile);
}
}
})
</script>
This does not work.
If I hard code the component file name, it works correctly... e.g.
return ()=> import('MyComponent.vue');
The moment I change the import statement to use a variable, it gives an error even though the variable contains the same string I hard code.
What am I doing wrong?
compName() {
const MyComponent = () => import("~/components/MyComponent.js");
}
You can see this post
https://vuedose.tips/dynamic-imports-in-vue-js-for-better-performance
You can put the components you want to add dynamically in a directory, for example: ./component, and try this
compName () {
return ()=> import(`./component/${this.vueFile}`);
}
The import() must contain at least some information about where the module is located.
https://webpack.js.org/api/module-methods/#dynamic-expressions-in-import

How can I load an nested component dynamically in Vuejs without hardcoding the url

I followed Markus Oberlehner for loading Vue components via http. We have a Vue component precompiled as a .js file hosted on a separate server. When the user navigates to this loader component below, the call to externalComponent() successfully fetches the .js file and renders the component on this page. That is great. This only works if we hardcode the url in the loader component.
We are building a plugin architecture into our site. We have written some single page Vue component files. Each of these files is a plugin. We precomiled these .vue files into .js files according to Markus Oberlehner's helpful tutorial here: https://github.com/maoberlehner/distributed-vue-applications-loading-components-via-http.
We also have a Vue component in our main site - let's call it the loader component - that fetches a .js file and renders it into a component using the externalComponent() method - demonstrated in Markus's tutorial. This works, but since the MyComponent is a constant defined outside of the loader component's data object, we cannot dynamically inject the plugin_id of from the vue router into the .js file's url.
If you're curious why our urls don't end in .js it is because we are passing a url to an endpoint in our server instead. This endpoint fetches the .js file and returns it to our client.
<template>
<div>
<MyComponent />
</div>
</template>
<script>
import util from "~/js/util.js";
let MyComponent = () =>
/* If we hadcode the url, the page renders no problemo.
*
* util.externalComponent("http://localhost:8081/api/plugins/167/code");
*/
/* However, we'd like to fetch the plugin_id from the Vue router and inject that into the argument
* as I've tried to achieve in the line of code below.
*
* The following code does not work because Vue apparently loads the MyComponent element in the DOM
* before executing the created() hook. We get the error "plugin_id is not defined."
*/
util.externalComponent(
"http://localhost:8081/api/plugins/" + plugin_id + "/code"
);
export default {
name: "plugin",
components: {
MyComponent
},
data() {
return {
plugin_id: null
};
},
created() {
/* This line does indeed populate the plugin_id data variable, although this happens after the
* page attempts to load MyComponent
*/
this.plugin_id = this.$route.params.plugin_id;
}
};
</script>
So, how can we modify this code to dynamically insert the plugin_id into the url?
This is a nuxt project by the way.
Update
Here is an approach that works the first time I load the page, but consecutive loads are still a problem. Specifically, the first component just loads again regardless of whatever the new plugin id may be.
But this looks like the right direction...
<template>
<div>
<component v-bind:is="component" v-if="loadedUrl"></component>
{{ plugin_id }}
</div>
</template>
<script>
import util from "~/js/util.js";
export default {
name: "plugin",
props: [],
data() {
return {
loadedUrl: false,
component: null
};
},
beforeRouteEnter(to, from, next) {
next(vm => {
vm.loadedUrl = false;
vm.component = () =>
util.externalComponent(
"http://localhost:8081/api/plugins/" + to.params.plugin_id + "/code"
);
vm.loadedUrl = true;
next();
});
},
beforeDestroy() {
//does not seem to help.
console.log("in beforeDestroy");
this.component = null;
}
};
</script>

Vue/Nuxt: How to define a global method accessible to all components?

I just want to be able to call
{{ globalThing(0) }}
in templates, without needing to define globalThing in each .vue file.
I've tried all manner of plugin configurations (or mixins? not sure if Nuxt uses that terminology.), all to no avail. It seems no matter what I do, globalThing and this.globalThing remain undefined.
In some cases, I can even debug in Chrome and see this this.globalThing is indeed defined... but the code crashes anyway, which I find very hard to explain.
Here is one of my many attempts, this time using a plugin:
nuxt.config.js:
plugins: [
{
src: '~/plugins/global.js',
mode: 'client'
},
],
global.js:
import Vue from 'vue';
Vue.prototype.globalFunction = arg => {
console.log('arg', arg);
return arg;
};
and in the template in the .vue file:
<div>gloabal test {{globalFunction('toto')}}</div>
and... the result:
TypeError
_vm.globalFunction is not a function
Here's a different idea, using Vuex store.
store/index.js:
export const actions = {
globalThing(p) {
return p + ' test';
}
};
.vue file template:
test result: {{test('fafa')}}
.vue file script:
import { mapActions } from 'vuex';
export default {
methods: {
...mapActions({
test: 'globalThing'
}),
}
};
aaaaaaaaand the result is.........
test result: [object Promise]
OK, so at least the method exists this time. I would much prefer not to be forced to do this "import mapActions" dance etc. in each component... but if that's really the only way, whatever.
However, all I get is a Promise, since this call is async. When it completes, the promise does indeed contain the returned value, but that is of no use here, since I need it to be returned from the method.
EDIT
On the client, "this" is undefined, except that..... it isn't! That is to say,
console.log('this', this);
says "undefined", but Chrome's debugger claims that, right after this console log, "this" is exactly what it is supposed to be (the component instance), and so is this.$store!
I'm adding a screenshot here as proof, since I don't even believe my own eyes.
https://nuxtjs.org/guide/plugins/
Nuxt explain this in Inject in $root & context section.
you must inject your global methods to Vue instance and context.
for example we have a hello.js file.
in plugins/hello.js:
export default (context, inject) => {
const hello = (msg) => console.log(`Hello ${msg}!`)
// Inject $hello(msg) in Vue, context and store.
inject('hello', hello)
// For Nuxt <= 2.12, also add 👇
context.$hello = hello
}
and then add this file in nuxt.config.js:
export default {
plugins: ['~/plugins/hello.js']
}
Use Nuxt's inject to get the method available everywhere
export default ({ app }, inject) => {
inject('myInjectedFunction', (string) => console.log('That was easy!', string))
}
Make sure you access that function as $myInjectedFunction (note $)
Make sure you added it in nuxt.config.js plugins section
If all else fails, wrap the function in an object and inject object so you'd have something like $myWrapper.myFunction() in your templates - we use objects injected from plugins all over the place and it works (e.g. in v-if in template, so pretty sure it would work from {{ }} too).
for example, our analytics.js plugin looks more less:
import Vue from 'vue';
const analytics = {
setAnalyticsUsersData(store) {...}
...
}
//this is to help Webstorm with autocomplete
Vue.prototype.$analytics = analytics;
export default ({app}, inject) => {
inject('analytics', analytics);
}
Which is then called as $analytics.setAnalyticsUsersData(...)
P.S. Just noticed something. You have your plugin in client mode. If you're running in universal, you have to make sure that this plugin (and the function) is not used anywhere during SSR. If it's in template, it's likely it actually is used during SSR and thus is undefined. Change your plugin to run in both modes as well.
This would be the approach with Vuex and Nuxt:
// store/index.js
export const state = () => ({
globalThing: ''
})
export const mutations = {
setGlobalThing (state, value) {
state.globalThing = value
}
}
// .vue file script
export default {
created() {
this.$store.commit('setGlobalThing', 'hello')
},
};
// .vue file template
{{ this.$store.state.globalThing }}

How to create unit test cases with Vue, Karma, browserify

I am trying to build some unit test cases to my existing Vue project.
I found some documents there but not useful especially for testing on functions such as Watch, Promise and Then.
Is there any specific and detailed guide line on unit testing with Vue and these plugins?
The target vue has defined a function named test.
const vm = new Vue(target).$mount();
vm.test("message");
But the error message is vm.test is not a function
I do not know why I could not use the function defined in the target.vue.
Meanwhile once I use the test function to change some data, the target vue will update the data automatically.
But it seems that Vue.nextTick does not work on this situation.
Could someone help me on this point?
Thank you very much for your help.
Hellocomponent
export default {
name: 'hello',
data () {
return {
msg: 'Welcome to Your Vue.js App',
test: 'Testing'
}
}
}
Hello.spec.js //for testing Hello.vue
describe('Hello', () => {
it('set correct default data', () => {
expect(typeof Hello.data).to.equal('function')
assert.typeOf(Hello.data, 'function')
const defaultdata = Hello.data()
expect(defaultdata.test).to.be.a('string')
expect(defaultdata.test).to.equal('Testing')
})
})
This is test case of Hello component of vue.js which is created automatically when new template is created. This is using Karma+Mocha+Chai.

How to dynamically mock ES6 modules with SystemJS?

I have a single-page application written in ES6. The code in transpiled server-side into classic javascript by babelJs, then loaded by SystemJs.
Javascript present in my html file:
System.config({
baseURL: '/js',
meta: {
'/js/*': { format: 'cjs' }
}});
System.defaultJSExtensions = true;
System.import("index.js")
.catch(function (error) {
console.error(error)
});
index.js:
import f1 from 'file1';
import f2 from 'file2';
// code here ...
Everything works fine. index.js is loaded, and all import statements are correctly executed.
Now, I want to create some pages with mocked ES6 modules, for testing purpose. My goal is to display pages by replacing model classes (contained in ES6 modules) with other static test classes.
Let's say I have 3 files: real_model.js, fake_model.js and component.js. component.js import the real model (import Model from 'real_model';).
How can I replace the real model by the fake one (in the component) dynamically ?
It's been a while since this question was posted, but maybe this solution might still be of help to anyone else.
With SystemJS it is possible to create a module on-the-fly using System.newModule. Then you can use System.set to overwrite existing modules with the new one. In our tests we use the following helper function to mock existing modules:
function mockModule(name, value) {
const normalizedName = System.normalizeSync(name);
System.delete(normalizedName);
System.set(normalizedName, System.newModule(Object.assign({ default: value }, value)));
}
Then, e.g. inside the beforeEach callback, we assign the mock and then import the module to be tested using System.import:
let [component, fake_model] = [];
beforeEach(() => {
// define mock
fake_model = { foo: 'bar' };
// overwrite module with mock
mockModule('real_model', fake_model);
// delete and reimport module
System.delete(System.normalizeSync('component'));
return System.import('src/testing').then((m) => {
component = m.default;
}).catch(err => console.error(err));
});
// test your component here ...
A big advantage of this approach is that you don't need an additional mocking library and it works solely with SystemJS.