Vue3 Test Utils v-binded attribute not available - vuejs3

I am using Vue3 and Vue Test Utils V2, I want to test if a v-binded property has a certain value. But I am not able to retreive the v-binded href property in my unit test.
it("renders english hyperlinks", () => {
const wrapper = footerFactory({
year: 2022,
currentYear: 2022,
locale: "en",
});
expect(wrapper.find("#tacLink").attributes("href")).equals(
"http://www.taclink_en.com"
);
});
When doing a console.log for wrapper.find("#tacLink").attributes() it retreives the following string "{ id: 'tacLink', target: '_blank', 'data-v-1d6d62bc': '' }" so it really is not available in my test.
But in my template it looks like this
<a :href="tacLink" id="tacLink" target="_blank">{{
$t("footer.tac")
}}</a>
In my application the rendered html looks like this
Terms and Conditions
So my question is how can I test v-binded values?

Related

Custom controls in StoryBook with Angular

I'm starting to use Storybook in an Angular component library.
It works fine for components with inputs like booleans or strings, it shows those inputs using controls.
But there are certain components where the input is an object.
For those components I'm able to provide an object, but users are able to edit a string with the JSON representation of the object instead of several inputs.
How do I do this in a user-friendly way so users can edit those properties in the control without using a JSON representation of the object?
If you're using Knobs, you can write them like this:
This sample here:
class sample{
title: string;
text: string;
settings: {
language: string;
disabled: boolean;
}
}
would turn into this:
template: `
<div style="max-width:80vw;margin:auto;">
<app-custom-component
[title]="this.titleKnob"
[text]="this.textKnob"
[settings]="this.settingsKnob"
></app-custom-component>
</div>
`,
props: {
titleKnob: text('Title',''),
textKnob: text('Text area', ''),
settingsKnob: {
language: text('Default Language', 'en'),
disabled: boolean('Disabled', false),
}
}

Flags inside a React Bootstrap select option renders as [object, object]

I want to display Flags icons inside a React Bootstrap selection Option. I have tried both CSS based and React based libraries to do so and in each case I get only [object object]
I have tried with the https://github.com/lipis/flag-icon-css CSS library
<Form.Control as="select">
<option><span className="flag-icon flag-icon-gr"></span></option>
</Form.Control>
Which gives me a warning and the same [Object object]
Warning: Only strings and numbers are supported as <option> children.
I have also attempted with the React wrapper for the same library https://www.npmjs.com/package/react-flag-icon-css
<Form.Control as="select">
<option><FlagIcon className="countryIcon" code="us" size="lg"/></option>
</Form.Control>
Which does not generate a warning but no results either
Does anyone know how I can get something else than string or number in the Option, or another way to include an icon ?
Option HTML tag accepts text only, it can't accept any other HTML, it will strip it. You can check this React issue [bug][16.5.0] option returns [object Object] instead of string and read the comment by Dan Abramov:
I don't think it was strictly a regression. This is kind of a thorny
area. It was never intentionally supported. It accidentally worked on
initial mount but then crashed on updates (#13261). Fixing the crash
was more important, so we fixed it to be treated as text content
(which it should be). Unfortunately this means putting custom
components in the middle is not supported. That's consistent with how
textarea and similar elements work.
I think it's better to show invalid output and warn about something
that breaks on updates, than to let people use it only to discover it
crashes in production. But I can see arguments for why this should be
supported when the custom component returns a string. Unfortunately I
don't know how to fix it in a way that would both solve the update
crashes and support text-only content. I think for now it's reasonable
to say putting custom components into doesn't really work
(and never quite worked correctly), and ask you to manually provide a
string to it.
Alternatively, you can use Bootstrap Dropdowns to create a dropdown button with a list of countries using the code below:
App.js:
...
import Dropdown from 'react-bootstrap/Dropdown';
import FlagIcon from './FlagIcon.js'
function App() {
const [countries] = useState([
{ code: 'gr', title: 'Greece'},
{ code: 'gb', title: 'United Kingdom'},
{ code: 'us', title: 'United States'}
]);
const [toggleContents, setToggleContents] = useState("Select a country");
const [selectedCountry, setSelectedCountry] = useState();
return (
<div className="App">
<Form>
<Dropdown
onSelect={eventKey => {
const { code, title } = countries.find(({ code }) => eventKey === code);
setSelectedCountry(eventKey);
setToggleContents(<><FlagIcon code={code}/> {title}</>);
}}
>
<Dropdown.Toggle variant="secondary" id="dropdown-flags" className="text-left" style={{ width: 300 }}>
{toggleContents}
</Dropdown.Toggle>
<Dropdown.Menu>
{countries.map(({ code, title }) => (
<Dropdown.Item key={code} eventKey={code}><FlagIcon code={code}/> {title}</Dropdown.Item>
))}
</Dropdown.Menu>
</Dropdown>
</Form>
</div>
);
}
FlagIcon.js:
import React from 'react';
import FlagIconFactory from 'react-flag-icon-css';
// const FlagIcon = FlagIconFactory(React);
// If you are not using css modules, write the following:
const FlagIcon = FlagIconFactory(React, { useCssModules: false })
export default FlagIcon;
You'll get a dropdown button like this:
You can also check this working Stackblitz: https://stackblitz.com/edit/react-bootstrap-flags-dropdown-menu
Are you closing the tag
<Form.Control as="select">
[object Object] is displayed e.g when you are concatenating a string with an object, for example:
console.log(""+{})

How to test React dynamic style prop?

Am teaching myself React and am starting on testing. Using the recommended stuff from the docs... create-react-app, jest, testing-library/react. I have a component that renders a dynamic style, something like this
const ScalingDiv = (props) => (
<StyledDiv size={props.size || 42}>
<OtherEl>
{props.text}
</OtherEl>
</StyledDiv>
);
I would like to verify that my logic in there is working correctly (that 42 is used as a fallback size). I cannot find any examples or docs of this. I had hoped that something like this would work-
test('check fallback size', async () => {
const {container} = render(<ScalingDiv/>);
expect(container.firstChild).toHaveAttribute('size', 42);
});
But I haven't found any combination of matchers and queries that returns ANY attributes. Then I tried to just check the style directly using jest-dom, but toHaveStyle('this_isnt_valid_css: 199') passes- I couldn't stick anything in there to get it to fail. So... what is the right way to do this?
It's not attribute but prop. So we can refer to prop() and props() methods like
expect(container.firstChild.prop('size')).toEqual(42);
or
expect(container.firstChild.props()).toEqual({
size: 42
});

Syncing a paper-input with firebase using polymer

How is this for a solution for syning the data from a paper input to a firebase database.
properties: {
teamid: {
type: String,
value: null
},
formid: {
type: String,
value: null
},
metaName: {
type: String,
value: null,
observer: '_updateMetaName'
}
},
_updateMetaName: function(metaName) {
var path = 'formModel/' + this.teamid + '/' + this.formid + '/meta/name';
firebase.database().ref(path).set(metaName);
},
The data metaName comes from a a paper-input element
<paper-input value="{{metaName}}"></paper-input>
I'm using an observer over the on-change attribute because I hate the idea that a user must move out of an input for it to persist.
I've also chosen not to use PolymerFire because i dosen't have some features I need and its not production ready.
I also don't like the idea that the observer runs multiple times before any data has been changed. And that should, i thought, break it but its working to my surprise.
What other options do I have?
Are their any disadvantages to my current solution?
One disadvantage is that every keystroke fires off a request to Firebase, which could be inefficient (a waste of CPU and bandwidth).
To address this, you could debounce the callback with this.debounce(jobName, callback, wait), as shown in the following demo.
HTMLImports.whenReady(_ => {
"use strict";
Polymer({
is: 'x-foo',
properties : {
metaName: {
type: String,
value: 'Hello world!',
observer: '_metaNameChanged'
}
},
_setFirebaseMetaName: function(metaName) {
var path = 'formModel/' + this.teamid + '/' + this.formid + '/meta/name';
//firebase.database().ref(path).set(metaName);
console.log('metaName', metaName);
},
_metaNameChanged: function(metaName) {
this.debounce('keyDebouncer',
_ => this._setFirebaseMetaName(metaName),
500);
}
});
});
<head>
<base href="https://polygit.org/polymer+1.5.0/components/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link rel="import" href="paper-input/paper-input.html">
</head>
<body>
<x-foo></x-foo>
<dom-module id="x-foo">
<template>
<paper-input label="Meta Name" value="{{metaName}}"></paper-input>
</template>
</dom-module>
</body>
codepen
I've decided to go with on-keyup="_updateViewDesc" to stop a error occurring when multiple clients have the same page open. Using observers, when some data updates, it triggers the observer on all the connected clients. Causing characters to go missing.

What is the best way to declare global variables in Vue.js?

I need access to my hostname variable in every component.
Is it a good idea to put it inside data?
Am I right in understanding that if I do so, I will able to call it everywhere with this.hostname?
As you need access to your hostname variable in every component, and to change it to localhost while in development mode, or to production hostname when in production mode, you can define this variable in the prototype.
Like this:
Vue.prototype.$hostname = 'http://localhost:3000'
And $hostname will be available in all Vue instances:
new Vue({
beforeCreate: function () {
console.log(this.$hostname)
}
})
In my case, to automatically change from development to production, I've defined the $hostname prototype according to a Vue production tip variable in the file where I instantiated the Vue.
Like this:
Vue.config.productionTip = false
Vue.prototype.$hostname = (Vue.config.productionTip) ? 'https://hostname' : 'http://localhost:3000'
An example can be found in the docs:
Documentation on Adding Instance Properties
More about production tip config can be found here:
Vue documentation for production tip
a vue3 replacement of this answer:
// Vue3
const app = Vue.createApp({})
app.config.globalProperties.$hostname = 'http://localhost:3000'
app.component('a-child-component', {
mounted() {
console.log(this.$hostname) // 'http://localhost:3000'
}
})
Warning: The following answer is using Vue 1.x. The twoWay data mutation is removed from Vue 2.x (fortunately!).
In case of "global" variables—that are attached to the global object, which is the window object in web browsers—the most reliable way to declare the variable is to set it on the global object explicitly:
window.hostname = 'foo';
However form Vue's hierarchy perspective (the root view Model and nested components) the data can be passed downwards (and can be mutated upwards if twoWay binding is specified).
For instance if the root viewModel has a hostname data, the value can be bound to a nested component with v-bind directive as v-bind:hostname="hostname" or in short :hostname="hostname".
And within the component the bound value can be accessed through component's props property.
Eventually the data will be proxied to this.hostname and can be used inside the current Vue instance if needed.
var theGrandChild = Vue.extend({
template: '<h3>The nested component has also a "{{foo}}" and a "{{bar}}"</h3>',
props: ['foo', 'bar']
});
var theChild = Vue.extend({
template: '<h2>My awesome component has a "{{foo}}"</h2> \
<the-grandchild :foo="foo" :bar="bar"></the-grandchild>',
props: ['foo'],
data: function() {
return {
bar: 'bar'
};
},
components: {
'the-grandchild': theGrandChild
}
});
// the root view model
new Vue({
el: 'body',
data: {
foo: 'foo'
},
components: {
'the-child': theChild
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.16/vue.js"></script>
<h1>The root view model has a "{{foo}}"</h1>
<the-child :foo="foo"></the-child>
In cases that we need to mutate the parent's data upwards, we can add a .sync modifier to our binding declaration like :foo.sync="foo" and specify that the given 'props' is supposed to be a twoWay bound data.
Hence by mutating the data in a component, the parent's data would be changed respectively.
For instance:
var theGrandChild = Vue.extend({
template: '<h3>The nested component has also a "{{foo}}" and a "{{bar}}"</h3> \
<input v-model="foo" type="text">',
props: {
'foo': {
twoWay: true
},
'bar': {}
}
});
var theChild = Vue.extend({
template: '<h2>My awesome component has a "{{foo}}"</h2> \
<the-grandchild :foo.sync="foo" :bar="bar"></the-grandchild>',
props: {
'foo': {
twoWay: true
}
},
data: function() {
return { bar: 'bar' };
},
components: {
'the-grandchild': theGrandChild
}
});
// the root view model
new Vue({
el: 'body',
data: {
foo: 'foo'
},
components: {
'the-child': theChild
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.16/vue.js"></script>
<h1>The root view model has a "{{foo}}"</h1>
<the-child :foo.sync="foo"></the-child>
I strongly recommend taking a look at Vuex, it is made for globally accessible data in Vue.
If you only need a few base variables that will never be modified, I would use ES6 imports:
// config.js
export default {
hostname: 'myhostname'
}
// .vue file
import config from 'config.js'
console.log(config.hostname)
You could also import a json file in the same way, which can be edited by people without code knowledge or imported into SASS.
For any Single File Component users, here is how I set up global variable(s)
Assuming you are using Vue-Cli's webpack template
Declare your variable(s) in somewhere variable.js
const shallWeUseVuex = false;
Export it in variable.js
module.exports = { shallWeUseVuex : shallWeUseVuex };
Require and assign it in your vue file
export default {
data() {
return {
shallWeUseVuex: require('../../variable.js')
};
}
}
Ref: https://v2.vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch
In vue cli-3 You can define the variable in main.js like
window.basurl="http://localhost:8000/";
And you can also access this variable in any component by using
the the
window.basurl
A possibility is to declare the variable at the index.html because it is really global. It can be done adding a javascript method to return the value of the variable, and it will be READ ONLY.
An example of this solution can be found at this answer:
https://stackoverflow.com/a/62485644/1178478

Resources