How to test await block logic in Svelte with Jest - asynchronous

This is my first attempt at writing unit tests for a Svelte app. The below example is based on the standard Svelte template with an additional element added. In this element I conditionally render some text with await block logic.
<script land="ts">
import { asyncthing } from "./asyncthing";
import { onMount } from "svelte";
export let name: string;
let isReady: Promise<boolean> = asyncthing().then((resolveTo) => {
console.log("asyncthing resolved");
return resolveTo;
});
</script>
<main>
<h1 data-testid="greeting">Hello {name}!</h1>
<p>
Visit the Svelte tutorial to learn
how to build Svelte apps.
</p>
<p data-testid="async">
{#await isReady}Awaiting...{:then value}Resolved to: {value}{/await}
</p>
</main>
<style>
main {
text-align: center;
padding: 1em;
max-width: 240px;
margin: 0 auto;
}
h1 {
color: #ff3e00;
text-transform: uppercase;
font-size: 4em;
font-weight: 100;
}
#media (min-width: 640px) {
main {
max-width: none;
}
}
</style>
The function asyncthing returns a Promise<boolean> that resolves after 5 seconds with true.
export const asyncthing = async () => {
console.log("asyncthing() called");
return new Promise<boolean>((resolve) => {
setTimeout(() => {
console.log("timeout expired");
resolve(true);
}, 5000);
});
};
Opening the app in a browser works as expected, but the test below fails.
import App from "./App.svelte";
import { render } from "#testing-library/svelte";
import { tick } from "svelte";
import * as asyncthings from "./asyncthing";
test("messing with async", async () => {
jest.setTimeout(20000);
jest.useFakeTimers();
jest.spyOn(global, "setTimeout");
const asyncthingMock = jest.spyOn(asyncthings, "asyncthing");
const app = render(App, { name: "Everybody" });
expect(app.getByTestId("greeting")).toBeInTheDocument();
expect(app.getByTestId("greeting")).toHaveTextContent("Hello Everybody!");
expect(app.getByTestId("async")).toBeInTheDocument();
expect(app.getByTestId("async")).toHaveTextContent("Awaiting...");
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 5000);
jest.runAllTimers();
await tick();
expect(app.getByTestId("async")).toHaveTextContent("Resolved to: true");
});
The log statements are printed, so the promise does seem to resolve. However, according to Jest the text is not updated. I'm sure I must be doing something wrong, so please, somebody set me straight.

Related

Nuxt 3 with Vuetify 3 and SSR reactivity

I am trying to use Nuxt 3 together with Vuetify 3 in SSR mode. I face a problem using display's breakpoints. What is more, this functionality works with Nuxt 2 and Vuetify 2.
The code below shows only div element with red background instead of green, although the screen size is large. The reason is that the initial DOM rendering, which happens on the server side, assumes that the screen's size is small. The hydration on the client side somehow doesn't take into account, that the real size is large, although you can see in the browser's web inspector a log information result green.
<template>
<div>
<div :class="divClass">Reactivity</div>
</div>
</template>
<script setup>
import { computed, ref } from 'vue'
import { useDisplay } from 'vuetify'
const counter = ref(1)
const { lgAndUp } = useDisplay()
const divClass = computed(() => {
const result = lgAndUp.value ? 'green' : 'red'
console.log('result', result)
return result
})
</script>
<style>
.green {
background-color: green;
}
.red {
background-color: red;
}
</style>
This seems like a bug, but maybe I've done some silly mistake here. Could you look at this and verify? Thanks in advance :)
The project sources can be found on GitHub
You could use ref property and watch the lgAndUp value to update it :
<template>
<div>
<div :class="divClass">Reactivity</div>
</div>
</template>
<script setup>
import { ref, watch } from 'vue';
import { useDisplay } from 'vuetify';
const { lgAndUp } = useDisplay();
const divClass = ref('');
watch(lgAndUp, (val) => {
console.log(val);
divClass.value = val ? 'green' : 'red';
},{immediate:true});
</script>
<style>
.green {
background-color: green;
}
.red {
background-color: red;
}
</style>
DEMO

How do I render a native web component with Deno?

I have a native web component (no, not react component)...that I would like to render server side (ssr) in Deno.
Here is the web component:
import { api } from '/global.js';
class StatsBar extends HTMLElement {
constructor() {
super();
this.stats = {};
}
async getStats() {
const res = await fetch(`${api}/stats`);
const data = res.ok && await res.json();
this.stats = data;
}
async connectedCallback() {
await this.getStats();
this.innerHTML = `
<style>
.stats {
margin: 2rem 0;
display: block;
width: 100%;
text-align: center;
padding: 1rem;
}
</style>
<div class="stats">
Read ${this.stats.urls.count.toLocaleString()} articles on ${this.stats.tags.count.toLocaleString()} topics by ${this.stats.visits.count.toLocaleString()} visitors so far...
</div>
`;
}
}
customElements.define("stats-bar", StatsBar);
export default StatsBar;
It makes an api to get data, this all works fine client side in browsers, but then google can't crawl it becuase google still doesn't do well with javascript.
I want to see if its possible (With Deno and Oak) to render this component server side.

Unable to override SSR styling of styled components from packages

I am working on a project where we make use of Styled Components with NextJS for server side rendering. We use a private package which we include in multiple custom projects. This private package contains styling, but in some projects we want to override the styling with our custom styling as can be seen in the code below. It seems like it has something to do with the component selector ${H1} not being able to find the actual H1 component. Why is this happening?
Code in private package:
const baseHeading = css`
font-family: ${(props) => props.theme.fonts.sec};
display: block;
line-height: 1.3;
font-weight: bold;
`;
export const H1Styles = css`
font-size: ${(props) => props.theme.fonts.content.headings.h1.b1};
text-transform: uppercase;
#media screen and (min-width: ${size.min.b4}) {
font-size: ${(props) => props.theme.fonts.content.headings.h1.b4};
}
`;
export const H1 = styled.h1`
${baseHeading};
${H1Styles};
`;
Code in custom component:
import { H1 } from '#testproject/testproject-core-ui'
....
<section className={className}>
<FooterBrandingInner>
<H1>
<span>{params.LeftColumnTitleRow1}</span>
<span>{params.LeftColumnTitleRow2}</span>
</H1>
....
const FooterBrandingInner = styled.div`
${H1} {
font-size: 3.8rem;
text-transform: none;
line-height: 1.2;
}
....
Code in _document.tsx
static async getInitialProps(ctx: any) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App: any) => (props: any) => sheet.collectStyles(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
};
} finally {
sheet.seal();
}
}
Code in .babelrc
[
"babel-plugin-styled-components",
{
"ssr": true,
"displayName": true,
"preprocess": false
}
]
Expected Behavior
I would expect to see the H1 without 'text-transform: uppercase' when server side rendering.
Actual Behavior
When we visit a page with the custom styling which is defined in our custom class, we will not see it while server side rendering. We do see it when we are not server side rendering. We do sometimes see a flash of the custom styling and it seems that the wrong ClassNames are being used on the component.

Having trouble getting rid of the blue highlight

I've been working on a section with expandable/collapsible sections. When I click on a section to expand or collapse it, a blue focus area shows up but it is placed on a weird angle. I don't know what is causing it and would like a solution to either get rid of it or place it back at the normal horizontal angle. Does anybody have any suggestions as to how to fix this?
I am using a Macbook and Chrome browser.
The entire grey block that this component appears in is placed at an angle as you can see from the top of the image attached below but in the reverse direction from the highlighted focus area.
My css:
#import '../../theme/variables.css';
.rotatedSection {
padding-bottom: 2rem;
}
.container {
max-width: 64rem;
margin: 0 auto;
display: flex;
padding: 2rem 0;
#media screen and (max-width: 68rem) {
margin: 0 3rem;
}
}
.accordianContainer {
flex: 1;
margin-right: 2rem;
min-width: 500px;
#media screen and (max-width: $tablet-lg-max-width) {
margin-right: 0;
}
#media screen and (max-width: 900px) {
min-width: 0;
}
}
.imageContainer {
flex: 1;
margin-left: 2rem;
max-height: 300px;
display: flex;
justify-content: center;
img {
flex: 1;
}
#media screen and (max-width: $tablet-lg-max-width) {
margin-left: 0;
}
}
.heading {
composes: h2 from 'theme/text';
margin-left: auto;
margin-right: auto;
}
My react code:
import React, {Component, PropTypes} from 'react';
import RotatedSection from 'components/RotatedSection';
import AccordionItem from './AccordionItem';
import css from './styles.css';
class AccordionSectionWithImage extends Component {
constructor (props) {
super(props);
this.state = {
activeIndex: null,
};
this.onOpen = this.onOpen.bind(this);
this.onClose = this.onClose.bind(this);
this.setActive = this.setActive.bind(this);
this.handleClickOutside = this.handleClickOutside.bind(this);
}
onOpen = (index) => {
this.setActive(index);
};
onClose = (callback = () => null) => {
this.setActive(null);
callback();
};
setActive = (activeIndex) => this.setState({activeIndex});
handleClickOutside = () => this.props.collapseOnBlur && this.onClose();
render () {
const {
entry: {
items,
heading,
image,
},
showIndex,
classNames,
meta = {},
} = this.props;
const {routeParams, toggleHamburger} = meta;
const {activeIndex} = this.state;
return (
<RotatedSection color='whisper' className={css.rotatedSection}>
<div className={css.container}>
<div className={css.accordianContainer}>
<h2 className={css.heading}>{heading}</h2>
{items && items.map((item, index) => (
<AccordionItem
key={index}
showIndex={showIndex}
entry={item}
meta={{
position: index,
isOpen: (index === activeIndex),
onOpen: () => this.onOpen(index),
onClose: () => this.onClose(),
onChildClick: () => this.onClose(toggleHamburger),
routeParams,
}}
classNames={classNames}
/>
))}
</div>
<div className={css.imageContainer}>
<img src={image && image.fields && image.fields.file.url} alt='Educational assessment' />
</div>
</div>
</RotatedSection>
);
}
}
AccordionSectionWithImage.propTypes = {
meta: PropTypes.object,
entry: PropTypes.object,
collapseOnBlur: PropTypes.bool,
showIndex: PropTypes.bool,
classNames: PropTypes.object,
};
export default AccordionSectionWithImage;
React component for individual section:
function AccordionItem (props) {
const {
meta: {
isOpen,
onOpen,
onClose,
},
entry: {
heading,
text,
},
} = props;
const handleClick = () => (isOpen ? onClose() : onOpen());
return (
<div className={css.itemContainer}>
<div className={css.innerContainer}>
<h3 className={css.heading} onClick={handleClick}>
<span className={css.titleText}>{heading}</span>
<i className={`zmdi zmdi-plus ${css.titleToggle}`} />
</h3>
{isOpen && (
<div className={css.contents}>
{text}
</div>
)}
</div>
</div>
);
}
For anybody else experiencing a similar problem:
Problem only appeared on mobile phones and the device mode of chrome inspector. It was due to the tap-highlight property.
Setting -webkit-tap-highlight-color to rgba(0,0,0,0) hid the problem but it's a non standard css property so the solution may not work for all devices/browsers/users.

Vue.js - cannot add click event

I'm trying to add click event listener for DOM. All the scripts are bundled with webpack which compiles it to 1 large file.
This is my component:
<template>
<div class="formchat-window">
<a>test</a>
<div class="title" v-on:click.self="toggle">NevĂ­te si rady? Zeptejte se!</div>
<wp-formchat-screen v-if="windowActive"></wp-formchat-screen>
</div>
</template>
<script>
export default {
mounted () {
console.log('mounted');
},
methods: {
toggle(event) {
alert('click on toggle');
this.windowActive = !this.windowActive;
}
},
data() {
return {
windowActive: false,
};
}
}
</script>
<style lang="scss">
$color: red;
.formchat-window {
position: fixed;
right: 10%;
bottom: 0;
width: 300px;
z-index: 9999;
.title {
background: $color;
}
}
</style>
This is my main JS file:
import Vue from 'vue'
import FormChat from './FormChat'
import FormchatAnswer from './components/FormchatAnswer'
import FormchatEntry from './components/FormchatEntry'
import FormchatScreen from './components/FormchatScreen'
import FormchatWindow from './components/FormchatWindow'
window.Vue = Vue;
console.log('test');
Vue.component('WpFormchatAnswer', FormchatAnswer);
Vue.component('WpFormchatEntry', FormchatEntry);
Vue.component('WpFormchatScreen', FormchatScreen);
Vue.component('WpFormchatWindow', FormchatWindow);
const app = new Vue({
el: '#wp-formchat-vue-root',
//render: h => h(FormChat)
});
However I did everything and compiler works perfectly, I cant register the click, so the method won't trigger.
Can anyone please help me?
I have finally figured it out. In the console, there was a warning about component name and main filename. I have renamed main file and the Vue started to work again.
Beware of ambigious naming :)

Resources