How to configure Jasmine in Rails 6? - ruby-on-rails

How do I configure Jasmine in the Rails 6 environment (where Webpack replaces the asset pipeline for Javascript) so I can test the Javascript modules I've written for my app?
I installed the jasmine gem, ran rails generate jasmine:install, and edited jasmine.yml to point to the location of my Javascript source and specs.
The problem is that I can't use import/export statements. (For example, attempting to load my first module to test results in this error in Chrome: Uncaught SyntaxError: Unexpected token 'export')
From what I can tell, I need to set up Jasmine to use babel; but, I'm not having any luck finding instructions on how to do this in the new Rails 6 layout.

Yes, you're right. The main problem of jasmine-gem is that it doesn't pipe the spec through babel. Let me post the quickest solution to your problem and after that, I will think of the possible implementation of a similar approach in jasmine-gem.
The main idea is to pipe the specs through the rails webpack as long as it has all the required babel configurations.
Install jasmine-core since we will not use jasmine-gem in this solution
yarn add jasmine-core -D
Now create two additional webpack packs.
One is for Jasmine and will contain only Jasmine and the test runner
// app/javascript/packs/jasmine.js
import 'jasmine-core/lib/jasmine-core/jasmine.css'
import 'jasmine-core/lib/jasmine-core/jasmine-html.js'
import 'jasmine-core/lib/jasmine-core/boot.js'
import 'jasmine-core/images/jasmine_favicon.png'
And the second one for your application code and the specs
// app/javascript/packs/specs.js
// First load your regular JavaScript (copy all the JavaScript imports from your main pack).
let webpackContext = require.context('../javascripts', true, /\.js(\.erb)?$/)
for(let key of webpackContext.keys()) { webpackContext(key) }
// Then load the specs
let specsContext = require.context('../spec', true, /\.js(\.erb)?$/)
for(let key of specsContext.keys()) { specsContext(key) }
Pay attention to your '../javascripts' and '../spec' paths. For me it looked like '../../assets/javascripts' and '../../../spec' respectevly.
Then add the Webpack ProvidePlugin for Jasmine (add this code to config/webpack/environment.js)
// config/webpack/environment.js
const webpack = require('webpack')
environment.plugins.prepend('Provide', new webpack.ProvidePlugin({
jasmineRequire: 'jasmine-core/lib/jasmine-core/jasmine.js',
}))
Add Jasmine ranner page to your application
# config/routes.rb
Rails.application.routes.draw do
# ...
if Rails.env.development? || Rails.env.test?
get 'jasmine', to: 'jasmine#index'
end
end
# app/controllers/jasmine_controller.rb
class JasmineController < ApplicationController
layout false
def index
end
end
# app/views/jasmine/index.html.haml
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<%= stylesheet_pack_tag 'jasmine', :media => 'all' %>
</head>
<body>
<%= javascript_pack_tag 'jasmine' %>
<%= javascript_pack_tag 'specs' %>
</body>
</html>
Now your Jasmine should work on /jasmine route
This answer is prepared on the basis of this post, however, I've rechecked the instructions on ruby 2.6.3, rails 6.0.2, added appropriate changes to the recommendations and prove that this works.
Please, let me know if my answer was helpful for you or you need some additional information. However, I'm going to work on a solution that will succeed with jasmine gem or similar implementation.

Related

How to use third party plugin in rails 6? Using webpacker

How can I use the the third party plugin via webpacker. I dont want to use cloudfare external link in application.html.erb.
Why means, I just want to compile all the plugin while application loading for the initial time.
Thanks in advance :)
<!DOCTYPE html>
<html lang="en">
<head>
<title><%= yield(:title).present? ? yield(:title) : 'Tryblank' %></title>
<meta name="description" content="<%= yield(:description).present? ? yield(:description) : 'Tryblank' %>" />
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2#8"></script>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/lightgallery/1.6.12/css/lightgallery.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/lightgallery/1.6.12/js/lightgallery.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lg-thumbnail/1.1.0/lg-thumbnail.min.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.1/jquery.validate.js" ></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.7.1/css/bootstrap-datepicker3.standalone.min.css" />
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.7.1/js/bootstrap-datepicker.min.js"></script>
</head>
<body>
<%# <p class="notice"> notice</p> <p class="alert">alert</p> %>
<%= yield %>
</body>
</html>
application.js
require("#rails/ujs").start()
require("turbolinks").start()
require("#rails/activestorage").start()
require("channels")
require("jquery")
import '../stylesheets/style'
import './default.js'
import './bootstrap_custom.js'
import './side_menu.js.erb'
//import './sweetalert.js'
import './business_hours.js'
import './admin.js'
import './services.js'
import './user_service.js'
I answered a similar question here:
https://stackoverflow.com/a/58794513/12104222
There, I explained how to include jquery, bootstrap and popper.js to a project using yarn, and then two different approaches to actually importing the libraries (or modules) to use them in your app, depending on whether you need to expose the modules as a global plugin (something you probably want only for jQuery and similar global libraries) or not, as a single import (the most common option, which I exemplified with Bootstrap).
Updated with instructions for sweetalert & lightgallery, as requested by OP:
Run $ yarn sweetalert lightgallery lg-thumbnail (or whatever plugins you may need; you can also use yarn sweetalert#2.1.2, etc if you need a specific version of the plugin). That will install the modules in your node_modules directory, and will make them available to Webpacker.
You will also need $ yarn jquery to install jquery as a node module as well.
Edit your config/webpack/environment.js so it looks like this:
# Some other previous code
environment.plugins.append('Provide',
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
})
)
module.exports = environment
That will ensure jQuery is exposed to every other plugin that depends on it.
Edit app/javascript/packs/application.js so it looks like this:
require("#rails/ujs").start()
require("turbolinks").start()
require("#rails/activestorage").start()
require("channels")
//require("jquery") //commented as you loaded jQuery before.
import '../stylesheets/style'
import './default.js'
import './bootstrap_custom.js'
import './side_menu.js.erb'
import swal from 'sweetalert' //Note you don't need './' in your path if you import via yarn
import 'lightgallery'
import './business_hours.js'
import './admin.js'
import './services.js'
import './user_service.js'
Import lightGallery.css in your application.scss
Remove these lines from your view, since you will be loading them using webpack:
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/lightgallery/1.6.12/css/lightgallery.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/lightgallery/1.6.12/js/lightgallery.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lg-thumbnail/1.1.0/lg-thumbnail.min.js" type="text/javascript"></script>
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
By now, you've installed jQuery, lightgallery and sweetalert as webpack modules. I cannot guarantee, however, that this will work in your case since the code you provided looks like it's importing a lot of different plugins from many different sources (webpack, CDN, maybe also sprockets) which can (and probably will) cause conflicts between them.
If possible, I'd recommend you replace every <script> tag with the correct import method for webpack, which is just following the method I described for the plugins you mentioned in this answer, but applying them to your other libraries/plugins.
Edit 2:
Remember that, if you use Turbolinks (and it looks like you do), you need to replace this sort of code:
$(document).ready(function() {
$("#lightgallery").lightGallery();
});
With this:
$(document).on('turbolinks:load', function() {
$("#lightgallery").lightGallery();
});
Good luck!

Sass support in Rails 5.2 with webpacker 3+ directly in JS

I want to include SASS support in a brand new Rails App directly importing the .scss file on my Javascript files.
So far this is what I have done:
1- Follow https://github.com/rails/webpacker to create a new webpacker rails app with react integrations. Hello world was succesfull.
I have a entry point for Webpacker in packs folder
import ReactDOM from "react-dom";
import Hello from "../hello_world";
document.addEventListener("DOMContentLoaded", () => {
ReactDOM.render(
<Hello name="React" />,
document.body.appendChild(document.createElement("div"))
);
});
3 - My Hello world component
import React from "react";
import "./test.scss";
const Hello = props => <div className="red">Hello {props.name}!</div>;
export default Hello;
4 - My sass file
.red {
background-color: red;
}
5 - view
<%= javascript_pack_tag 'hello_world_pack' %>
6 - layout template
<html>
<head>
<title>Myapp</title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
</head>
<body>
<%= yield %>
</body>
</html>
No complaints at all but Red color is not applied. What I overlook?
NOTE: Successfully I could add SASS support but adding a my_stylehseet.scss file directly in pack folder and referencing it into my layout template with `<% = stylesheet_pack_tag 'my_stylesheet %>'. But I want that my template is stylesheet agnostic s I don't have to have severals of them.
I really think there is an more elegant way If I understood properly webpacker doc https://github.com/rails/webpacker/blob/master/docs/css.md
Maybe Am I wrong?
Just for the records the question is already answered here Rails Webpacker 3.0 import CSS file is not working and the webpacker documentation is clear about it https://github.com/rails/webpacker/blob/master/docs/css.md

How to use html templates in electron framework?

I need to build a cross platform app with multiple windows. So I would like to know how to use html templates in electron.
Based on a similar question and what I've seen, there's no built in html template language in Electron, which is actually great because it allows you to use any other template language.
I'm currently playing with ejs in Electron.
Below is my index.ejs template file:
<html lang="en">
<head>
<title>The Index Page</title>
</head>
<body>
<h1>Welcome, this is the Index page.</h1>
<% if (user) { %>
<h3>Hello there <%= user.name %></h3>
<% } %>
</body>
</html>
And below is a section of my main.js file where the above template is rendered and loaded onto the BrowserWindow. Note that I've left out most of the boilerplate code:
const ejs = require('ejs');
//... Other code
let win = new BrowserWindow({width: 800, height: 600});
//... Other code
// send the data and options to the ejs template
let data = {user: {name: "Jeff"}};
let options = {root: __dirname};
ejs.renderFile('index.ejs', data, options, function (err, str) {
if (err) {
console.log(err);
}
// Load the rendered HTML to the BrowserWindow.
win.loadURL('data:text/html;charset=utf-8,' + encodeURI(str));
});
I'll give some credit to this gist for helping me find the data:text/html;charset=utf-8 part of the url that can be used to load dynamic content.
UPDATE
I'm actually not using this anymore. It's faster to just load the default html and use the native DOM methods. The Electron Quickstart program shows how to do this nicely.
Another option is to do the templating during your build. Here is a simple example using gulp to add nonces to the CSP meta tag and the inline script.
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'nonce-<%= scriptNonce %>';">
<title>Basic Electron App</title>
</head>
<body>
<div id="app"></div>
<script type="application/javascript" nonce=<%= scriptNonce %>>
require('./index.js');
</script>
</body>
</html>
and in gulfile.js add the following to what you already have and make sure this task is included in your pipeline. You can also just update your current html task with the code below.
const template = require('gulp-template');
const uuidv4 = require('uuid/v4');
gulp.task('copy-html', () => {
// Create nonces during the build and pass them to the template for use with inline scripts and styles
const nonceData = {
scriptNonce: new Buffer(uuidv4()).toString('base64'),
styleNonce: new Buffer(uuidv4()).toString('base64')
};
return gulp.src('src/*.html')
.pipe(template(nonceData))
.pipe(gulp.dest('dist/'));
});
This is a very stripped down example. I have a more complete example at https://github.com/NFabrizio/data-entry-electron-app if anyone is interested, though there is still one warning when running the application because one of the packages I am using pulls in react-beautiful-dnd, which adds inline styles but does not currently accept nonces.

Async JavaScript in production environment only?

I'm trying to get my JavaScript (Angular app) to load asynchronously so that the page can render a loading image while the browser downloads the JavaScript later. In production, this works just fine, but not in development, because Sprockets hasn't concatinated all of the files yet.
I'm using the following in my HAML file:
= javascript_include_tag "mio", :async => true
which is working as intended in production:
<script async="async" src="/assets/mio.js"></script>
However, in development the files are all separate, and execute out of order. For example, my Angular Quote Form Controller is executing before Angular has finished loading:
<script async="async" src="/assets/angular.js?body=1"></script>
<script async="async" src="/assets/mio.js?body=1"></script>
<script async="async" src="/assets/mio-ng/controllers/quote_form.js?body=1"></script>
So the question is, can javascript_include_tag ignore the async flag when in development, but not in production?
I found a way to rig this up to work, but I feel it is not the best solution:
= javascript_include_tag "application", :async => Rails.env == "production"

Typescript AMD and AngularJs - Failed to instantiate module

I'm unsing TypeScript AMD (RequireJs) and AngularJs.
I want to use AMD for my typescript code and not for the rest: jquery, angular, bootstrap, ...
For thirdparty js I'm using MVC bundling and I want to continue this way.
This is my thirdparty bundle config:
bundles.Add(new ScriptBundle("~/bundles/thirdparty").Include(
"~/Scripts/jquery-{version}.js",
"~/Scripts/bootstrap.js",
"~/Scripts/respond.js",
"~/Scripts/require.js",
"~/Scripts/angular.js",
"~/Scripts/angular-route.js"
));
My _Layout.cshtml is something like:
<!DOCTYPE html>
<html ng-app="PafBase">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
#Scripts.Render("~/bundles/thirdparty")
<script>
require.config({
baseUrl: 'typescript/'
});
require(['module/BaseApplication']);
</script>
</head>
<body>
#RenderBody()
</body>
BaseApplication.ts is something like:
var app: ng.IModule = angular.module('PafBase', ['ngRoute']); // Breakpoint here ****
app.config(['$routeProvider', ($routeProvider) => { ... }]);
When run the application I get this javascript error:
[$injector:modulerr] Failed to instantiate module PafBase due to:
Error: [$injector:nomod] Module 'PafBase' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
If I continue the execution I can see that BaseApplication.ts is executed after the angular error.
I think it can be due to angular scan the DOM after ready and found <html ng-app="PafBase"> then search "PafBase" module and not find it due to requireJs don't load BaseApplication.ts before angular scan the DOM.
How to do angular scan the dom after my code is executed?
You can manually bootstrap Angular instead of specifying ng-app="PafBase". Take a look at the docs here: https://docs.angularjs.org/guide/bootstrap
Basically you just need to remove ng-app from your html and add this to your JS
angular.element(document).ready(function() {
angular.bootstrap(document, ['PafBase']);
});

Resources