define(function(require) { 'use strict'; var Factory = require('test/support/factory'); var IntervalTree = require('interval_tree'); var Overlap = require('utils/overlap'); var Timespan = require('common/timespan'); suite('overlap', function() { var forever; var subject; var baseDate = new Date(2012, 0, 1); function createEl() { return document.createElement('span'); } // all time is based on the same // day each overlap instance is expected // to only cover a single date. function time(hours=0, minutes=0, seconds=0) { var date = new Date(baseDate.valueOf()); date.setHours(hours); date.setMinutes(minutes); date.setSeconds(seconds); return date; } function record(start, end) { var r = { element: createEl(), busytime: Factory('busytime', { startDate: start, endDate: end }) }; r.element.id = r.busytime._id; return r; } function addRecord(startHour, startMin, endHour, endMin) { var rec = record( time(startHour, startMin), time(endHour, endMin) ); subject.add(rec.busytime, rec.element); return rec; } function addRecords(times) { var records = []; times.forEach(function(tm) { records.push(addRecord.apply(this, tm)); }); return records; } function busytimesFromRecords(records) { return records.map(function(r) { return r.busytime; }); } function conflictSpansFromRecords(records) { return records.map(function(r) { return subject.getConflictSpan(r.busytime); }); } function elementsFromRecords(records) { return records.map(function(r) { return subject.getElement(r.busytime); }); } function widthAndLeftFromRecords(records) { return records.map(function(r) { var el = subject.getElement(r.busytime); if (!el) { return [null, null]; } else { return [el.style.left, el.style.width]; } }); } function classNamesFromRecords(records) { return elementsFromRecords(records) .map(function(el) { // make sure it is always in the same order return el ? el.className.trim().split(/\s+/).sort().join(' ') : null; }); } setup(function() { subject = new Overlap(); forever = new Timespan(0, Infinity); }); teardown(function() { subject.reset(); }); test('initialize', function() { assert.instanceOf(subject.tree, IntervalTree); assert.deepEqual( subject.elements, {} ); }); suite('#add', function() { setup(function() { subject.reset(); }); test('single', function() { var records = addRecords([ [1, 0, 2, 0] ]); assert.deepEqual( subject.tree.query(forever), busytimesFromRecords(records) ); }); test('2 non-conflicting', function() { var records = addRecords([ [1, 0, 2, 0], [2, 0, 3, 0] ]); assert.deepEqual( subject.tree.query(forever), busytimesFromRecords(records) ); assert.deepEqual(subject.conflicts, {}); }); test('2 in conflict', function() { var records = addRecords([ [1, 0, 2, 0], [1, 1, 2, 1] ]); var cs1 = subject.getConflictSpan(records[0].busytime); assert.ok(cs1, 'conflict found for record 0'); var cs2 = subject.getConflictSpan(records[1].busytime); assert.ok(cs2, 'conflict found for record 1'); assert.equal(cs1, cs2, 'records share the same conflict span'); assert.equal(cs1.columns.length, 2); }); test('3 in conflict', function() { var records = addRecords([ [1, 0, 2, 0], [1, 1, 2, 1], [1, 2, 2, 2] ]); var cs = conflictSpansFromRecords(records); var cs0 = cs[0]; assert.deepEqual(cs, [cs0, cs0, cs0], 'records share the same conflict span'); assert.equal(cs0.columns.length, 3); }); test('3 in staggered conflict', function() { var records = addRecords([ [1, 0, 2, 0], [1, 30, 2, 30], [2, 0, 3, 0] ]); var cs = conflictSpansFromRecords(records); var cs0 = cs[0]; assert.deepEqual(cs, [cs0, cs0, cs0], 'records share the same conflict span'); assert.equal(cs0.columns.length, 2, 'staggered conflict results in 2 columns'); }); test('event add crossing spans should trigger merge', function() { // Compose events such that there are 2 separate conflict spans: var records = addRecords([ [1, 0, 2, 0], [1, 1, 2, 1], [3, 0, 3, 30], [3, 15, 4, 1] ]); // Ensure there are 2 separate conflict spans. var cs = conflictSpansFromRecords(records); assert.equal(cs[0], cs[1]); assert.equal(cs[2], cs[3]); assert.notEqual(cs[0], cs[2]); assert.equal(cs[0].columns.length, 2); assert.equal(cs[2].columns.length, 2); // Add an event that partly crosses the 2 spans, triggering a merge. records.push(addRecord(1, 30, 3, 15)); // There should be one span, now, encompassing all events. cs = conflictSpansFromRecords(records); cs.forEach(function(c) { assert.equal(c, cs[0]); assert.equal(c.columns.length, 3); }); }); }); suite('#remove', function() { setup(function() { subject.reset(); }); test('span with a gap on event removal should split', function() { // Compose events such that there are 2 separate conflict spans: var records = addRecords([ [1, 0, 2, 0], [1, 1, 2, 1], // The gap goes here. [3, 0, 3, 30], [3, 15, 4, 1] ]); // Add an event that crosses the 2 spans, triggering a merge. var mergeRec = addRecord(1, 30, 3, 15); // There should be one span, now, encompassing all events. var cs = conflictSpansFromRecords(records); cs.forEach(function(c) { assert.equal(c, cs[0]); assert.equal(c.columns.length, 3); }); // Removing the event that joins 2 hunks should result in a split. subject.remove(mergeRec.busytime); assert.ok(!subject.tree.byId[mergeRec.busytime._id]); // Ensure there are 2 separate conflict spans. cs = conflictSpansFromRecords(records); assert.notEqual(cs[0].id, cs[2].id); assert.equal(cs[0], cs[1]); assert.equal(cs[2], cs[3]); // Columns in the new conflict spans should have collapsed to 2 assert.equal(cs[0].columns.length, 2); assert.equal(cs[2].columns.length, 2); }); test('span with no conflicts should self-destruct', function() { // Non-conflicting events var records = addRecords([ [1, 0, 2, 0], [3, 0, 4, 0] ]); // Cause a staggered conflict. var conflict = addRecord(1, 30, 3, 30); // Ensure the conflict exists as expected. var cs = conflictSpansFromRecords(records); assert.equal(cs[0], cs[1]); assert.notEqual(cs[0], undefined); // Remove the event that causes all the conflict. subject.remove(conflict.busytime); // Now, none of the records should be a part of a conflict. cs = conflictSpansFromRecords(records); assert.deepEqual(cs, [undefined, undefined]); }); }); suite('#_updateLayout', function() { setup(function() { subject.reset(); }); test('layout updates triggered on #add and #remove', function() { var records = addRecords([ [1, 0, 2, 0], [1, 15, 2, 15], [1, 30, 2, 30], [3, 0, 4, 0], [3, 30, 4, 30], [6, 0, 7, 0] ]); assert.deepEqual(widthAndLeftFromRecords(records), [ ['33.3333%', '33.3333%'], ['0%', '33.3333%'], ['66.6667%', '33.3333%'], ['50%', '50%'], ['0%', '50%'], ['', ''] ]); assert.deepEqual(classNamesFromRecords(records), [ 'has-overlaps', 'has-overlaps', 'has-overlaps', 'has-overlaps', 'has-overlaps', '' ]); var big1 = addRecord(1, 45, 7, 45); records.push(big1); assert.deepEqual(widthAndLeftFromRecords(records), [ ['25%', '25%'], ['0%', '25%'], ['50%', '25%'], ['25%', '25%'], ['0%', '25%'], ['0%', '25%'], ['75%', '25%'] ]); assert.deepEqual(classNamesFromRecords(records), [ 'has-overlaps', 'has-overlaps', 'has-overlaps', 'has-overlaps', 'has-overlaps', 'has-overlaps', 'has-overlaps' ]); var big2 = addRecord(3, 45, 6, 30); records.push(big2); assert.deepEqual(widthAndLeftFromRecords(records), [ ['25%', '25%'], ['0%', '25%'], ['50%', '25%'], ['25%', '25%'], ['0%', '25%'], ['0%', '25%'], ['75%', '25%'], ['50%', '25%'] ]); assert.deepEqual(classNamesFromRecords(records), [ 'has-overlaps', 'has-overlaps', 'has-overlaps', 'has-overlaps', 'has-overlaps', 'has-overlaps', 'has-overlaps', 'has-overlaps' ]); var big3 = addRecord(1, 15, 3, 15); records.push(big3); assert.deepEqual(widthAndLeftFromRecords(records), [ ['20%', '20%'], ['0%', '20%'], ['40%', '20%'], ['20%', '20%'], ['0%', '20%'], ['0%', '20%'], ['60%', '20%'], ['40%', '20%'], ['80%', '20%'] ]); assert.deepEqual(classNamesFromRecords(records), [ 'has-overlaps many-overlaps', 'has-overlaps many-overlaps', 'has-overlaps many-overlaps', 'has-overlaps many-overlaps', 'has-overlaps many-overlaps', 'has-overlaps many-overlaps', 'has-overlaps many-overlaps', 'has-overlaps many-overlaps', 'has-overlaps many-overlaps' ]); subject.remove(big1.busytime); assert.deepEqual(widthAndLeftFromRecords(records), [ ['25%', '25%'], ['0%', '25%'], ['50%', '25%'], ['25%', '25%'], ['0%', '25%'], ['0%', '25%'], [null, null], ['50%', '25%'], ['75%', '25%'] ]); assert.deepEqual(classNamesFromRecords(records), [ 'has-overlaps', 'has-overlaps', 'has-overlaps', 'has-overlaps', 'has-overlaps', 'has-overlaps', null, 'has-overlaps', 'has-overlaps' ]); subject.remove(big2.busytime); assert.deepEqual(widthAndLeftFromRecords(records), [ ['25%', '25%'], ['0%', '25%'], ['50%', '25%'], ['25%', '25%'], ['0%', '25%'], ['', ''], [null, null], [null, null], ['75%', '25%'] ]); assert.deepEqual(classNamesFromRecords(records), [ 'has-overlaps', 'has-overlaps', 'has-overlaps', 'has-overlaps', 'has-overlaps', '', null, null, 'has-overlaps' ]); subject.remove(big3.busytime); assert.deepEqual(widthAndLeftFromRecords(records), [ ['33.3333%', '33.3333%'], ['0%', '33.3333%'], ['66.6667%', '33.3333%'], ['0%', '50%'], ['50%', '50%'], ['', ''], [null, null], [null, null], [null, null] ]); assert.deepEqual(classNamesFromRecords(records), [ 'has-overlaps', 'has-overlaps', 'has-overlaps', 'has-overlaps', 'has-overlaps', '', null, null, null ]); }); test('bug 817763 - collapse columns on conflict delete', function() { var records = addRecords([ [1, 0, 1, 15], [1, 30, 2, 0] ]); var to_delete = addRecord(1, 0, 2, 0); records.push(to_delete); assert.deepEqual(widthAndLeftFromRecords(records), [ ['50%', '50%'], ['50%', '50%'], ['0%', '50%'] ]); assert.deepEqual(classNamesFromRecords(records), [ 'has-overlaps', 'has-overlaps', 'has-overlaps' ]); subject.remove(to_delete.busytime); assert.deepEqual(widthAndLeftFromRecords(records), [ ['', ''], ['', ''], [null, null] ]); assert.deepEqual(classNamesFromRecords(records), [ '', '', null ]); }); }); }); });