I'm trying to get some TDD happening with Ember and Konacha - I get this error when I try to use visit
ReferenceError: visit is not defined
at Context.<anonymous> (http://localhost:3500/assets/api_spec.js?body=1:24:5)
at Test.Runnable.run (http://localhost:3500/assets/mocha.js:4336:32)
at Runner.runTest (http://localhost:3500/assets/mocha.js:4724:10)
at http://localhost:3500/assets/mocha.js:4802:12
at next (http://localhost:3500/assets/mocha.js:4649:14)
at http://localhost:3500/assets/mocha.js:4659:7
at next (http://localhost:3500/assets/mocha.js:4597:23)
at http://localhost:3500/assets/mocha.js:4621:7
at Hook.Runnable.run (http://localhost:3500/assets/mocha.js:4338:5)
at next (http://localhost:3500/assets/mocha.js:4609:10)
api_spec.rb
//= require spec_helper
describe("fake server", function() {
beforeEach(function() {
this.server = sinon.fakeServer.create();
});
afterEach(function() {
this.server.restore();
});
it("#transition off Landing", function() {
visit("/").then(function() {
var mock = sinon.mock(testHelper.lookup('route', 'index'));
mock.expects('transitionTo').once();
mock.verify();
mock.restore();
});
});
}
here's my spec_helper.rb
//= require sinon
//= require ember-mocha-adapter
//= require application
mocha.ui('bdd');
mocha.globals(['Ember', 'App', 'DS', 'MD5']);
mocha.timeout(500);
chai.Assertion.includeStack = true;
ENV =
{
TESTING:true
};
window.server = sinon.fakeServer.create();
window.testHelper = {
lookup: function(object, object_name) {
name = object_name || "main";
return App.__container__.lookup(object + ":" + name);
}
}
App.Router.reopen({
location: 'none'
});
Konacha.reset = Ember.K;
How do I make ember play nice with visit?
Cheers!
visit is only injected when you call
App.injectTestHelpers();
Additionally you must be running a debug build of Ember.
But it only works with qunit, you'll need to write your own visit if you're using a different testing framework.
Related
I try to implement ActionTest with old way asset pipeline (without Webpack) on rails 6
Almost all is good, except loading of #rails/actiontext
in my application.js I've
//= require trix
//= require #rails/actiontext
The riche editor appear, I can change bold/italic text, but can't add image (not uploaded)
I've an JS error : Uncaught SyntaxError: Cannot use import statement outside a module
on line : import { AttachmentUpload } from "./attachment_upload" from attachment_uplaod.js in actiontext.
Any way to achieve this without webpack?
thanks
I don't know what will be the official way, but I did it this way as I'm waiting for an updated install generator:
Prerequisites
hotwire-rails
CSS
Copy the CSS file in your asset pipeline (https://github.com/basecamp/trix/tree/main/dist)
JS Libraries
In app/assets/javascripts/libraries create these two files
Updated content may be found on https://www.skypack.dev
// app/assets/javascripts/libraries/actiontext#6.1.4.js
export * from 'https://cdn.skypack.dev/pin/#rails/actiontext#v6.1.4-znF92tREya92yxvegJvq/mode=imports/optimized/#rails/actiontext.js';
export { default } from 'https://cdn.skypack.dev/pin/#rails/actiontext#v6.1.4-znF92tREya92yxvegJvq/mode=imports,min/optimized/#rails/actiontext.js';
// app/assets/javascripts/libraries/trix#1.3.1.js
export * from 'https://cdn.skypack.dev/pin/trix#v1.3.1-EGGvto9zyvcAYsikgQxN/mode=imports/optimized/trix.js';
export { default } from 'https://cdn.skypack.dev/pin/trix#v1.3.1-EGGvto9zyvcAYsikgQxN/mode=imports,min/optimized/trix.js';
Import through Stimulus
In app/assets/javascripts/controllers create this file
//app/assets/javascripts/controllers/trix_controller.js
import { Controller } from "stimulus"
export default class extends Controller {
connect() {
import("actiontext").catch(err => null)
import("trix").catch(err => null)
}
}
On pages where trix/action_text should be loaded, add a data-controller="trix" to the relevant div
And voilĂ !
https://github.com/rails/rails/issues/41221#issuecomment-871853505
Got Action Text working by copying the actiontext scripts into a custom file, and removing the imports and exports.
And second, you will need to require activestorage in your application.js to make use of DirectUpload.
application.js
//= require trix
//=# require #rails/actiontext
//= require activestorage
//= require actiontext
actiontext.js
// Copied from node_modules/#rails/actiontext/app/javascript/actiontext/attachment_upload.js
class AttachmentUpload {
constructor(attachment, element) {
this.attachment = attachment;
this.element = element;
// Requires `require activestorage` in application.js
this.directUpload = new ActiveStorage.DirectUpload(
attachment.file,
this.directUploadUrl,
this
);
}
start() {
this.directUpload.create(this.directUploadDidComplete.bind(this));
}
directUploadWillStoreFileWithXHR(xhr) {
xhr.upload.addEventListener("progress", event => {
const progress = (event.loaded / event.total) * 100;
this.attachment.setUploadProgress(progress);
});
}
directUploadDidComplete(error, attributes) {
if (error) {
throw new Error(`Direct upload failed: ${error}`);
}
this.attachment.setAttributes({
sgid: attributes.attachable_sgid,
url: this.createBlobUrl(attributes.signed_id, attributes.filename)
});
}
createBlobUrl(signedId, filename) {
return this.blobUrlTemplate
.replace(":signed_id", signedId)
.replace(":filename", encodeURIComponent(filename));
}
get directUploadUrl() {
return this.element.dataset.directUploadUrl;
}
get blobUrlTemplate() {
return this.element.dataset.blobUrlTemplate;
}
}
// Copied from node_modules/#rails/actiontext/app/javascript/actiontext/index.js
addEventListener("trix-attachment-add", event => {
const { attachment, target } = event;
if (attachment.file) {
const upload = new AttachmentUpload(attachment, target);
upload.start();
}
});
This still uses ES6 syntax, so if you want to support older browsers and aren't using Babel, you might want to rewrite or transpile this to ES5.
How can I upload images in Trix editor with Rails 5.2 configured with ActiveStorage?
I saw some videos using others uploaders, but could not adapt the idea to ActiveStorage.
Other (maybe) solution is: use ActionText with Rails 5.2. Is it safe to use already?
Active Storage has direct upload js, you need just add:
//= require activestorage
to your application.js, and then create trix-attachment-add event listener:
document.addEventListener('trix-attachment-add', function (event) {
var file = event.attachment.file;
if (file) {
var upload = new window.ActiveStorage.DirectUpload(file,'/rails/active_storage/direct_uploads', window);
upload.create((error, attributes) => {
if (error) {
return false;
} else {
return event.attachment.setAttributes({
url: `/rails/active_storage/blobs/${attributes.signed_id}/${attributes.filename}`,
href: `/rails/active_storage/blobs/${attributes.signed_id}/${attributes.filename}`,
});
}
});
}
});
Hope this helps you!
Hi i have implemented action cable using rails 5. its working fine locally but its not working on heroku. on heroku whenever i send any message it sends four messages, two duplicates and 2 blank messages. here is my code :-
conversation.js
App.conversation = App.cable.subscriptions.create("ConversationChannel", {
connected: function() {},
disconnected: function() {},
received: function(data) {
var conversation = $('#conversations-list').find("[data-conversation-id='" + data['conversation_id'].$oid + "']");
if (data['window'] !== undefined) {
var conversation_visible = conversation.is(':visible');
if (conversation_visible) {
var messages_visible = (conversation).find('.panel-body').is(':visible');
if (!messages_visible) {
conversation.removeClass('panel-default').addClass('panel-success');
conversation.find('.panel-body').toggle();
}
conversation.find('.messages-list').find('ul').append(data['message']);
}
else {
$('#conversations-list').append(data['window']);
conversation = $('#conversations-list').find("[data-conversation-id='" + data['conversation_id'].$oid + "']");
conversation.find('.panel-body').toggle();
}
}
else {
conversation.find('ul').append(data['message']);
}
var messages_list = conversation.find('.messages-list');
var height = messages_list[0].scrollHeight;
messages_list.scrollTop(height);
},
speak: function(message) {
return this.perform('speak', {
message: message
});
},
});
$(document).on('submit', '.new_message', function(e) {
e.preventDefault();
var values = $(this).serializeArray();
App.conversation.speak(values);
$(this).trigger('reset');
});
Application.js
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
// vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require rails-ujs
// require turbolinks
//= require jquery-3.2.1.min
// require_tree .
(function() {
$(document).on('click', '.toggle-window', function(e) {
e.preventDefault();
var panel = $(this).parent().parent();
var messages_list = panel.find('.messages-list');
panel.find('.panel-body').toggle();
panel.attr('class', 'panel panel-default');
if (panel.find('.panel-body').is(':visible')) {
var height = messages_list[0].scrollHeight;
messages_list.scrollTop(height);
}
});
})();
Cable.js
//= require action_cable
//= require_self
//= require_tree ./channels
(function() {
this.App || (this.App = {});
App.cable = ActionCable.createConsumer();
}).call(this);
create.js
var conversations = $('#conversations-list');
var conversation = conversations.find("[data-conversation-id='" + "<%= #conversation.id %>" + "']");
if (conversation.length !== 1) {
conversations.append("<%= j(render 'conversations/conversation', conversation: #conversation, user: current_user) %>");
conversation = conversations.find("[data-conversation-id='" + "<%= #conversation.id %>" + "']");
}
conversation.find('.panel-body').show();
var messages_list = conversation.find('.messages-list');
var height = messages_list[0].scrollHeight;
messages_list.scrollTop(height);
Chat Screenshot
enter image description here
Please let me know how i can fix it. i am using rails 5 with ruby-2.4.0. i am also using redis server for jobs.
You set a Javascript event listener that has nothing to do with ActionCable.
Every time you trigger the submit bottom you call the App.conversation.speak() function that append the message on the page
$(document).on('submit', '.new_message', function(e) {
e.preventDefault();
var values = $(this).serializeArray();
App.conversation.speak(values);
$(this).trigger('reset');
});
this is your speak function
speak: function(message) {
return this.perform('speak', {
message: message
});
I quote Defining The Channel's Subscriber
We add our new subscription to our consumer with App.cable.subscriptions.create. We give this function an argument of the name of the channel to which we want to subscribe, ConversationChannel.
When this subscriptions.create function is invoked, it will invoke the ConversationChannel#subscribed method, which is in fact a callback method.
So what is a callback method? I can't answer clearly this question.
This method is responsible for subscribing to and streaming messages that are broadcast to this channel.
app/channels/conversation_channel.rb
class ConversationChannel < ApplicationCable::Channel
def subscribed
stream_from 'conversation'
end
end
that
ConversationChannel#subscribed streams from our messages broadcast, sending along any new messages as JSON to the client-side subscription function.
This is how I implement ActionCable, after the Message is saved to the db I trigger the following action in my MessagesController as in Sophie Debenedetto guide (I don't know if you save a Conversation to the DB or a Message)
app/controllers/messages_controller.rb
class MessagesController < ApplicationController
def create
message = Message.new(message_params)
message.user = current_user
if message.save
ActionCable.server.broadcast 'messages',
message: message.content,
user: message.user.username
head :ok
end
end
...
end
ActionCable.server.broadcast 'messages', sends a call to the received function inside App.cable.subscription and that function is responsible for updating the page with the new message.
This call will be performed only for the user that are subscribed to this event. The subscriptions are managed in the subscribed method of the ConversationChannel
App.conversation = App.cable.subscriptions.create('ConversationChannel', {
received: function(data) {
$('#messages').append(this.speak(data));
},
speak: function(data) {
return "<br><p> <strong>" + data.user + ": </strong>" + data.message + "</p>";
};
}
});
It passed the following data from your rails controller in the json format message: message.content, user: message.user.username
Some of this code is taken from my app https://sprachspiel.xyz that is an action cable app you can test, the app should be still working. This is the github repository
I believe you are calling your js function twice, or doing some workaround to make actioncable work that causes the div to be appended twice. I believe your are executing 2 different time js to run action cable.
Remember that action cable is a websocket meant to update that message on 2 different users/browsers
I'm trying to make a link to create new object when no results were found. I'm using Rails 4. I've included gem gem "select2-rails", added //= require select2 in application.js and
*= require select2
*= require select2-bootstrap
in application.js. Here's my coffeescript which should work fine (ajax working well) but function formatNoMatches seems to do nothing.
$('#invoice_client_id').select2
theme: 'bootstrap'
formatNoMatches: (term) ->
return "<a href='#' onclick='alert('" + term + "');'" + "id='newClient'>Add New Client</a>";
ajax:
url: '/clients.json'
dataType: 'json'
processResults: (data) ->
return {
results: $.map(data, (client, i) ->
{
id: client.id
text: client.name
}
)}
I have found in documentation no mentions of formatNoMatches but in a lot of user examples it's used to do same thing like here.
EDIT
Link or custom text doesn't appear at all.
I looked around a bit and found this way of doing it in the 4.0+ versions.
$('select').select2
language:
noResults: ->
return "<a href='http://google.com'>Add</a>";
escapeMarkup: (markup) ->
return markup;
Let me know if this works for you!
I have a projectFactory:
#app.factory "projectFactory", ['$http', ($http) ->
factory = {}
factory.loadProject = (projectId) ->
$http.get( endpoint(projectId) )
(endpoint is a method that generates the backend api url)
I then have a projectCtrl that is dependent on that factory:
#app.controller 'ProjectCtrl', ['$scope','$routeParams', 'projectFactory', ($scope, $routeParams, projectFactory) ->
$scope.projectId = $routeParams.projectId
$scope.loadProject = (projectId) ->
projectFactory.loadProject(projectId)
.success((data)->$scope.project = data.project)
I then have my project_control_spec test:
'use strict'
describe "ProjectCtrl", ->
beforeEach module 'app'
ProjectCtrl = {}
$scope = {}
projectFactory = {}
beforeEach ->
module($provide) ->
$provide.factory "projectFactory", projectFactory
module inject($controller, $rootScope) ->
$scope = $rootScope.$new()
ProjectCtrl = $controller 'ProjectCtrl', {
$scope : $scope,
$routeParams: {projectId: 1},
}
it "should instantiate a PC", ->
expect(ProjectCtrl).toBeDefined()
it "should have access to the projectId via the routeParams", ->
expect($scope.projectId).toEqual(1)
it "should have access to projectFactory", ->
expect($scope.projectFactory).toBeDefined()
it "should create $scope.project when calling loadProject", ->
expect($scope.project).toBeUndefined();
expect($scope.loadProject(1)).toBe(1)
expect($scope.project).toEqual({//a project object})
I am getting the error ReferenceError: Can't find variable: $provide, when trying to require my projectFactory
You cannot inject $provide on line module inject($controller, $rootScope, $provide) ->. It is also not used or needed in any case.
You should also test this case with $httpBackend. Check the first example.
What if you try this ?
(It is in javascript, sorry I can't write coffee yet)
beforeEach( inject (function( $injector ){
$provide = $injector.get('$provide');
// ...
}));
I'm not familiar with CoffeeScript, but this is how I would do it in plain old JS:
var projectFactory = {};
beforeEach(function () {
module('app', function ($provide) {
$provide.factory('projectFactory', projectFactory);
});
});
When taking some of your code and running it through a Coffee to JS interpreter, I get the following result:
describe("ProjectCtrl", function() {
var $scope, ProjectCtrl, projectFactory;
beforeEach(module('app'));
ProjectCtrl = {};
$scope = {};
projectFactory = {};
beforeEach(function() {
module($provide)(function() {
$provide.factory("projectFactory", projectFactory);
});
});
});
Basically you're trying to load a second module called $provide, when what you actually want to do is open up a config block for the first module (app) and inject $provide into the configuration block.
If you were using angular.module for your actual implementation of app, it'd look something like this:
angular.module('app', []).config(function ($provide) {
$provide.value('..', {});
$provide.constant('...', {});
/** and so on **/
});
Whereas in your specs when using angular-mocks, a config block gets set like this:
module('app', function ($provide) {
$provide.value('..', {});
/** and so on **/
});
I hope that helps. I'm not sure how you would actually write this in CoffeeScript, but I'm sure you can figure that one out. When taking the result of the Coffee to JS interpreter of your code, I received the same result - there is no variable $provide.