1

Overview

I'm working on a Ruby on Rails application, part of which uses Fullcalendar to drag and arrange events onto, out of, and within, the calendar space to set up a day schedule.

This is all working beautifully, but testing it is another matter. Initially I tested the functionality by a controller spec that emulated the data gathered via drag-and-drop methods, but unfortunately the controller specs were discovered to have been breaking the entire test suite. Thus I'm forced to test this with feature specs, and actually dragging and dropping the panels onto the calendar.

Failed Testing Attempt #1

Initially I tried using Capybara's Node::Element#drag_to, specifying an element as deep into the calendar as I could (which turned out to be the second <td> child element of a timeslot's <tr> element (example: $('tr[data-time="12:00:00"] td.fc-widget-content')[1]).

The exact call was:

find('div[data-duration="1800000"]').drag_to all('tr[data-time="12:00:00"] td.fc-widget-content')[1] The find was finding the proper element, as was the all, as verified using binding.pry

This fails, and I'm pretty sure it fails because the left edge of that <td> element is outside of the area which Fullcalendar registers as droppable, and thus the element is returned to it's home.

I figured I'd need a way to specify a pixel horizontal offset to get around this problem.

Failed Testing Attempt #2

Capybara/Poltergeist comes with a seemingly-handy drag_by(x, y) (see documentation here). Because I needed to calculate my own x and y offsets from given div elements, I sequestered that operation into it's own function in my specs:

def drag_to_calendar(element_identifier, time = '12:00:00', x_off = 0, y_off = 0)
  x_buff = 100 # Number of pixels to the right of the left edge to get to the middle-area of the element
  y_buff = 10  # Number of pixels below the top to get to the middle-area of the element
  dest_x_script = "$('tr[data-time=\"#{time}\"]').offset().left"
  dest_y_script = "$('tr[data-time=\"#{time}\"]').offset().top"
  init_x_script = "$('#{element_identifier}').offset().left"
  init_y_script = "$('#{element_identifier}').offset().top"

  dest_x = page.evaluate_script(dest_x_script) + x_buff + x_off
  dest_y = page.evaluate_script(dest_y_script) + y_buff + y_off
  init_x = page.evaluate_script(init_x_script)
  init_y = page.evaluate_script(init_y_script)
  x = dest_x - init_x
  y = dest_y - init_y

  find(element_identifier).native.drag_by(x, y)
end

I made the function to allow any time to be specified, along with any additional x and y offsets (for moving elements more dynamically within/around the calendar). The element_identifier is just a string that can be used inserted directly into a Capybara find() function to get that element.

Example usage:

drag_to_calendar("div[data-duration="1800000"]", '12:00:00', 0, 0)

(The *_script variables are because you can't insert #{variables} into the evaluate_script arguments directly, and so I had to build those strings before calling them. You might notice I'm only getting the <tr> elements in the scripts, instead of the second <td> child element; that's because I couldn't get the .offset() to work on the child elements; the 100-pixel horizontal offset was to get around starting farther left than before)

Using binding.pry I confirmed that all variables were being set correctly, all scripts were running properly, and all arithmetic at the end was functioning as it should.

Using a completely unique testing page unrelated to the app based heavily off robertc's answer here I confirmed that Capybara/Poltergeist's drab_by is functioning as well. (To build the test environment I overwrote the #edit page on which this is all taking place with robertc's example, added his aside style to application.scss, and added the following code to the javascript file that operates on that page:

$(document).on('turbolinks:load',function() {
    // Pre-existing javascript things....

    dm = document.getElementById('dragme');
    if (dm != null) {
        dm.addEventListener("mousedown", dmMouseDown, false);
    }
});

function dmMouseDown () {
    stateMouseDown = true;
    document.addEventListener ("mousemove", dmMouseMove, false);
}

function dmMouseMove(ev) {
    var pX = ev.pageX;
    var pY = ev.pageY;
    dm.style.left = pX + "px";
    dm.style.top = pY + "px";
    document.addEventListener ("mouseup", dmMouseUp, false);
}

function dmMouseUp() {
    document.removeEventListener ("mousemove", dmMouseMove, false);
    document.removeEventListener ("mouseup", dmMouseUp, false);
}

This revealed that Capybara/Poltergeist's drag_by was operating exactly as dragging the element with the mouse was.

And yet, when all those functioning elements came together, the spec was still not functioning. Within a binding.pry environment I tested the drag using every transmutation I could think of: wapping x and y, using negative values (in case I entered a mirror universe where +x means left), treating x and y as offsets from the initial element, treating them as absolute from the page origin, and even throwing semirandom values into x and y in the hopes that enough darts might eventually hit the dart-board. Nothing worked. Either elements aren't getting dragged (unlikely, considering the above), or fullcalendar isn't registering the drop.

Assessment

I'm thinking that the problem lies with Fullcalendar, or in the interaction between Poltergeist and Fullcalendar where Fullcalendar doesn't recognize anything as getting dropped. Looking through the event listeners on Chrome's debugging tools, it seems mousedown, mouseup, and mousemove are the only drag-and-drop-related things Javascript has, and thus are the only things Fullcalendar could be looking for, and thus it seems it should be working. But it's not.

Any advice would be much appreciated. Any solutions, much more so!

Community
  • 1
  • 1

1 Answers1

0

The following code shows drag and drop of an event working on the FullCalendar demo page

require 'capybara/poltergeist'

Capybara.register_driver :poltergeist do |app|
  Capybara::Poltergeist::Driver.new(app, js_errors: true, phantomjs_options: ['--ssl-protocol=TLSv1.2'])
end

sess = Capybara::Session.new(:poltergeist)
sess.visit 'https://fullcalendar.io/'
event = sess.first('.fc-day-grid-event', minimum: 1)
first_day = sess.find('.fc-day-grid .fc-week:first-child .fc-content-skeleton tbody td:first-child')
first_day.assert_no_selector('.fc-day-grid-event')

event.drag_to(first_day)
first_day.assert_selector('.fc-day-grid-event')

which drags the first event on the calendar back to the first day of the calendar. Since that's working it's probably a configuration issue with FullCalendar, your location calculations are off, or you're dragging to the wrong element. I see in FullCalendar there is a configurable delay before dragging starts, have you set that by any chance?

Thomas Walpole
  • 48,548
  • 5
  • 64
  • 78