I'm very new to Grails but have been using Ruby on Rails for the past few months, I can't seem to get my head around how to correctly Mock some functionality within a Service I have so I can properly Unit Test it.
I have a RestService which uses a RestBuilder plugin
import javax.persistence.Converter;
import org.codehaus.groovy.grails.web.json.JSONArray
import org.json.JSONObject
import grails.converters.JSON
import grails.plugins.rest.client.RestBuilder
import grails.transaction.Transactional
#Transactional
class RestService {
def retrieveFromRESTClient(url) {
System.properties.putAll( ["http.proxyHost":"some.proxy.host.com", "http.proxyPort":"8080", "https.proxyHost":"some.proxy.host.com", "https.proxyPort":"8080"] )
def restBuilder = new RestBuilder()
def clientResponse = restBuilder.get(url)
// For development purposes
print "retrieveFromRESTClient: " + clientResponse.json
return clientResponse
}
}
I'm attempting to write a Unit Test for retrieveFromRESTClient() and my thoughts are I should be Mocking the restBuilder.get() plugin call so it doesn't go off and actually do a get request to a URL during the Test. I've attempted a few things already such as extracting the plugin functionality to it's own method:
def retrieveFromRESTClient(url) {
System.properties.putAll( ["http.proxyHost":"some.proxy.host.com", "http.proxyPort":"8080", "https.proxyHost":"some.proxy.host.com", "https.proxyPort":"8080"] )
def clientResponse = getResponse(url)
// For development purposes
print "retrieveFromRESTClient: " + clientResponse.json
return clientResponse
}
def getResponse(url) {
def restBuilder = new RestBuilder()
def resp = restBuilder.get(url)
resp
}
and in my RestServiceSpec attempting to mock getResponse
import org.springframework.http.ResponseEntity;
import grails.plugins.rest.client.RestBuilder;
import grails.test.mixin.TestFor
import groovy.mock.interceptor.MockFor
import spock.lang.Specification
#TestFor(RestService)
class RestServiceSpec extends Specification {
def cleanup() {
}
void "retrieveFromRESTClient's responses JSON can be accessed"() {
when:
service.metaClass.getResponse { ResponseEntity<String> foo -> return new ResponseEntity(OK) }
def resp = service.retrieveFromRESTClient("http://mocking.so.it.doesnt.matter/")
then:
print resp.dump()
assert resp.json == [:]
}
}
Although this test passes, when I look at resp.dump() in the test-reports I see it's still gone and made a request to 'mocking.so.it.doesnt.matter' and returned that object instead of the mocked ResponseEntity which I assumed it would return.
test-report output:
retrieveFromRESTClient: [:]<grails.plugins.rest.client.RestResponse#433b546f responseEntity=<404 Not Found,<HTML><HEAD>
<TITLE>Network Error</TITLE>
<style>
body { font-family: sans-serif, Arial, Helvetica, Courier; font-size: 13px; background: #eeeeff;color: #000044 }
li, td{font-family: Arial, Helvetica, sans-serif; font-size: 12px}
hr {color: #3333cc; width=600; text-align=center}
{color: #ffff00}
text {color: #226600}
a{color: #116600}
</style>
</HEAD>
<BODY>
<big><strong></strong></big><BR>
<blockquote>
<TABLE border=0 cellPadding=1 width="80%">
<TR><TD>
<big>Network Error (dns_unresolved_hostname)</big>
<BR>
<BR>
</TD></TR>
<TR><TD>
RYLCR-BC-20
</TD></TR>
<TR><TD>
Your requested host "mocking.so.it.doesnt.matter" could not be resolved by DNS.
</TD></TR>
<TR><TD>
</TD></TR>
<TR><TD>
<BR>
</TD></TR>
</TABLE>
</blockquote>
</BODY></HTML>
,{Cache-Control=[no-cache], Pragma=[no-cache], Content-Type=[text/html; charset=utf-8], Proxy-Connection=[close], Connection=[close], Content-Length=[784]}> encoding=UTF-8 $json=[:] $xml=null $text=null>
My end goal is to bypass the plugins get call and return a ResponseEntity object. I'm not really sure if I'm using the correct approach for this?
In your unit test, you mocked a method that is expecting a
ResponseEntity<String> foo
as parameter.
But then you call :
service.retrieveFromRESTClient("http://mocking.so.it.doesnt.matter/")
with a parameter type of String. So your mocked method is not being called (method signatures differs).
Change your mocked method to :
service.metaClass.getResponse { String foo -> return new ResponseEntity(OK) }
and it should work.
Related
all!
I am developing an application which receives data from a temperature sensor and prints it dynamically in a flask-served webpage at localhost:5000.
To receive the data from the temperature sensor, an udp server is employed. The receive function is implemented inside a thread. To "real-time" print data to the webpage, socketio is employed - it all runs inside "app.py".
The problem which I am facing is the slowness to print data in the webpage. To test my code I developed an udp client (client.py) which connects to my app.py and, via user input, sends data. What I am observing is that the socket receives the data (I am able to print everything I send in client.py and everything I receive in app.py) but something between my socketio emit function and the website feedback makes this update process slow.
Am I missing something? Could you guys help me out discovering why is this process so slow in my code?
All help is much appreciated!! Many thanks!!
app.py
from flask import Flask, render_template, flash, url_for, redirect, request
from flask_sqlalchemy import SQLAlchemy
from flask_socketio import SocketIO, emit
import socket
import threading
HOST = ''
PORT = 3456
udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
orig = (HOST, PORT)
udp.bind(orig)
app = Flask(__name__)
app.config ['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///measurements.sqlite3'
app.config ['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SECRET_KEY'] = "secret!"
async_mode = None
socketio = SocketIO(app, async_mode=async_mode)
def thread_function():
last_temp = 0
while True:
temperatura, client = udp.recvfrom(5)
temp = float(temperatura)
print("Temp: " + str(temp))
print("Last temp: " + str(last_temp))
if (temp != last_temp):
socketio.emit('my_response', {'temp': temp})
print("I am here!")
last_temp = temp
udp.close()
receive_thread = threading.Thread(target=thread_function)
receive_thread.start()
#app.route('/')
def home():
# Home acts as a dashboard for the page
return render_template('index.html')
if __name__ == '__main__':
socketio.run(app)
client.py
import socket
HOST = '127.0.0.1'
PORT = 3456
udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
dest = (HOST, PORT)
print('Para sair use CTRL+X\n')
msg = input()
while msg != '\n':
udp.sendto(bytes(msg, "utf-8"), dest)
msg = input()
udp.close()
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Temperature sense</title>
<style>
img {
display: block;
margin-left: auto;
margin-right: auto;
}
button {
background-color: #4180a0;
border: none;
border-radius: 8px;
color: white;
<!--- padding: 32px 64px; --->
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
<!--- margin: 4px 2px; --->
cursor: pointer;
width: 128px;
height: 64px;
}
button.button:hover{
background-color: #2a2349;
}
div {
border: 1px;
border-style: solid;
border-color: #bebbb2;
padding: 8px;
}
measurementPrint{
border: 1px;
border-style: solid;
border-color: #bebbb2;
padding: 8px;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js" integrity="sha512-bLT0Qm9VnAYZDflyKcBaQ2gg0hSYNQrJ8RilYldYQ1FxQYoCLtUjuuRuZo+fjqhx/qtq/1itJ0C2ejDxltZVFg==" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/3.0.4/socket.io.js" integrity="sha512-aMGMvNYu8Ue4G+fHa359jcPb1u+ytAF+P2SCb+PxrjCdO3n3ZTxJ30zuH39rimUggmTwmh2u7wvQsDTHESnmfQ==" crossorigin="anonymous"></script>
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
// Connect to the Socket.IO server.
// The connection URL has the following format, relative to the current page:
// http[s]://<domain>:<port>[/<namespace>]
var socket = io();
// Event handler for server sent data.
// The callback function is invoked whenever the server emits data
// to the client. The data is then displayed in the "Received"
// section of the page.
socket.on('my_response', function(msg) {
$('#log').text(msg.temp);
});
});
</script>
</head>
<body>
<div>
<img src="/static/images/mentorphotonics.jpeg" class="center" alt="Mentor Photonics" width="200" height=auto />
<p>
<a href=/ ><button class=button >Realtime</button></a>
<measurementPrint>
<b>Temperature: <span id="log"></span></b>
</measurementPrint>
</p>
<p><a href=export ><button class=button >Export</button></a></p>
<p><a href=about ><button class=button >About</button></a></p>
</div>
</body>
</html>
guys
I re-studied the example from Miguel Grinberg's tutorial at https://github.com/miguelgrinberg/Flask-SocketIO and I adjusted the code.
The main issues were:
I was not using the "thread = socketio.start_background_task(background_thread)" structed, as he used with the #socketio.event
UDP's recvfrom is blocking. To solve this issue I set a timeout "udp.settimeout(0.1)" so it becomes non-blocking. However, now you should use the try;exception pair when the timeout occurs.
Regards, Fávero
I have created a new Nativescript-Vue project but the CSS is not working, scoped and global. But inline CSS like the code blocks below are working properly.
<ActionBar style="background-color: green" flat title="Nativescript">
and
<ActionBar backgroundColor="green" flat title="Nativescript">
Any tips? TIA
Here is my main.js:
import Vue from 'nativescript-vue'
import Home from './components/Home'
import VueDevtools from 'nativescript-vue-devtools'
import FontIcon from 'nativescript-vue-fonticon'
import store from './store'
if(TNS_ENV !== 'production') {
Vue.use(VueDevtools, {
host: "192.168.42.28" // IP of the machine you are writing your code on, _not_ the Device IP your app runs on
})
}
// Prints Vue logs when --env.production is *NOT* set while building
Vue.config.silent = (TNS_ENV === 'production')
Vue.use(FontIcon, {
debug: true, // <-- Optional. Will output the css mapping to console.
paths: {
'fa': './assets/css/fontawesome.min.css',
'far': './assets/css/regular.min.css',
'fas': './assets/css/solid.min.css',
'fab': './assets/css/brands.min.css'
}
})
new Vue({
store,
render: h => h('frame', [h(Home)])
}).$start()
And here is my Home.vue
<template>
<Page>
<ActionBar style="background-color: green" flat title="Nativescript">
<ActionItem ios.position="right">
<FontIcon name="fa-shopping-bag" type="fas"/>
</ActionItem>
</ActionBar>
<StackLayout>
<Label class="message" :text="msg"/>
</StackLayout>
</Page>
</template>
<script>
export default {
data() {
return {
msg: 'Welcome to Nativescript',
}
}
}
</script>
<style scoped>
ActionBar {
background-color: #53ba82;
color: #ffffff;
}
.message {
vertical-align: center;
text-align: center;
font-size: 20;
color: #333333;
}
</style>
This issue has been fixed in #nativescript/webpack#3.0.3.
Make sure you update the webpack plugin:
ns clean
npm i --save-dev #nativescript/webpack#^3.0.3
ns run <platform>
Details in pinned issue: https://github.com/nativescript-vue/nativescript-vue/issues/715
I'm building my website with Flutter but programming for web is very new to me and I'm not quite sure I understand exactly how Cookies work.
I still need to understand what cookies are to be written where, and where do I take those cookies from.
Building the banner to manage the should be easy, and if I'm not wrong it should be the first thing that pops up in the home page.
For example Medium banner is just a dismissible banner swing the message
To make Medium work, we log user data. By using Medium, you agree to our Privacy Policy.
with a link the Privacy Policy, but it doesn't have any opt-in so it doesn't look GDPR compliant..
Here https://medium.com/#mayur_solanki/flutter-web-formerly-hummingbird-ba52f5135fb0 shows how cookies are written and read in flutter web
html.window.localStorage['key']
html.window.sessionStorage['key']
html.window.document.cookie
html.window.localStorage['local_value'] = localStorage;
html.window.sessionStorage['session_value'] = sessionStorage;
html.window.document.cookie= "username=${cookies}; expires=Thu, 18 Dec 2020 12:00:00 UTC";
As far as I understood cookies are of these types.
First-party:
To track user behavior( page visits , number of users etc..) and as I use google Analytics I do need to ask consent for these.
here Google Analytics, Flutter, cookies and the General Data Protection Regulation (GDPR) is shown how to activate/deactivate it, so I shouldn't store anything myself if I'm not wrong.
Third-party:
These would be from a YouTube linked video on my Home page for example, so I need to ask consent for these too.
Haven't checked it yet but I guess it should be similar to Google Analytics
Session cookies:
These should be used to remember Items in a shopping basket.
I shouldn't be needing these..
Persistent cookies:
These should be for keeping user's logged in.
One of the web pages is the Retailer access, which is retailer's app (the supply side of the marketplace ).
I'm using Google signing to log in users, so I should be needing these, as the login form it always presented to users when navigating to Retailer access even after they logged in.
Secure cookies:
These are for https only and used for check-out/payment pages.
In my web the retailer's app only creates products, manages workshop bookings, and handles communication with customers..
The mobile app (the demand side of the marketplace) is where all the payments are made using Stripe, so I shouldn't have to store anything on web..right?
Sorry for the long question, I hope it's clear enough.
Thank you for your help.
I basically had the problem since I am also using third-party scripts (firebase, stripe, ...) and I need the user's consent before any of those scripts are run.
I build my solution around Yett (https://github.com/elbywan/yett), which blocks scripts that are part of a previously defined blacklist. You could even implement this functionality by yourself, the author has written an interesting medium article.
In my case I only have "essential" scripts, so I built a solution where the flutter app loads only if the user consented to the all necessary scripts. But it shouldn't be too difficult to adjust this solution if one needs more fined grained control about the user's cookie settings and I added a second entry for "Analytics" as a possible starting point.
I store the user's settings in the localStorage and retrieve them directly on app start to create the blacklist and to decide whether the cookie banner should be shown.
This is my index.html.
It references the following scripts: get_consent.js, set_consent.js, init_firebase.js and load_app.js (more info on them below).
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
-->
<base href="/">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="flutter_utils">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Assigns blacklist of urls based on localStorage (must be placed before yett script) -->
<script src="get_consent.js"></script>
<!-- Yett is used to block all third-party scripts that are part of the blacklist (must be placed before all other (third-party) scripts) -->
<script src="https://unpkg.com/yett"></script>
<script src="https://js.stripe.com/v3/"></script>
<title>flutter_utils</title>
<link rel="manifest" href="manifest.json">
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<!-- The standard consent popup (hidden by default) -->
<div id="consent-popup" class="hidden consent-div">
<h2>We use cookies and other technologies</h2>
<p>This website uses cookies and similar functions to process end device information and personal data. The processing serves the integration of content, external services and elements of third parties, statistical analysis/measurement, personalized advertising and the integration of social media. Depending on the function, data may be passed on to third parties within the EU in the process. Your consent is always voluntary, not required for the use of our website and can be rejected or revoked at any time via the icon at the bottom right.
</p>
<div>
<button id="accept-btn" class="btn inline">Accept</button>
<button id="reject-btn" class="btn inline">Reject</button>
<button id="info-btn" class="btn inline">More info</button>
</div>
</div>
<!-- Detailed consent popup allows the user to control scripts by their category -->
<div id="consent-popup-details" class="hidden consent-div">
<h2>Choose what to accept</h2>
<div>
<div class="row-div">
<h3>Essential</h3>
<label class="switch">
<!-- Essentials must always be checked -->
<input id="essential-cb" type="checkbox" checked disabled=true>
<span class="slider round"></span>
</label>
</div>
<p>
Here you can find all technically necessary scripts, cookies and other elements that are necessary for the operation of the website.
</p>
</div>
<div>
<div class="row-div">
<h3>Analytics</h3>
<label class="switch">
<input id ="analytics-cb" type="checkbox">
<span class="slider round"></span>
</label>
</div>
<p>
For the site, visitors, web page views and diveerse other data are stored anonymously.
</p>
</div>
<div>
<button id="save-btn" class="btn inline">Save</button>
<button id="cancel-btn" class="btn inline">Cancel</button>
</div>
</div>
<!-- Updates localStorage with user's cookie settings -->
<script src="set_consent.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.6.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.6.1/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.6.1/firebase-firestore.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.6.1/firebase-storage.js"></script>
<!-- Initializes firebase (if user gave consent) -->
<script src="init_firebase.js"></script>
<!-- Loads flutter app (if user gave consent) -->
<script src="load_app.js"></script>
</body>
</html>
The get_consent.js is the first script, retrieves the user's settings from localStorage and also defines the yett blacklist:
const essentialCookies = ["js.stripe.com", "www.gstatic.com"];
const analyticsCookies = ["www.google-analytics.com"];
const allCookies = [...essentialCookies, ...analyticsCookies];
const consentPropertyName = "cookie_consent";
const retrieveConsentSettings = () => {
const consentJsonString = localStorage.getItem(consentPropertyName);
return JSON.parse(consentJsonString);
};
const checkConsentIsMissing = () => {
const consentObj = retrieveConsentSettings();
if (!consentObj || consentObj.length == 0) {
return true;
}
return false;
};
const consentIsMissing = checkConsentIsMissing();
var blacklist;
if (consentIsMissing) {
blacklist = allCookies;
} else {
const acceptedCookies = retrieveConsentSettings();
// Remove all script urls from blacklist that the user accepts (if all are accepted the blacklist will be empty)
blacklist = allCookies.filter( ( el ) => !acceptedCookies.includes( el ) );
}
// Yett blacklist expects list of RegExp objects
var blacklistRegEx = [];
for (let index = 0; index < blacklist.length; index++) {
const regExp = new RegExp(blacklist[index]);
blacklistRegEx.push(regExp);
}
YETT_BLACKLIST = blacklistRegEx;
set_consent.js is responsible for updating localStorage with the user's settings and also hides/shows the respective divs for the cookie consent. Usually, one could simply call window.yett.unblock() to unblock the scripts, but since their order matters I decided to simply reload the window after the localStorage was updated:
const saveToStorage = (acceptedCookies) => {
const jsonString = JSON.stringify(acceptedCookies);
localStorage.setItem(consentPropertyName, jsonString);
};
window.onload = () => {
const consentPopup = document.getElementById("consent-popup");
const consentPopupDetails = document.getElementById("consent-popup-details");
const acceptBtn = document.getElementById("accept-btn");
const moreInfoBtn = document.getElementById("info-btn");
const saveBtn = document.getElementById("save-btn");
const cancelBtn = document.getElementById("cancel-btn");
const rejectBtn = document.getElementById("reject-btn");
const acceptFn = (event) => {
const cookiesTmp = [...essentialCookies, ...analyticsCookies];
saveToStorage(cookiesTmp);
// Reload window after localStorage was updated.
// The blacklist will then only contain items the user has not yet consented to.
window.location.reload();
};
const cancelFn = (event) => {
consentPopup.classList.remove("hidden");
consentPopupDetails.classList.add("hidden");
};
const rejectFn = (event) => {
console.log("Rejected!");
// Possible To-Do: Show placeholder content if even essential scripts are rejected.
};
const showDetailsFn = () => {
consentPopup.classList.add("hidden");
consentPopupDetails.classList.remove("hidden");
};
const saveFn = (event) => {
const analyticsChecked = document.getElementById("analytics-cb").checked;
var cookiesTmp = [...essentialCookies];
if (analyticsChecked) {
cookiesTmp.push(...analyticsCookies);
}
saveToStorage(cookiesTmp);
// Reload window after localStorage was updated.
// The blacklist will then only contain items the user has not yet consented to.
window.location.reload();
};
acceptBtn.addEventListener("click", acceptFn);
moreInfoBtn.addEventListener("click", showDetailsFn);
saveBtn.addEventListener("click", saveFn);
cancelBtn.addEventListener("click", cancelFn);
rejectBtn.addEventListener("click", rejectFn);
if (consentIsMissing) {
consentPopup.classList.remove("hidden");
}
};
init_firebase.js is the usual script for initializing the service, but I only inizialize if consent was obtained:
var firebaseConfig = {
// your standard config
};
// Initialize Firebase only if user consented
if (!consentIsMissing) {
firebase.initializeApp(firebaseConfig);
}
The same logic is applied to the script load_app.js. The Flutter app is only loaded if the user consented.
One might therefore add some fallback content to the index.html which would be shown if the user rejects the necessary scripts. Depending on your use case it might also be an option load the app anyway and then differentiate within the app by accessing the user's settings from localStorage.
var serviceWorkerVersion = null;
var scriptLoaded = false;
function loadMainDartJs() {
if (scriptLoaded) {
return;
}
scriptLoaded = true;
var scriptTag = document.createElement("script");
scriptTag.src = "main.dart.js";
scriptTag.type = "application/javascript";
document.body.append(scriptTag);
}
// Load app only if user consented
if (!consentIsMissing) {
if ("serviceWorker" in navigator) {
// Service workers are supported. Use them.
window.addEventListener("load", function () {
// Wait for registration to finish before dropping the <script> tag.
// Otherwise, the browser will load the script multiple times,
// potentially different versions.
var serviceWorkerUrl =
"flutter_service_worker.js?v=" + serviceWorkerVersion;
navigator.serviceWorker.register(serviceWorkerUrl).then((reg) => {
function waitForActivation(serviceWorker) {
serviceWorker.addEventListener("statechange", () => {
if (serviceWorker.state == "activated") {
console.log("Installed new service worker.");
loadMainDartJs();
}
});
}
if (!reg.active && (reg.installing || reg.waiting)) {
// No active web worker and we have installed or are installing
// one for the first time. Simply wait for it to activate.
waitForActivation(reg.installing ?? reg.waiting);
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
// When the app updates the serviceWorkerVersion changes, so we
// need to ask the service worker to update.
console.log("New service worker available.");
reg.update();
waitForActivation(reg.installing);
} else {
// Existing service worker is still good.
console.log("Loading app from service worker.");
loadMainDartJs();
}
});
// If service worker doesn't succeed in a reasonable amount of time,
// fallback to plaint <script> tag.
setTimeout(() => {
if (!scriptLoaded) {
console.warn(
"Failed to load app from service worker. Falling back to plain <script> tag."
);
loadMainDartJs();
}
}, 4000);
});
} else {
// Service workers not supported. Just drop the <script> tag.
loadMainDartJs();
}
}
And here is my style.css:
html,
body {
height: 100%;
width: 100%;
background-color: #2d2d2d;
font-family: Arial, Helvetica, sans-serif;
}
.hidden {
display: none;
visibility: hidden;
}
.consent-div {
position: fixed;
bottom: 40px;
left: 10%;
right: 10%;
width: 80%;
padding: 14px 14px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
background-color: #eee;
border-radius: 5px;
box-shadow: 0 0 5px 5px rgba(0, 0, 0, 0.2);
}
.row-div {
display: flex;
justify-content: space-between;
align-items: center;
}
#accept-btn,
#save-btn {
background-color: #103900;
}
#reject-btn,
#cancel-btn {
background-color: #ff0000;
}
.btn {
height: 25px;
width: 140px;
background-color: #777;
border: none;
color: white;
border-radius: 5px;
cursor: pointer;
}
.inline {
display: inline-block;
margin-right: 5px;
}
h2 {
margin-block-start: 0.5em;
margin-block-end: 0em;
}
h3 {
margin-block-start: 0.5em;
margin-block-end: 0em;
}
/* The switch - the box around the slider */
.switch {
position: relative;
display: inline-block;
width: 50px;
height: 25px;
}
/* Hide default HTML checkbox */
.switch input {
opacity: 0;
width: 0;
height: 0;
}
/* The slider */
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
}
.slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 4px;
bottom: 4px;
background-color: white;
}
input:checked + .slider {
background-color: #2196f3;
}
input:focus + .slider {
box-shadow: 0 0 1px #2196f3;
}
input:checked + .slider:before {
-webkit-transform: translateX(24px);
-ms-transform: translateX(24px);
transform: translateX(24px);
}
/* Rounded sliders */
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}
I have a service API that pulls data from Django-REST. The returned JSON looks like:
[
{
"manufacturer": "Mfg",
"model": "Model",
},
{
"manufacturer": "Mfg2",
"model": "Model2",
}
]
The service API function getData returns:
return this.httpClient.get(`${this.apiURL}/data/`);
Note I am using ListAPIView in Django, and I have verified that the above URL returns the JSON object shown above (though I have to add ?format=json to get the raw data outside of Django's APIView).
I then have an angular component that calls the service API function to convert the observable to an array of objects:
export class Data implements OnInit {
private records = Array<object> = [];
...
constructor(private apiService: ApiService) {}
ngOnInit() {
this.getData();
}
public getData() {
this.apiService.getData().subscribe((data: Array<object>) => {this.records = data});
There are no error or warnings, but in the component's HTML file when I try to access the record array it always has a length of 0. For example, the following will print "0" and an empty table.
<P> {{ records.length }}</P>
<table>
<tr>
<th>Manufacturer</th>
</tr>
<tr *ngFor="let record of records">
<td> {{ record.manufacturer }} </td>
</tr>
</table>
What am I missing?
If you define a model using an interface, and then use that interface as a generic parameter to the get call, the HttpClient will automatically map the response to the defined structure.
For example:
Interface
export interface Product {
manufacturer: string;
model: string;
}
Data Service
return this.httpClient.get<Product[]>(`${this.apiURL}/data/`);
Component
private records: Product[];
public getData() {
this.apiService.getData().subscribe((data: Product[]) => this.records = data);
}
EDIT: If you want to add a console.log to the above, you need to add it like this:
public getData() {
this.apiService.getData().subscribe((data: Product[]) => {
this.records = data;
console.log(this.records);
);
}
Template
<div *ngIf="records">
<p> {{ records.length }}</p>
...
</div>
When the template first appears, the async data retrieval process may not yet have finished. So by putting an ngIf around the page, it won't attempt to display the elements until the data is retrieved.
you need to add the async pipe to it.
<tr *ngFor="let record of records|async">
<td> {{ record.manufacturer }} </td>
</tr>
I'm attempting to simulate a button click with Enzyme. I've been able to write simple tests if an element renders, however the fun tests such as button clicks etc. are falling short.
In this example the error in terminal is:
1) Front End #Profile Component clicks a button :
Error: This method is only meant to be run on single node. 0 found instead.
at ShallowWrapper.single (node_modules/enzyme/build/ShallowWrapper.js:1093:17)
at ShallowWrapper.props (node_modules/enzyme/build/ShallowWrapper.js:532:21)
at ShallowWrapper.prop (node_modules/enzyme/build/ShallowWrapper.js:732:21)
at ShallowWrapper.simulate (node_modules/enzyme/build/ShallowWrapper.js:505:28)
at Context.<anonymous> (cmpnt-profile.spec.js:36:32)
the unit test is:
describe('Front End #Profile Component', () => {
const wrapper = shallow(<Profile/>);
...(other tests here)...
it('clicks a button ', () => {
wrapper.find('button').simulate('click');
expect(onButtonClick.calledOnce).to.equal(true);
})
});
the component is:
import _ from 'lodash';
import React, { Component, PropTypes } from 'react';
import { reduxForm } from 'redux-form';
import ExpireAlert from '../components/alert';
import SocialAccount from '../components/socialAccounts'
const FIELDS = {
name : {
type : 'input',
label : 'name'
},
username : {
type : 'input',
label: 'username'
},
email : {
type : 'input',
label: 'email'
}
};
let alert = false;
export default class Profile extends Component {
componentWillReceiveProps(nextProps){
if(nextProps.userIsUpdated){
alert = !alert
}
}
handleSubmit(userData) { // update profile
userData.id = this.props.userInfo.id
this.props.updateUserInfo(userData)
}
renderField(fieldConfig, field) { // one helper per ea field declared
const fieldHelper = this.props.fields[field];
return (
<label>{fieldConfig.label}
<fieldConfig.type type="text" placeholder={fieldConfig.label} {...fieldHelper}/>
{fieldHelper.touched && fieldHelper.error && <div>{fieldHelper.error}</div>}
</label>
);
}
render() {
const {resetForm, handleSubmit, submitting, initialValues} = this.props;
return (
<div className="login">
<div className="row">
<div className="small-12 large-7 large-centered columns">
<div className="component-wrapper">
<ExpireAlert
set={this.props.userIsUpdated}
reset={this.props.resetAlert}
status="success"
delay={3000}>
<strong> That was a splendid update! </strong>
</ExpireAlert>
<h3>Your Profile</h3>
<SocialAccount
userInfo={this.props.userInfo}
unlinkSocialAcc={this.props.unlinkSocialAcc}
/>
<form className="profile-form" onSubmit={this.props.handleSubmit(this.handleSubmit.bind(this))}>
<div className="row">
<div className="small-12 large-4 columns">
<img className="image" src={this.props.userInfo.img_url}/>
</div>
<div className="small-12 large-8 columns">
{_.map(FIELDS, this.renderField.bind(this))}
</div>
<div className="small-12 columns">
<button type="submit" className="primary button expanded" disabled={submitting}>
{submitting ? <i/> : <i/>} Update
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
);
}
}
const validate = values => {
const errors = {}
if (!values.name) {
errors.name = 'Name is Required'
}
if (!values.username) {
errors.username = 'Username is Required'
} else if (values.username.length > 30) {
errors.username = 'Must be 30 characters or less'
}
if (!values.email) {
errors.email = 'Email is Required'
} else if (!/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {
errors.email = 'Invalid email address'
}
return errors;
}
Profile.propTypes = {
fields: PropTypes.object.isRequired,
handleSubmit: PropTypes.func.isRequired,
resetForm: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired
}
export default reduxForm({
form: 'Profile',
fields: _.keys(FIELDS),
validate
})(Profile)
Any suggestions appreciated. I would love to make some amazing unit tests!
ZekeDroid is correct. Another way to handle the situation though would be to use mount, which is Enzyme's way to deep render components, meaning the entire tree of components are rendered. This would be considered an integration test, instead of a unit test. ZekeDroid mentions "This assumes your component can handle not receiving whatever reduxForm supplies it with, which is good practice anyhow as you will find it easier to test.", and I believe he's referencing unit testing. Ideally, your tests should include both unit tests and integration tests. I created a simple project to show how to do both unit and integration tests with redux-form. See this repo.
You have two export defaults on your file. This will naturally mean that the second one is overriding the first. If you really want to be able to export the reduxForm version and the component itself, as you should for testing, then remove the word default from the component.
On your test, you shallow render, meaning no children are rendered. Since you import the reduxForm, nothing much will get rendered, definitely not your Profile. Therefore, if you removed the default keyword like I mentioned, your test will start working when you import it using a named import:
import { Profile } from 'path/to/component';
*This assumes your component can handle not receiving whatever reduxForm supplies it with, which is good practice anyhow as you will find it easier to test.