<commit_before>'use strict'

module.exports = function (mongoose, config, db) {
    const utils = require('../lib/utils')();

    function commentResponse(comment, _user) {
        const user = _user || comment.user;
        return {
            id: comment.id,
            created_at: comment.created_at,
            user_id: comment.user_id,
            user: {
                id: user.id,
                name: user.name,
                email: user.email
            },
            target_article_id: comment.target_article_id,
            target_id: comment.target_id,
            content: comment.content
        }
    }

    function createComment(res, content, user, target_article, target) {
        db.Sequence.getNextSequence('comment', (err, nextCommentId) => {
            const comment = db.Comment({
                nextCommentId,
                user_id: user.id,
                user,
                target_article_id: target_article.id,
                target_article,
                target_id: target ? target.id : 0,
                target,
                content
            });
            comment.save(err => err ?
                utils.error(res, 422, err.message) :
                utils.success(res, "update success!", commentResponse(comment, user))
                )
        });
    }

    return {
        create(req, res) {
            db.Article.findOne({ id: req.body.article }, (err, target_article) => {
                if (err) return utils.error(res, 422, err);
                if (!target_article) return utils.error(res, 404);
                if (req.body.target) {
                    db.Comment.findOne({ id: req.body.target }, (err, target) =>
                        createComment(res, req.body.content, req.user, target_article, target));
                } else {
                    createComment(res, req.body.content, req.user, target_article);
                }
            });
        },

        delete(req, res) {
            db.Comment.findOne({ id: req.params.commentId }).populate('target_article').populate('target').exec((err, comment) => {
                if (err) return utils.error(res, 422, err);
                if (comment.user_id != req.user.id
                    || comment.target_article.user_id != req.user.id
                    || comment.target.user_id != req.user.id) return utils.error(res, 403, "Forbidden");
                if (!comment) return utils.error(res, 404);
                comment.remove(err => err ? utils.error(res, 422) : utils.success(res));
            })
        },

        get(req, res) {
            const id = req.params.commentId;
            let query = {};
            let sortBy = null;

            if (id) {
                query = { id };
            } else {
                sortBy = {};
                sortBy["order_by"] = "created_at";
                sortBy["order_type"] = "desc";
                if (req.query.user_id) query["user_id"] = req.query.user_id;
                if (req.query.target_article_id) query["target_article_id"] = req.query.target_article_id;
                if (req.query.target_id) query["target_id"] = req.query.target_id;
            }

            const queryRequest = db.Comment.find(query);
            if (sortBy) {
                var sort = {};
                sort[sortBy["order_by"]] = sortBy["order_type"] === "asc" ? "ascending" : "descending";
                queryRequest.sort(sort);
            }

            queryRequest.populate({path: 'user', select: 'id name email'}).exec((err, dbResponse) => {
                if (err) return utils.error(res, 422, err.message);
                if (!dbResponse) return utils.error(res, 404);
                console.log('response', dbResponse);
                utils.responseData(res, dbResponse.count, dbResponse.map(comment => commentResponse(comment)));
            })
        }
    }
}<commit_msg>remove log<commit_after>'use strict'

module.exports = function (mongoose, config, db) {
    const utils = require('../lib/utils')();

    function commentResponse(comment, _user) {
        const user = _user || comment.user;
        return {
            id: comment.id,
            created_at: comment.created_at,
            user_id: comment.user_id,
            user: {
                id: user.id,
                name: user.name,
                email: user.email
            },
            target_article_id: comment.target_article_id,
            target_id: comment.target_id,
            content: comment.content
        }
    }

    function createComment(res, content, user, target_article, target) {
        db.Sequence.getNextSequence('comment', (err, nextCommentId) => {
            const comment = db.Comment({
                nextCommentId,
                user_id: user.id,
                user,
                target_article_id: target_article.id,
                target_article,
                target_id: target ? target.id : 0,
                target,
                content
            });
            comment.save(err => err ?
                utils.error(res, 422, err.message) :
                utils.success(res, "update success!", commentResponse(comment, user))
                )
        });
    }

    return {
        create(req, res) {
            db.Article.findOne({ id: req.body.article }, (err, target_article) => {
                if (err) return utils.error(res, 422, err);
                if (!target_article) return utils.error(res, 404);
                if (req.body.target) {
                    db.Comment.findOne({ id: req.body.target }, (err, target) =>
                        createComment(res, req.body.content, req.user, target_article, target));
                } else {
                    createComment(res, req.body.content, req.user, target_article);
                }
            });
        },

        delete(req, res) {
            db.Comment.findOne({ id: req.params.commentId }).populate('target_article').populate('target').exec((err, comment) => {
                if (err) return utils.error(res, 422, err);
                if (comment.user_id != req.user.id
                    || comment.target_article.user_id != req.user.id
                    || comment.target.user_id != req.user.id) return utils.error(res, 403, "Forbidden");
                if (!comment) return utils.error(res, 404);
                comment.remove(err => err ? utils.error(res, 422) : utils.success(res));
            })
        },

        get(req, res) {
            const id = req.params.commentId;
            let query = {};
            let sortBy = null;

            if (id) {
                query = { id };
            } else {
                sortBy = {};
                sortBy["order_by"] = "created_at";
                sortBy["order_type"] = "desc";
                if (req.query.user_id) query["user_id"] = req.query.user_id;
                if (req.query.target_article_id) query["target_article_id"] = req.query.target_article_id;
                if (req.query.target_id) query["target_id"] = req.query.target_id;
            }

            const queryRequest = db.Comment.find(query);
            if (sortBy) {
                var sort = {};
                sort[sortBy["order_by"]] = sortBy["order_type"] === "asc" ? "ascending" : "descending";
                queryRequest.sort(sort);
            }

            queryRequest.populate({path: 'user', select: 'id name email'}).exec((err, dbResponse) => {
                if (err) return utils.error(res, 422, err.message);
                if (!dbResponse) return utils.error(res, 404);
                utils.responseData(res, dbResponse.count, dbResponse.map(comment => commentResponse(comment)));
            })
        }
    }
}<|endoftext|>


<commit_before>import { test } from 'substance-test'
import { setCursor, openManuscriptEditor, PseudoFileEvent, getEditorSession, loadBodyFixture, getDocument, setSelection, LOREM_IPSUM } from './shared/integrationTestHelpers'
import setupTestApp from './shared/setupTestApp'

// TODO: add a test demonstrating that the TOC is working as expected
// TODO: test editing of block-quote
// TODO: test editing of inline-formula
// TODO: test editing of block-formula
// TODO: test editing of supplementary file description
// TODO: test open link in EditExtLinkTool
// TODO: test IncreaseHeadingLevel
// TODO: test insert table
// TODO: test key-handling of table cell editor
// TODO: test key-handling of table component
// TODO: test TableEditing
// TODO: test save button
// TODO: find out why Footnote.getTemplate() is not covered -> insert footnote?
// TODO: test changin level of list item
// TODO: find out why TableRow.getCellAt is not covered (isn't it used in TableEditing, and table editing is covered?)
// TODO: BreakComponent not used
// TODO: error case for GraphicComponent and InlineGraphicCOmponent not tested

test('ManuscriptEditor: add inline graphic', t => {
  let { app } = setupTestApp(t, LOREM_IPSUM)
  let editor = openManuscriptEditor(app)
  setCursor(editor, 'p-2.content', 3)
  let insertInlineGraphicTool = editor.find('.sc-insert-inline-graphic-tool')
  // Trigger onFileSelect() directly
  insertInlineGraphicTool.onFileSelect(new PseudoFileEvent())
  let inlineGraphic = editor.find('[data-id=p-2] .sc-inline-node.sm-inline-graphic')
  t.notNil(inlineGraphic, 'there should be an inline-graphic now')
  t.end()
})

test('ManuscriptEditor: add inline formula', t => {
  let { app } = setupTestApp(t, LOREM_IPSUM)
  let editor = openManuscriptEditor(app)
  setCursor(editor, 'p-2.content', 3)
  // Open insert dropdown
  const insertDropdown = editor.find('.sc-tool-dropdown.sm-insert .sc-button')
  insertDropdown.click()
  let insertInlineFormulaTool = editor.find('.sc-menu-item.sm-insert-formula')
  t.ok(insertInlineFormulaTool.click(), 'clicking on the insert inline formula button should not throw error')
  let inlineFormula = editor.find('[data-id=p-2] .sc-inline-node.sm-inline-formula')
  t.notNil(inlineFormula, 'there should be an inline-formula now')
  t.end()
})

test('ManuscriptEditor: add block formula', t => {
  let { app } = setupTestApp(t, LOREM_IPSUM)
  let editor = openManuscriptEditor(app)
  setCursor(editor, 'p-2.content', 5)
  // Open insert dropdown
  const insertDropdown = editor.find('.sc-tool-dropdown.sm-insert .sc-button')
  insertDropdown.click()
  let insertDispFormulaTool = editor.find('.sc-menu-item.sm-insert-disp-formula')
  t.ok(insertDispFormulaTool.click(), 'clicking on the insert disp formula button should not throw error')
  let blockFormula = editor.find('*[data-id=p-2] + .sm-block-formula')
  t.notNil(blockFormula, 'there should be a block-formula now')
  // TODO: we should test automatic labeling
  t.end()
})

test('ManuscriptEditor: TOC should be updated on change', t => {
  let { app } = setupTestApp(t, LOREM_IPSUM)
  let editor = openManuscriptEditor(app)
  let toc = editor.find('.sc-toc')
  let editorSession = getEditorSession(editor)
  editorSession.transaction(tx => {
    tx.set(['sec-1', 'content'], 'TEST')
  })
  let h1 = toc.find('*[data-id="sec-1"]')
  t.equal(h1.el.text(), 'TEST', 'TOC entry should have been updated')
  t.end()
})

const ONE_PARAGRAPH = '
ABC
'

test('ManuscriptEditor: Switch paragraph to heading', t => {
  let { app } = setupTestApp(t, { archiveId: 'blank' })
  let editor = openManuscriptEditor(app)
  loadBodyFixture(editor, ONE_PARAGRAPH)
  setCursor(editor, 'p1.content', 0)
  // open the switch type dropdown
  let switchTypeDropdown = editor.find('.sc-tool-dropdown.sm-text-types')
  switchTypeDropdown.find('button').click()
  let h1button = switchTypeDropdown.find('.sc-menu-item.sm-heading1')
  t.notNil(h1button, 'there should be an option to switch to heading level 1')
  h1button.click()
  // ATTENTION: we do not change id, which might be confusing for others
  let h1El = editor.find('.sc-surface.sm-body > h1')
  t.notNil(h1El, 'there should be a
 element now')
  t.end()
})

test('ManuscriptEditor: Switch paragraph to preformat', t => {
  let { app } = setupTestApp(t, { archiveId: 'blank' })
  let editor = openManuscriptEditor(app)
  loadBodyFixture(editor, ONE_PARAGRAPH)
  setCursor(editor, 'p1.content', 0)
  // open the switch type dropdown
  let switchTypeDropdown = editor.find('.sc-tool-dropdown.sm-text-types')
  switchTypeDropdown.find('button').click()
  let preformatButton = switchTypeDropdown.find('.sc-menu-item.sm-preformat')
  t.notNil(preformatButton, 'there should be an option to switch to preformat')
  preformatButton.click()
  let preformatEl = editor.find('.sc-surface.sm-body > .sc-text-node.sm-preformat')
  t.notNil(preformatEl, 'there should be a div with preformat component class now')
  t.end()
})

const SOME_PS = `
abcdef

ghijkl

mnopqr

stuvwx
`

test('ManuscriptEditor: toggling a list', t => {
  let { app } = setupTestApp(t, { archiveId: 'blank' })
  let editor = openManuscriptEditor(app)
  let doc = getDocument(editor)
  loadBodyFixture(editor, SOME_PS)

  let p1Text = doc.get(['p1', 'content'])

  setSelection(editor, 'p1.content', 0)
  let ulTool = editor.find('.sc-toggle-tool.sm-toggle-unordered-list')
  t.notNil(ulTool, 'the unordered-list tool should be visible')

  // click on list tool to turn "p1" into a list
  ulTool.find('button').el.click()
  let listNode = doc.get('body').getNodeAt(0)
  t.equal(listNode.type, 'list', 'first node should now be a list')
  t.equal(listNode.items.length, 1, '.. with one item')
  let listItem = listNode.getItemAt(0)
  t.equal(listItem.getText(), p1Text, '.. with the text of the former paragraph')

  // now there should be contextual list tools be visible
  t.notNil(editor.find('.sc-toggle-tool.sm-indent-list'), 'now there should be the indent tool be visible')
  t.notNil(editor.find('.sc-toggle-tool.sm-dedent-list'), '.. and the dedent tool')

  // click on list tool to turn it into a paragraph again
  editor.find('.sc-toggle-tool.sm-toggle-unordered-list > button').click()
  let pNode = doc.get('body').getNodeAt(0)
  t.equal(pNode.type, 'paragraph', 'first node should now be a paragraph again')

  t.end()
})

const P_WITH_EXTERNAL_LINK = `<p id="p1">This is a <ext-link xmlns:xlink="http://www.w3.org/1999/xlink" id="link" xlink:href="substance.io">link</ext-link></p>`

test('ManuscriptEditor: editing an external link', t => {
  let { app } = setupTestApp(t, { archiveId: 'blank' })
  let editor = openManuscriptEditor(app)
  let doc = getDocument(editor)
  loadBodyFixture(editor, P_WITH_EXTERNAL_LINK)

  function _getUrlInput () { return editor.find('.sc-edit-external-link-tool > input') }
  function _getUrlInputValue () { return _getUrlInput().el.val() }
  function _setUrlInputValue (val) { return _getUrlInput().el.val(val) }

  let link = doc.get('link')
  setCursor(editor, 'p1.content', link.start.offset + 1)

  t.equal(_getUrlInputValue(), link.href, 'url input field should show current href value')
  _setUrlInputValue('foo')
  t.doesNotThrow(() => {
    _getUrlInput()._onChange()
  }, 'triggering href update should not throw')
  t.equal(link.href, 'foo', '.. and the link should have been updated')
  t.end()
})
<commit_msg>Fix manuscript editor test.<commit_after>import { test } from 'substance-test'
import { setCursor, openManuscriptEditor, PseudoFileEvent, getEditorSession, loadBodyFixture, getDocument, setSelection, LOREM_IPSUM } from './shared/integrationTestHelpers'
import setupTestApp from './shared/setupTestApp'

// TODO: add a test demonstrating that the TOC is working as expected
// TODO: test editing of block-quote
// TODO: test editing of inline-formula
// TODO: test editing of block-formula
// TODO: test editing of supplementary file description
// TODO: test open link in EditExtLinkTool
// TODO: test IncreaseHeadingLevel
// TODO: test insert table
// TODO: test key-handling of table cell editor
// TODO: test key-handling of table component
// TODO: test TableEditing
// TODO: test save button
// TODO: find out why Footnote.getTemplate() is not covered -> insert footnote?
// TODO: test changin level of list item
// TODO: find out why TableRow.getCellAt is not covered (isn't it used in TableEditing, and table editing is covered?)
// TODO: BreakComponent not used
// TODO: error case for GraphicComponent and InlineGraphicCOmponent not tested

test('ManuscriptEditor: add inline graphic', t => {
  let { app } = setupTestApp(t, LOREM_IPSUM)
  let editor = openManuscriptEditor(app)
  setCursor(editor, 'p-2.content', 3)
  let insertInlineGraphicTool = editor.find('.sc-insert-inline-graphic-tool')
  // Trigger onFileSelect() directly
  insertInlineGraphicTool.onFileSelect(new PseudoFileEvent())
  let inlineGraphic = editor.find('[data-id=p-2] .sc-inline-node.sm-inline-graphic')
  t.notNil(inlineGraphic, 'there should be an inline-graphic now')
  t.end()
})

test('ManuscriptEditor: add inline formula', t => {
  let { app } = setupTestApp(t, LOREM_IPSUM)
  let editor = openManuscriptEditor(app)
  setCursor(editor, 'p-2.content', 3)
  // Open insert dropdown
  const insertDropdown = editor.find('.sc-tool-dropdown.sm-insert .sc-button')
  insertDropdown.click()
  let insertInlineFormulaTool = editor.find('.sc-menu-item.sm-insert-formula')
  t.ok(insertInlineFormulaTool.click(), 'clicking on the insert inline formula button should not throw error')
  let inlineFormula = editor.find('[data-id=p-2] .sc-inline-node.sm-inline-formula')
  t.notNil(inlineFormula, 'there should be an inline-formula now')
  t.end()
})

test('ManuscriptEditor: add block formula', t => {
  let { app } = setupTestApp(t, LOREM_IPSUM)
  let editor = openManuscriptEditor(app)
  setCursor(editor, 'p-2.content', 5)
  // Open insert dropdown
  const insertDropdown = editor.find('.sc-tool-dropdown.sm-insert .sc-button')
  insertDropdown.click()
  let insertDispFormulaTool = editor.find('.sc-menu-item.sm-insert-disp-formula')
  t.ok(insertDispFormulaTool.click(), 'clicking on the insert disp formula button should not throw error')
  let blockFormula = editor.find('*[data-id=p-2] + .sm-block-formula')
  t.notNil(blockFormula, 'there should be a block-formula now')
  // TODO: we should test automatic labeling
  t.end()
})

test('ManuscriptEditor: TOC should be updated on change', t => {
  let { app } = setupTestApp(t, LOREM_IPSUM)
  let editor = openManuscriptEditor(app)
  let toc = editor.find('.sc-toc')
  let editorSession = getEditorSession(editor)
  editorSession.transaction(tx => {
    tx.set(['sec-1', 'content'], 'TEST')
  })
  let h1 = toc.find('*[data-id="sec-1"]')
  t.equal(h1.el.text(), 'TEST', 'TOC entry should have been updated')
  t.end()
})

const ONE_PARAGRAPH = '
ABC
'

test('ManuscriptEditor: Switch paragraph to heading', t => {
  let { app } = setupTestApp(t, { archiveId: 'blank' })
  let editor = openManuscriptEditor(app)
  loadBodyFixture(editor, ONE_PARAGRAPH)
  setCursor(editor, 'p1.content', 0)
  // open the switch type dropdown
  let switchTypeDropdown = editor.find('.sc-tool-dropdown.sm-text-types')
  switchTypeDropdown.find('button').click()
  let h1button = switchTypeDropdown.find('.sc-menu-item.sm-heading1')
  t.notNil(h1button, 'there should be an option to switch to heading level 1')
  h1button.click()
  // ATTENTION: we do not change id, which might be confusing for others
  let h1El = editor.find('.sc-surface.sm-body > h1')
  t.notNil(h1El, 'there should be a
 element now')
  t.end()
})

test('ManuscriptEditor: Switch paragraph to preformat', t => {
  let { app } = setupTestApp(t, { archiveId: 'blank' })
  let editor = openManuscriptEditor(app)
  loadBodyFixture(editor, ONE_PARAGRAPH)
  setCursor(editor, 'p1.content', 0)
  // open the switch type dropdown
  let switchTypeDropdown = editor.find('.sc-tool-dropdown.sm-text-types')
  switchTypeDropdown.find('button').click()
  let preformatButton = switchTypeDropdown.find('.sc-menu-item.sm-preformat')
  t.notNil(preformatButton, 'there should be an option to switch to preformat')
  preformatButton.click()
  let preformatEl = editor.find('.sc-surface.sm-body > .sc-text-node.sm-preformat')
  t.notNil(preformatEl, 'there should be a div with preformat component class now')
  t.end()
})

const SOME_PS = `
abcdef

ghijkl

mnopqr

stuvwx
`

test('ManuscriptEditor: toggling a list', t => {
  let { app } = setupTestApp(t, { archiveId: 'blank' })
  let editor = openManuscriptEditor(app)
  let doc = getDocument(editor)
  loadBodyFixture(editor, SOME_PS)

  let p1Text = doc.get(['p1', 'content'])

  setSelection(editor, 'p1.content', 0)
  let ulTool = _openMenuAndFindTool(editor, 'text-types', 'sm-create-unordered-list')
  t.ok(ulTool, 'the unordered-list tool should be visible')

  // click on list tool to turn "p1" into a list
  ulTool.find('button').el.click()
  let listNode = doc.get('body').getNodeAt(0)
  t.equal(listNode.type, 'list', 'first node should now be a list')
  t.equal(listNode.items.length, 1, '.. with one item')
  let listItem = listNode.getItemAt(0)
  t.equal(listItem.getText(), p1Text, '.. with the text of the former paragraph')

  // now there should be contextual list tools be visible
  t.ok(_openMenuAndFindTool(editor, 'list-tools', 'sm-indent-list'), 'now there should be the indent tool be visible')
  t.ok(_openMenuAndFindTool(editor, 'list-tools', 'sm-dedent-list'), '.. and the dedent tool')

  // click on list tool to turn it into a paragraph again
  editor.find('.sc-toggle-tool.sm-toggle-unordered-list > button').click()
  let pNode = doc.get('body').getNodeAt(0)
  t.equal(pNode.type, 'paragraph', 'first node should now be a paragraph again')

  t.end()
})

const P_WITH_EXTERNAL_LINK = `<p id="p1">This is a <ext-link xmlns:xlink="http://www.w3.org/1999/xlink" id="link" xlink:href="substance.io">link</ext-link></p>`

test('ManuscriptEditor: editing an external link', t => {
  let { app } = setupTestApp(t, { archiveId: 'blank' })
  let editor = openManuscriptEditor(app)
  let doc = getDocument(editor)
  loadBodyFixture(editor, P_WITH_EXTERNAL_LINK)

  function _getUrlInput () { return editor.find('.sc-edit-external-link-tool > input') }
  function _getUrlInputValue () { return _getUrlInput().el.val() }
  function _setUrlInputValue (val) { return _getUrlInput().el.val(val) }

  let link = doc.get('link')
  setCursor(editor, 'p1.content', link.start.offset + 1)

  t.equal(_getUrlInputValue(), link.href, 'url input field should show current href value')
  _setUrlInputValue('foo')
  t.doesNotThrow(() => {
    _getUrlInput()._onChange()
  }, 'triggering href update should not throw')
  t.equal(link.href, 'foo', '.. and the link should have been updated')
  t.end()
})

function _openMenuAndFindTool (el, menuName, toolClass) {
  const menu = el.find(`.sc-tool-dropdown.sm-${menuName}`)
  if (menu.hasClass('sm-disabled')) return false
  const isActive = menu.find('button').hasClass('sm-active')
  if (!isActive) {
    menu.find('button').el.click()
  }
  return menu.find('button.' + toolClass)
}
<|endoftext|>


<commit_before>var methods = {};

var validateOne = function(validatorFunction, fieldName) {
  // Get field value.
  var value = this.get(fieldName);

  try {
    // Run validator function which throws exception which we have to catch and
    // add error message to appropriate field.
    validatorFunction.call(this, fieldName, value);

    // If a field's validation didn't throw an exception then clear an error
    // message for the given field.
    this._errors.delete(fieldName);

    return true;
  } catch (e) {
    if (e instanceof ValidationError) {
      this._errors.set(fieldName, e.message);
    } else {
      throw e;
    }

    return false;
  }
};

methods.validate = function(fieldName) {
  // Run validation for current and parent classes. The "isValid" variable will
  // be set to true when all validators for all classes passed validation.
  var isValid = Astro.utils.everyClass(this.constructor, function(Class) {
    // Get list of all validators for given class.
    var validators = Class.getValidators();

    if (fieldName) {
      // Get validation function for given field name.
      var validatorFunction = validators[fieldName];

      // Validate field only if there is validation function.
      if (validatorFunction) {
        return validateOne.call(this, validatorFunction, fieldName);
      } else {
        return true;
      }
    } else {
      // Take validators and run each one.
      return _.every(validators, validateOne, this);
    }
  }, this);

  return isValid;
};

methods.getValidationError = function() {
  console.warn('getValidationError had been renamed to getError');
  return this.getError.apply(this, arguments);
};

methods.hasValidationError = function() {
  console.warn('hasValidationError had been renamed to hasError');
  return this.hasError.apply(this, arguments);
};

methods.hasValidationErrors = function() {
  console.warn('hasValidationErrors had been renamed to hasErrors');
  return this.hasErrors.apply(this, arguments);
};

methods.getError = function(fieldName) {
  return this._errors.get(fieldName);
};

methods.hasError = function(fieldName) {
  return this._errors.has(fieldName);
};

methods.hasErrors = function() {
  return this._errors.size;
};

var events = {};

events.tojsonvalue = function(e) {
  var self = this;

  Tracker.nonreactive(function() {
    e.data.errors = self._errors.all();
  });
};

events.fromjsonvalue = function(e) {
  this._errors.set(e.data.errors);
};

onInitModule = function() {
  _.extend(Astro.BaseClass.prototype, methods);

  Astro.on('tojsonvalue', events.tojsonvalue);
  Astro.on('fromjsonvalue', events.fromjsonvalue);
};
<commit_msg>Add getErrors method<commit_after>var methods = {};

var validateOne = function(validatorFunction, fieldName) {
  // Get field value.
  var value = this.get(fieldName);

  try {
    // Run validator function which throws exception which we have to catch and
    // add error message to appropriate field.
    validatorFunction.call(this, fieldName, value);

    // If a field's validation didn't throw an exception then clear an error
    // message for the given field.
    this._errors.delete(fieldName);

    return true;
  } catch (e) {
    if (e instanceof ValidationError) {
      this._errors.set(fieldName, e.message);
    } else {
      throw e;
    }

    return false;
  }
};

methods.validate = function(fieldName) {
  // Run validation for current and parent classes. The "isValid" variable will
  // be set to true when all validators for all classes passed validation.
  var isValid = Astro.utils.everyClass(this.constructor, function(Class) {
    // Get list of all validators for given class.
    var validators = Class.getValidators();

    if (fieldName) {
      // Get validation function for given field name.
      var validatorFunction = validators[fieldName];

      // Validate field only if there is validation function.
      if (validatorFunction) {
        return validateOne.call(this, validatorFunction, fieldName);
      } else {
        return true;
      }
    } else {
      // Take validators and run each one.
      return _.every(validators, validateOne, this);
    }
  }, this);

  return isValid;
};

methods.getValidationError = function() {
  console.warn('getValidationError had been renamed to getError');
  return this.getError.apply(this, arguments);
};

methods.hasValidationError = function() {
  console.warn('hasValidationError had been renamed to hasError');
  return this.hasError.apply(this, arguments);
};

methods.hasValidationErrors = function() {
  console.warn('hasValidationErrors had been renamed to hasErrors');
  return this.hasErrors.apply(this, arguments);
};

methods.getError = function(fieldName) {
  return this._errors.get(fieldName);
};

methods.getErrors = function() {
  return this._errors.all();
};

methods.hasError = function(fieldName) {
  return this._errors.has(fieldName);
};

methods.hasErrors = function() {
  return this._errors.size;
};

var events = {};

events.tojsonvalue = function(e) {
  var self = this;

  Tracker.nonreactive(function() {
    e.data.errors = self._errors.all();
  });
};

events.fromjsonvalue = function(e) {
  this._errors.set(e.data.errors);
};

onInitModule = function() {
  _.extend(Astro.BaseClass.prototype, methods);

  Astro.on('tojsonvalue', events.tojsonvalue);
  Astro.on('fromjsonvalue', events.fromjsonvalue);
};
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2015 - present Instructure, Inc.
 *
 * This file is part of Canvas.
 *
 * Canvas is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

import React from 'react'
import page from 'page'
import $ from 'jquery'
import classnames from 'classnames'
import {Mask, Overlay} from '@instructure/ui-overlays'
import FilePreviewInfoPanel from './FilePreviewInfoPanel'
import CollectionHandler from 'compiled/react_files/utils/collectionHandler'
import preventDefault from 'compiled/fn/preventDefault'

import _ from 'underscore'
import PropTypes from 'prop-types'
import customPropTypes from 'compiled/react_files/modules/customPropTypes'
import I18n from 'i18n!file_preview'
import File from 'compiled/models/File'
import FilesystemObject from 'compiled/models/FilesystemObject'
import codeToRemoveLater from './codeToRemoveLater'
import 'compiled/jquery.rails_flash_notifications'

export default class FilePreview extends React.PureComponent {
  static propTypes = {
    currentFolder: customPropTypes.folder,
    query: PropTypes.object,
    collection: PropTypes.object,
    params: PropTypes.object,
    isOpen: PropTypes.bool,
    closePreview: PropTypes.func,
    splat: PropTypes.string,
    usageRightsRequiredForContext: PropTypes.bool
  }

  state = {
    showInfoPanel: false,
    displayedItem: null
  }

  componentWillMount() {
    if (this.props.isOpen) {
      this.getItemsToView(this.props, items => this.setState(this.stateProperties(items)))
    }
  }

  componentDidMount() {
    return codeToRemoveLater.hideFileTreeFromPreviewInJaws()
  }

  componentWillReceiveProps(newProps) {
    if (newProps.isOpen) {
      this.getItemsToView(newProps, items => this.setState(this.stateProperties(items)))
    }
  }

  componentWillUnmount() {
    $(this.previewOverlay).on('keydown', this.handleKeyboardNavigation)
    return codeToRemoveLater.revertJawsChangesBackToNormal()
  }

  getRouteIdentifier = () => {
    if (this.props.query && this.props.query.search_term) {
      return '/search'
    } else if (this.props.splat) {
      return `/folder/${this.props.splat}`
    } else {
      return ''
    }
  }

  getNavigationParams = (opts = {id: null, except: []}) => {
    const obj = {
      preview: opts && opts.id,
      search_term: this.props.query.search_term || undefined,
      only_preview: this.props.query.only_preview || undefined,
      sort: this.props.query.sort || undefined,
      order: this.props.query.order || undefined
    }

    _.each(obj, (v, k) => {
      if (
        !v ||
        (opts.except && opts.except.length && (opts.except === k || opts.except.includes(k)))
      ) {
        delete obj[k]
      }
    })

    return obj
  }

  getItemsToView = (props, cb) => {
    if (typeof cb !== 'function')
      throw new Error(
        'getItemsToView(props: obj, callback: fn) requires `callback` to be a function'
      )
    // Sets up our collection that we will be using.
    let initialItem = null
    const onlyIdsToPreview = props.query.only_preview && props.query.only_preview.split(',')
    const files = props.query.search_term
      ? props.collection.models
      : props.currentFolder.files.models

    const otherItems = files.filter(file => {
      if (!onlyIdsToPreview) return true
      return onlyIdsToPreview.includes(file.id)
    })

    const visibleFile = props.query.preview && _.findWhere(files, {id: props.query.preview})

    if (!visibleFile) {
      const responseDataRequested = ['enhanced_preview_url']
      if (props.usageRightsRequiredForContext) {
        responseDataRequested.push('usage_rights')
      }
      new File({id: props.query.preview}, {preflightUrl: 'no/url/needed'})
        .fetch({data: $.param({include: responseDataRequested})})
        .success(file => {
          initialItem = new FilesystemObject(file)
          return cb({initialItem, otherItems})
        })
    } else {
      initialItem = visibleFile || (files.length ? files[0] : undefined)

      cb({initialItem, otherItems})
    }
  }

  setUpOtherItemsQuery = otherItems => otherItems.map(item => item.id).join(',')

  toggle = key => {
    const newState = {}
    newState[key] = !this.state[key]
    return () => {
      this.setState(newState, function() {
        if (key === 'showInfoPanel' && this.state.showInfoPanel) {
          $.screenReaderFlashMessage(I18n.t('Info panel displayed'))
        } else {
          $.screenReaderFlashMessage(I18n.t('Info panel hidden'))
        }
      })
    }
  }

  handleKeyboardNavigation = event => {
    if (!(event.keyCode === $.ui.keyCode.LEFT || event.keyCode === $.ui.keyCode.RIGHT)) {
      return null
    }
    let nextItem = null
    if (event.keyCode === $.ui.keyCode.LEFT) {
      nextItem = CollectionHandler.getPreviousInRelationTo(
        this.state.otherItems,
        this.state.displayedItem
      )
    }
    if (event.keyCode === $.ui.keyCode.RIGHT) {
      nextItem = CollectionHandler.getNextInRelationTo(
        this.state.otherItems,
        this.state.displayedItem
      )
    }

    page(`${this.getRouteIdentifier()}?${$.param(this.getNavigationParams({id: nextItem.id}))}`)
  }

  closeModal = () => {
    this.props.closePreview(
      `${this.getRouteIdentifier()}?${$.param(this.getNavigationParams({except: 'only_preview'}))}`
    )
  }

  stateProperties = items => ({
    initialItem: items.initialItem,
    displayedItem: items.initialItem,
    otherItems: items.otherItems
  })

  renderArrowLink = direction => {
    const nextItem =
      direction === 'left'
        ? CollectionHandler.getPreviousInRelationTo(this.state.otherItems, this.state.displayedItem)
        : CollectionHandler.getNextInRelationTo(this.state.otherItems, this.state.displayedItem)
    if (!nextItem) {
      return null
    }

    const linkText = direction === 'left' ? I18n.t('View previous file') : I18n.t('View next file')
    const baseUrl = page.base()
    return (

         page.clickHandler(e.nativeEvent)}
        >

            {linkText}
            <i aria-hidden="true" className={`icon-arrow-open-${direction}`} />

    )
  }

  renderPreview = () => {
    if (this.state.displayedItem && this.state.displayedItem.get('preview_url')) {
      const html = this.state.displayedItem.get('content-type') === 'text/html'
      const iFrameClasses = classnames({
        'ef-file-preview-frame': true,
        'ef-file-preview-frame-html': html,
        'attachment-html-iframe': html
      })

      return (

      )
    } else {
      return (

          {I18n.t('File not found')}

      )
    }
  }

  render() {
    const showInfoPanelClasses = classnames({
      'ef-file-preview-header-info': true,
      'ef-file-preview-button': true,
      'ef-file-preview-button--active': this.state.showInfoPanel
    })

    return (
      <Overlay
        ref={e => (this.previewOverlay = e)}
        open={this.props.isOpen}
        onDismiss={this.closeModal}
        onKeyDown={this.handleKeyboardNavigation}
        label={I18n.t('File Preview Overlay')}
        defaultFocusElement={() => this.closeButton}
        shouldContainFocus
        shouldReturnFocus
        unmountOnExit
      >
        <Mask theme={{background: 'rgba(0, 0, 0, 0.75)'}}>

                {this.state.initialItem ? this.state.initialItem.displayName() : ''}

                {this.state.displayedItem && !this.state.displayedItem.get('locked_for_user') && (

                    {` ${I18n.t('Download')}`}

                )}
                <button
                  type="button"
                  className={showInfoPanelClasses}
                  ref={e => (this.infoButton = e)}
                  onClick={this.toggle('showInfoPanel')}
                >
                  {/* Wrap content in a div because firefox doesn't support display: flex on buttons */}

                    {` ${I18n.t('Info')}`}

                </button>
                <button
                  type="button"
                  onClick={preventDefault(this.closeModal)}
                  ref={e => (this.closeButton = e)}
                  className="ef-file-preview-header-close ef-file-preview-button"
                >

                  {` ${I18n.t('Close')}`}
                </button>

              {this.state.otherItems &&
                this.state.otherItems.length &&
                this.renderArrowLink('left')}
              {this.renderPreview()}
              {this.state.otherItems &&
                this.state.otherItems.length &&
                this.renderArrowLink('right')}
              {this.state.showInfoPanel && (
                <FilePreviewInfoPanel
                  displayedItem={this.state.displayedItem}
                  getStatusMessage={this.getStatusMessage}
                  usageRightsRequiredForContext={this.props.usageRightsRequiredForContext}
                />
              )}

        </Mask>
      </Overlay>
    )
  }
}
<commit_msg>treesame commit of origin/stable/2020-08-12<commit_after>/*
 * Copyright (C) 2015 - present Instructure, Inc.
 *
 * This file is part of Canvas.
 *
 * Canvas is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

import React from 'react'
import page from 'page'
import $ from 'jquery'
import classnames from 'classnames'
import {Mask, Overlay} from '@instructure/ui-overlays'
import FilePreviewInfoPanel from './FilePreviewInfoPanel'
import CollectionHandler from 'compiled/react_files/utils/collectionHandler'
import preventDefault from 'compiled/fn/preventDefault'

import _ from 'underscore'
import PropTypes from 'prop-types'
import customPropTypes from 'compiled/react_files/modules/customPropTypes'
import I18n from 'i18n!file_preview'
import File from 'compiled/models/File'
import FilesystemObject from 'compiled/models/FilesystemObject'
import codeToRemoveLater from './codeToRemoveLater'
import 'compiled/jquery.rails_flash_notifications'

export default class FilePreview extends React.PureComponent {
  static propTypes = {
    currentFolder: customPropTypes.folder,
    query: PropTypes.object,
    collection: PropTypes.object,
    params: PropTypes.object,
    isOpen: PropTypes.bool,
    closePreview: PropTypes.func,
    splat: PropTypes.string,
    usageRightsRequiredForContext: PropTypes.bool
  }

  state = {
    showInfoPanel: false,
    displayedItem: null
  }

  componentWillMount() {
    if (this.props.isOpen) {
      this.getItemsToView(this.props, items => this.setState(this.stateProperties(items)))
    }
  }

  componentDidMount() {
    return codeToRemoveLater.hideFileTreeFromPreviewInJaws()
  }

  componentWillReceiveProps(newProps) {
    if (newProps.isOpen) {
      this.getItemsToView(newProps, items => this.setState(this.stateProperties(items)))
    }
  }

  componentWillUnmount() {
    $(this.previewOverlay).on('keydown', this.handleKeyboardNavigation)
    return codeToRemoveLater.revertJawsChangesBackToNormal()
  }

  getRouteIdentifier = () => {
    if (this.props.query && this.props.query.search_term) {
      return '/search'
    } else if (this.props.splat) {
      return `/folder/${this.props.splat}`
    } else {
      return ''
    }
  }

  getNavigationParams = (opts = {id: null, except: []}) => {
    const obj = {
      preview: opts && opts.id,
      search_term: this.props.query.search_term || undefined,
      only_preview: this.props.query.only_preview || undefined,
      sort: this.props.query.sort || undefined,
      order: this.props.query.order || undefined
    }

    _.each(obj, (v, k) => {
      if (
        !v ||
        (opts.except && opts.except.length && (opts.except === k || opts.except.includes(k)))
      ) {
        delete obj[k]
      }
    })

    return obj
  }

  getItemsToView = (props, cb) => {
    if (typeof cb !== 'function')
      throw new Error(
        'getItemsToView(props: obj, callback: fn) requires `callback` to be a function'
      )
    // Sets up our collection that we will be using.
    let initialItem = null
    const onlyIdsToPreview = props.query.only_preview && props.query.only_preview.split(',')
    const files = props.query.search_term
      ? props.collection.models
      : props.currentFolder.files.models

    const otherItems = files.filter(file => {
      if (!onlyIdsToPreview) return true
      return onlyIdsToPreview.includes(file.id)
    })

    const visibleFile = props.query.preview && _.findWhere(files, {id: props.query.preview})

    if (!visibleFile) {
      const responseDataRequested = ['enhanced_preview_url']
      if (props.usageRightsRequiredForContext) {
        responseDataRequested.push('usage_rights')
      }
      new File({id: props.query.preview}, {preflightUrl: 'no/url/needed'})
        .fetch({data: $.param({include: responseDataRequested})})
        .success(file => {
          initialItem = new FilesystemObject(file)
          return cb({initialItem, otherItems})
        })
    } else {
      initialItem = visibleFile || (files.length ? files[0] : undefined)

      cb({initialItem, otherItems})
    }
  }

  setUpOtherItemsQuery = otherItems => otherItems.map(item => item.id).join(',')

  toggle = key => {
    const newState = {}
    newState[key] = !this.state[key]
    return () => {
      this.setState(newState, function() {
        if (key === 'showInfoPanel' && this.state.showInfoPanel) {
          $.screenReaderFlashMessage(I18n.t('Info panel displayed'))
        } else {
          $.screenReaderFlashMessage(I18n.t('Info panel hidden'))
        }
      })
    }
  }

  handleKeyboardNavigation = event => {
    if (!(event.keyCode === $.ui.keyCode.LEFT || event.keyCode === $.ui.keyCode.RIGHT)) {
      return null
    }
    let nextItem = null
    if (event.keyCode === $.ui.keyCode.LEFT) {
      nextItem = CollectionHandler.getPreviousInRelationTo(
        this.state.otherItems,
        this.state.displayedItem
      )
    }
    if (event.keyCode === $.ui.keyCode.RIGHT) {
      nextItem = CollectionHandler.getNextInRelationTo(
        this.state.otherItems,
        this.state.displayedItem
      )
    }

    page(`${this.getRouteIdentifier()}?${$.param(this.getNavigationParams({id: nextItem.id}))}`)
  }

  closeModal = () => {
    this.props.closePreview(
      `${this.getRouteIdentifier()}?${$.param(this.getNavigationParams({except: 'only_preview'}))}`
    )
  }

  stateProperties = items => ({
    initialItem: items.initialItem,
    displayedItem: items.initialItem,
    otherItems: items.otherItems
  })

  renderArrowLink = direction => {
    const nextItem =
      direction === 'left'
        ? CollectionHandler.getPreviousInRelationTo(this.state.otherItems, this.state.displayedItem)
        : CollectionHandler.getNextInRelationTo(this.state.otherItems, this.state.displayedItem)
    if (!nextItem) {
      return null
    }

    const linkText = direction === 'left' ? I18n.t('View previous file') : I18n.t('View next file')
    const baseUrl = page.base()
    return (

         page.clickHandler(e.nativeEvent)}
        >

            {linkText}
            <i aria-hidden="true" className={`icon-arrow-open-${direction}`} />

    )
  }

  renderPreview = () => {
    if (this.state.displayedItem && this.state.displayedItem.get('preview_url')) {
      const html = this.state.displayedItem.get('content-type') === 'text/html'
      const iFrameClasses = classnames({
        'ef-file-preview-frame': true,
        'ef-file-preview-frame-html': html,
        'attachment-html-iframe': html
      })

      return (

      )
    } else {
      return (

          {I18n.t('File not found')}

      )
    }
  }

  render() {
    const showInfoPanelClasses = classnames({
      'ef-file-preview-header-info': true,
      'ef-file-preview-button': true,
      'ef-file-preview-button--active': this.state.showInfoPanel
    })

    return (
      <Overlay
        ref={e => (this.previewOverlay = e)}
        open={this.props.isOpen}
        onDismiss={this.closeModal}
        onKeyDown={this.handleKeyboardNavigation}
        label={I18n.t('File Preview Overlay')}
        defaultFocusElement={() => this.closeButton}
        shouldContainFocus
        shouldReturnFocus
        unmountOnExit
      >
        <Mask theme={{background: 'rgba(0, 0, 0, 0.75)'}}>

                {this.state.initialItem ? this.state.initialItem.displayName() : ''}

                {this.state.displayedItem && !this.state.displayedItem.get('locked_for_user') && (

                    {` ${I18n.t('Download')}`}

                )}
                <button
                  type="button"
                  className={showInfoPanelClasses}
                  ref={e => (this.infoButton = e)}
                  onClick={this.toggle('showInfoPanel')}
                >
                  {/* Wrap content in a div because firefox doesn't support display: flex on buttons */}

                    {` ${I18n.t('Info')}`}

                </button>
                <button
                  type="button"
                  onClick={preventDefault(this.closeModal)}
                  ref={e => (this.closeButton = e)}
                  className="ef-file-preview-header-close ef-file-preview-button"
                >

                  {` ${I18n.t('Close')}`}
                </button>

              {this.state.otherItems &&
                this.state.otherItems.length &&
                this.renderArrowLink('left')}
              {this.renderPreview()}
              {this.state.otherItems &&
                this.state.otherItems.length &&
                this.renderArrowLink('right')}
              {this.state.showInfoPanel && (
                <FilePreviewInfoPanel
                  displayedItem={this.state.displayedItem}
                  getStatusMessage={this.getStatusMessage}
                  usageRightsRequiredForContext={this.props.usageRightsRequiredForContext}
                />
              )}

        </Mask>
      </Overlay>
    )
  }
}
<|endoftext|>


<commit_before>/**
 * Autocomplete arguments
 *
 * api {string} api to point to
 * list {string} list (in the current site) to point to
 *
 * @module
 */

var dom = require('../services/dom'),
  db = require('../services/db'),
  site = require('../services/site'),
  cid = require('../services/cid'),
  getInput = require('../services/get-input');

/**
 * Converts array of strings to option elements.
 * @param {array} options
 * @returns {Element}
 */
function formatOptions(options) {

  var optionsEl;

  options = options.reduce(function (prev, curr) {
    return prev + '<option>' + curr + '</option>';
  }, '');

  optionsEl = dom.create(`
    <label>
      <select>
        ${ options }
      </select>
    </label>
  `);

  return optionsEl;
}

/**
 * Throw an error if missing requirements.
 * @param {string} api
 * @param {Element} existingInput
 */
function handleDevErrors(api, existingInput) {

  var missing;

  if (!api) {
    missing = 'an API';
  } else if (!existingInput) {
    missing = 'a text input';
  }

  if (missing) {
    throw new Error('Autocomplete requires ' + missing + '.');
  }

}

/**
 * get api from either the api or list args
 * @param {object} args
 * @param {string} [args.api]
 * @param {string} [args.list]
 * @returns {string|null}
 */
function getApi(args) {
  if (args.api) {
    return args.api;
  } else if (args.list) {
    return site.get('prefix') + '/lists/' + args.list;
  } else {
    return null;
  }
}

module.exports = function (result, args) {
  var api = getApi(args),
    existingInput = getInput(result.el),
    datalistId = 'autocomplete-' + cid(),
    datalist;

  handleDevErrors(api, existingInput);

  // Add element.
  datalist = document.createElement('datalist');
  result.el.appendChild(datalist);

  // Connect datalist to input.
  datalist.id = datalistId;
  existingInput.setAttribute('list', datalistId);

  // Todo: once lists become large, this will need to be optimized; perhaps using the 'input' event.
  existingInput.addEventListener('focus', function () {
    db.get(api)
      .then(formatOptions)
      .then(function (optionsEl) {
        dom.clearChildren(datalist);
        datalist.appendChild(optionsEl);
      });
  });

  return result;
};

// Export for unit testing
module.exports.formatOptions = formatOptions;
<commit_msg>Flatten list.<commit_after>/**
 * Autocomplete arguments
 *
 * api {string} api to point to
 * list {string} list (in the current site) to point to
 *
 * @module
 */

var _ = require('lodash'),
  dom = require('../services/dom'),
  db = require('../services/db'),
  site = require('../services/site'),
  cid = require('../services/cid'),
  getInput = require('../services/get-input');

function flattenText(items) {
  var hasTextProp = items[0] && typeof items[0].text === 'string';

  return hasTextProp ? _.pluck(items, 'text') : items;
}

/**
 * Converts array of strings to option elements.
 * @param {array} items
 * @returns {Element}
 */
function formatOptions(items) {
  var optionsEl,
    options = flattenText(items).reduce(function (prev, curr) {
      return prev + '<option>' + curr + '</option>';
    }, '');

  optionsEl = dom.create(`
    <label>
      <select>
        ${ options }
      </select>
    </label>
  `);

  return optionsEl;
}

/**
 * Throw an error if missing requirements.
 * @param {string} api
 * @param {Element} existingInput
 */
function handleDevErrors(api, existingInput) {

  var missing;

  if (!api) {
    missing = 'an API';
  } else if (!existingInput) {
    missing = 'a text input';
  }

  if (missing) {
    throw new Error('Autocomplete requires ' + missing + '.');
  }

}

/**
 * get api from either the api or list args
 * @param {object} args
 * @param {string} [args.api]
 * @param {string} [args.list]
 * @returns {string|null}
 */
function getApi(args) {
  if (args.api) {
    return args.api;
  } else if (args.list) {
    return site.get('prefix') + '/lists/' + args.list;
  } else {
    return null;
  }
}

module.exports = function (result, args) {
  var api = getApi(args),
    existingInput = getInput(result.el),
    datalistId = 'autocomplete-' + cid(),
    datalist;

  handleDevErrors(api, existingInput);

  // Add element.
  datalist = document.createElement('datalist');
  result.el.appendChild(datalist);

  // Connect datalist to input.
  datalist.id = datalistId;
  existingInput.setAttribute('list', datalistId);

  // Todo: once lists become large, this will need to be optimized; perhaps using the 'input' event.
  existingInput.addEventListener('focus', function () {
    db.get(api)
      .then(formatOptions)
      .then(function (optionsEl) {
        dom.clearChildren(datalist);
        datalist.appendChild(optionsEl);
      });
  });

  return result;
};

// Export for unit testing
module.exports.formatOptions = formatOptions;
<|endoftext|>


<commit_before>var assert = require('assert');
var common = require('../../common');
var crypto = require('crypto');

common.getTestConnection(function (err, connection) {
  assert.ifError(err);

  getMaxAllowedPacket(connection);
});

var oldMaxAllowedPacket;
function getMaxAllowedPacket(connection) {
  connection.query('SHOW VARIABLES WHERE Variable_name = ?', ['max_allowed_packet'], function(err, rows) {
    if (err) throw err;

    oldMaxAllowedPacket = Number(rows[0].Value);

    increaseMaxAllowedPacketIfNeeded(connection);
  });
}

function increaseMaxAllowedPacketIfNeeded(connection) {
  // Our test generates a SQL query a few bytes larger than 16 MB, but lets
  // leave a little margin:
  var minMaxAllowedPacket = 20 * 1024 * 1024;

  var newMaxAllowedPacket = (oldMaxAllowedPacket < minMaxAllowedPacket)
    ? minMaxAllowedPacket
    : oldMaxAllowedPacket;

  connection.query('SET GLOBAL max_allowed_packet = ?', [newMaxAllowedPacket], function(err, rows) {
    if (err) throw err;

    // We need to re-connect for this change to take effect, bah
    connection.end();
    connection = common.createConnection();

    // We need to wait for the re-connect to happen before starting the actual
    // test. That's because our buffer to hex shim in 0.4.x takes ~12 sec on
    // TravisCI, causing a MySQL connection timeout otherwise.
    connection.connect(function(err) {
      if (err) throw err;

      triggerLargeQueryAndResponsePackets(connection);
    });
  });
}

var length   = (Math.pow(256, 3) / 2) + 10; // Half, because of hex encoding
var trailing = 'tailing text';

function triggerLargeQueryAndResponsePackets(connection) {
  var random = crypto.pseudoRandomBytes || crypto.randomBytes; // Depends on node.js version
  var sql    = 'SELECT ? as bigField, ? as trailingField';

  random(length, function (err, buf) {
    assert.ifError(err);
    assert.equal(buf.length, length);

    connection.query(sql, [buf, trailing], function (err, rows) {
      assert.ifError(err);

      connection.query('SET GLOBAL max_allowed_packet = ?', [oldMaxAllowedPacket], assert.ifError);
      connection.end(function (err) {
        assert.ifError(err);
        assert.equal(rows.length, 1);
        assert.equal(rows[0].trailingField, trailing);
        assert.equal(rows[0].bigField.length, buf.length);
        assert.equal(rows[0].bigField.toString('base64'), buf.toString('base64'));
      });
    });
  });
}
<commit_msg>test: store the value in large packet test<commit_after>var assert = require('assert');
var common = require('../../common');
var crypto = require('crypto');

common.getTestConnection(function (err, connection) {
  assert.ifError(err);

  getMaxAllowedPacket(connection);
});

var oldMaxAllowedPacket;
function getMaxAllowedPacket(connection) {
  connection.query('SHOW VARIABLES WHERE Variable_name = ?', ['max_allowed_packet'], function (err, rows) {
    assert.ifError(err);

    oldMaxAllowedPacket = Number(rows[0].Value);

    increaseMaxAllowedPacketIfNeeded(connection);
  });
}

function increaseMaxAllowedPacketIfNeeded(connection) {
  // Our test generates a SQL query a few bytes larger than 16 MB, but lets
  // leave a little margin:
  var minMaxAllowedPacket = 20 * 1024 * 1024;

  var newMaxAllowedPacket = (oldMaxAllowedPacket < minMaxAllowedPacket)
    ? minMaxAllowedPacket
    : oldMaxAllowedPacket;

  connection.query('SET GLOBAL max_allowed_packet = ?', [newMaxAllowedPacket], function (err, rows) {
    assert.ifError(err);

    // We need to re-connect for this change to take effect, bah
    connection.end();
    connection = common.createConnection();

    // We need to wait for the re-connect to happen before starting the actual
    // test. That's because our buffer to hex shim in 0.4.x takes ~12 sec on
    // TravisCI, causing a MySQL connection timeout otherwise.
    connection.connect(function (err) {
      assert.ifError(err);

      triggerLargeQueryAndResponsePackets(connection);
    });
  });
}

var length = (Math.pow(256, 3) / 2) + 10; // Half, because of hex encoding
var random = crypto.pseudoRandomBytes || crypto.randomBytes; // Depends on node.js version
var table  = 'large_packet_test';

function triggerLargeQueryAndResponsePackets(connection) {
  random(length, function (err, buf) {
    assert.ifError(err);
    assert.equal(buf.length, length);

    common.useTestDb(connection);

    connection.query([
      'CREATE TEMPORARY TABLE ?? (',
      '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,',
      '`bb` longblob NOT NULL,',
      'PRIMARY KEY (`id`)',
      ') ENGINE=InnoDB DEFAULT CHARSET=utf8'
    ].join('\n'), [table], assert.ifError);

    connection.query('INSERT INTO ?? SET ?', [table, {bb: buf}], assert.ifError);

    connection.query('SELECT `id`, `bb` FROM ??', [table], function (err, rows) {
      assert.ifError(err);

      connection.query('SET GLOBAL max_allowed_packet = ?', [oldMaxAllowedPacket], assert.ifError);
      connection.end(function (err) {
        assert.ifError(err);
        assert.equal(rows.length, 1);
        assert.equal(rows[0].bb.length, buf.length);
        assert.equal(rows[0].bb.toString('base64'), buf.toString('base64'));
      });
    });
  });
}
<|endoftext|>


<commit_before>import webpack from 'webpack';
import cssnano from 'cssnano';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import config from '../../config';
import _debug from 'debug';
import path from 'path';

const paths = config.utils_paths;
const debug = _debug('app:webpack:_base');
debug('Create configuration.');

const CSS_LOADER = !config.compiler_css_modules
  ? 'css?sourceMap'
  : [
    'css?modules',
    'sourceMap',
    'importLoaders=1',
    'localIdentName=[name]__[local]___[hash:base64:5]'
  ].join('&');

const webpackConfig = {
  name: 'client',
  target: 'web',
  entry: {
    app: [
      'src/app.js' // ,
      // paths.base(config.dir_client) + '/app.js'
    ],
    vendor: config.compiler_vendor
  },
  output: {
    filename: `[name].[${config.compiler_hash_type}].js`,
    path: paths.base(config.dir_dist),
    publicPath: config.compiler_public_path
  },
  plugins: [
    new webpack.DefinePlugin(config.globals),
    new webpack.optimize.OccurrenceOrderPlugin(),
    new webpack.optimize.DedupePlugin(),
    new HtmlWebpackPlugin({
      template: paths.client('index.html'),
      hash: false,
      favicon: paths.client('static/img/favicon.png'),
      filename: 'index.html',
      inject: 'body',
      minify: {
        collapseWhitespace: true
      }
    })
  ],
  resolve: {
    root: paths.base(config.dir_client),
    extensions: ['', '.js', '.jsx']
  },
  module: {
    preLoaders: [
      {
        test: /\.js$/,
        loader: 'eslint',
        exclude: /node_modules/
      }
    ],
    loaders: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        loader: 'babel',
        query: {
          cacheDirectory: true,
          plugins: ['transform-runtime', 'add-module-exports'],
          presets: ['es2015', 'react', 'stage-0'],
          env: {
            development: {
              plugins: [
                ['react-transform', {
                  // omit HMR plugin by default and _only_ load in hot mode
                  transforms: [{
                    transform: 'react-transform-catch-errors',
                    imports: ['react', 'redbox-react']
                  }]
                }]
              ]
            }
          }
        }
      },
      {
        test: /\.json$/,
        loader: 'json'
      },
      {
        test: /\.scss$/,
        loaders: [
          'style',
          CSS_LOADER,
          'postcss',
          'sass'
        ]
      },
      {
        test: /\.css$/,
        loaders: [
          'style',
          CSS_LOADER,
          'postcss'
        ]
      },
      /* eslint-disable */
      { test: /\.woff(\?.*)?$/,  loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/font-woff' },
      { test: /\.woff2(\?.*)?$/, loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/font-woff2' },
      { test: /\.ttf(\?.*)?$/,   loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/octet-stream' },
      { test: /\.eot(\?.*)?$/,   loader: 'file?prefix=fonts/&name=[path][name].[ext]' },
      { test: /\.svg(\?.*)?$/,   loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=image/svg+xml' },
      { test: /\.(png|jpg)$/,    loader: 'url?limit=8192' }
      /* eslint-enable */
    ]
  },
  sassLoader: {
    includePaths: paths.client('styles')
  },
  postcss: [
    cssnano({
      sourcemap: true,
      autoprefixer: {
        add: true,
        remove: true,
        browsers: ['last 2 versions']
      },
      discardComments: {
        removeAll: true
      }
    })
  ],
  eslint: {
    configFile: paths.base('.eslintrc')
  }
};

// NOTE: this is a temporary workaround. I don't know how to get Karma
// to include the vendor bundle that webpack creates, so to get around that
// we remove the bundle splitting when webpack is used with Karma.
const commonChunkPlugin = new webpack.optimize.CommonsChunkPlugin({
  names: ['vendor']
});
commonChunkPlugin.__KARMA_IGNORE__ = true;

webpackConfig.plugins.push(commonChunkPlugin);

export default webpackConfig;
<commit_msg>Try relative path.<commit_after>import webpack from 'webpack';
import cssnano from 'cssnano';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import config from '../../config';
import _debug from 'debug';
import path from 'path';

const paths = config.utils_paths;
const debug = _debug('app:webpack:_base');
debug('Create configuration.');

const CSS_LOADER = !config.compiler_css_modules
  ? 'css?sourceMap'
  : [
    'css?modules',
    'sourceMap',
    'importLoaders=1',
    'localIdentName=[name]__[local]___[hash:base64:5]'
  ].join('&');

const webpackConfig = {
  name: 'client',
  target: 'web',
  entry: {
    app: [
      './src/app.js' // ,
      // paths.base(config.dir_client) + '/app.js'
    ],
    vendor: config.compiler_vendor
  },
  output: {
    filename: `[name].[${config.compiler_hash_type}].js`,
    path: paths.base(config.dir_dist),
    publicPath: config.compiler_public_path
  },
  plugins: [
    new webpack.DefinePlugin(config.globals),
    new webpack.optimize.OccurrenceOrderPlugin(),
    new webpack.optimize.DedupePlugin(),
    new HtmlWebpackPlugin({
      template: paths.client('index.html'),
      hash: false,
      favicon: paths.client('static/img/favicon.png'),
      filename: 'index.html',
      inject: 'body',
      minify: {
        collapseWhitespace: true
      }
    })
  ],
  resolve: {
    root: paths.base(config.dir_client),
    extensions: ['', '.js', '.jsx']
  },
  module: {
    preLoaders: [
      {
        test: /\.js$/,
        loader: 'eslint',
        exclude: /node_modules/
      }
    ],
    loaders: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        loader: 'babel',
        query: {
          cacheDirectory: true,
          plugins: ['transform-runtime', 'add-module-exports'],
          presets: ['es2015', 'react', 'stage-0'],
          env: {
            development: {
              plugins: [
                ['react-transform', {
                  // omit HMR plugin by default and _only_ load in hot mode
                  transforms: [{
                    transform: 'react-transform-catch-errors',
                    imports: ['react', 'redbox-react']
                  }]
                }]
              ]
            }
          }
        }
      },
      {
        test: /\.json$/,
        loader: 'json'
      },
      {
        test: /\.scss$/,
        loaders: [
          'style',
          CSS_LOADER,
          'postcss',
          'sass'
        ]
      },
      {
        test: /\.css$/,
        loaders: [
          'style',
          CSS_LOADER,
          'postcss'
        ]
      },
      /* eslint-disable */
      { test: /\.woff(\?.*)?$/,  loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/font-woff' },
      { test: /\.woff2(\?.*)?$/, loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/font-woff2' },
      { test: /\.ttf(\?.*)?$/,   loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/octet-stream' },
      { test: /\.eot(\?.*)?$/,   loader: 'file?prefix=fonts/&name=[path][name].[ext]' },
      { test: /\.svg(\?.*)?$/,   loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=image/svg+xml' },
      { test: /\.(png|jpg)$/,    loader: 'url?limit=8192' }
      /* eslint-enable */
    ]
  },
  sassLoader: {
    includePaths: paths.client('styles')
  },
  postcss: [
    cssnano({
      sourcemap: true,
      autoprefixer: {
        add: true,
        remove: true,
        browsers: ['last 2 versions']
      },
      discardComments: {
        removeAll: true
      }
    })
  ],
  eslint: {
    configFile: paths.base('.eslintrc')
  }
};

// NOTE: this is a temporary workaround. I don't know how to get Karma
// to include the vendor bundle that webpack creates, so to get around that
// we remove the bundle splitting when webpack is used with Karma.
const commonChunkPlugin = new webpack.optimize.CommonsChunkPlugin({
  names: ['vendor']
});
commonChunkPlugin.__KARMA_IGNORE__ = true;

webpackConfig.plugins.push(commonChunkPlugin);

export default webpackConfig;
<|endoftext|>


<commit_before>({
    //WebSQL is supported in only these modern browsers. http://caniuse.com/sql-storage
    browsers:["GOOGLECHROME", "SAFARI", "IPAD", "IPHONE", "ANDROID_PHONE", "ANDROID_TABLET"],
    setUp : function(cmp) {
		$A.test.overrideFunction($A.storageService, "selectAdapter", function() {
			return "websql";});
		$A.storageService.initStorage("browserdb", false, true, 1024, 200, 300, true, true);
    },

    resetCounters:function(cmp){
		cmp._storageModified = false;
		cmp._storageModifiedCounter = 0;
    },

    assertAfterStorageChange:function(cmp, callback){
		$A.test.addWaitFor(2, function() {
		    return cmp._storageModified?cmp._storageModifiedCounter:0;
		}, callback);
    },

    testGetName : {
		test : function(cmp) {
		    var storage = $A.storageService.getStorage("browserdb");
		    $A.test.assertEquals("websql", storage.getName());
		}
    },

    testClear:{
		test:[function(cmp){
		    $A.test.setTestTimeout(5000);
		    var storage = $A.storageService.getStorage("browserdb");
		    this.resetCounters(cmp);
		    storage.clear();
		    /*TODO: W-1620503 - auraStorage:modified is fired twice, once by AuraStorage and once by the Adapter
		     * this.assertAfterStorageChange(cmp, function(){
			    			$A.test.assertEquals(0, storage.getSize(),
			    				"Storage size not 0 after clear()");
						});*/
		    $A.test.addWaitFor(0, function(){return storage.getSize()});
		},
		/**
		 * Call clear() after the cache has some values
		 */
		function(cmp){
		    var storage = $A.storageService.getStorage("browserdb");
		    this.resetCounters(cmp);
		    storage.put("key1" , new Array(1024).join("x"));
		    /*W-1620503
		     * this.assertAfterStorageChange(cmp, function(){
						    debugger;
		    				    $A.test.assertEquals(1.00, storage.getSize().toFixed(2), "Storage size doesn't reflect size of items stored");
		    				} );*/
		    $A.test.addWaitFor(true, function(){
				return storage.getSize()>=1 && storage.getSize()<1.001
				}, function(){
				    storage.clear();
				    $A.test.addWaitFor(0, function(){return storage.getSize()});
				});
		},
		]
    },

    testGetSize:{
		test:[function(cmp){
		    $A.test.setTestTimeout(15000);
		    var storage = $A.storageService.getStorage("browserdb");
		    //One value
		    storage.put("key1" , new Array(1024).join("x")); //1kb
		    this.assertAfterGet(cmp, storage, "key1", function(){
			var item = cmp["key1"];
			$A.test.assertDefined(item);
			$A.test.assertTrue(storage.getSize()>=1 && storage.getSize()<1.001);
		    });
		},function(cmp){
		    var storage = $A.storageService.getStorage("browserdb");
		    //Two value to see that size is recalculated
		    storage.put("key2" , new Array(1024*5).join("y")); //5kb
		    this.assertAfterGet(cmp, storage, "key2", function(){
			var item = cmp["key2"];
			$A.test.assertDefined(item);
			$A.test.assertTrue(storage.getSize()>=6 && storage.getSize()<6.002);
		    });
		},function(cmp){
		    var storage = $A.storageService.getStorage("browserdb");
		    //Reset flags
		    cmp["key2"] = undefined;
		    cmp["key2flag"] = undefined;
		    //Dup value to see that only incremental value
		    storage.put("key2" , new Array(1024).join("z")); //1kb
		    this.assertAfterGet(cmp, storage, "key2", function(){
			var item = cmp["key2"];
			$A.test.assertDefined(item);
	        //waitFor size to change as an attempt to fix flappiness on autobuilds
	        $A.test.addWaitForWithFailureMessage(true, function(){
	            return storage.getSize()>=2 && storage.getSize()<2.002;
	        }, "Expected value of approx. 2, got: "+storage.getSize());
		    });
		}
		]
    },

    testGetMaxSize:{
		test:function(cmp){
		    //Max Size doesn't seem to mean anything incase of WebSQLAdapter. It just a transient variable.
		    $A.test.assertEquals(1, $A.storageService.getStorage("browserdb").getMaxSize(),
			    "Failed to configure max size of storage");
		}

    },

    testGet:{
		test:[
		 /**
		  * Bad key values
		  */
		 function(cmp){
		    $A.test.setTestTimeout(15000);
		    var storage = $A.storageService.getStorage("browserdb");
		    storage.get("key1", function(item) {$A.test.assertUndefinedOrNull(item);});
		    storage.get(undefined, function(item) { $A.test.assertUndefinedOrNull(item);});
		    storage.get(null, function(item) { $A.test.assertUndefinedOrNull(item);});
		    storage.get("", function(item) { $A.test.assertUndefinedOrNull(item);});
		},
		/**
		 * Insert a map as value.
		 */
		function(cmp){
		    var storage = $A.storageService.getStorage("browserdb");
		    var map = {"NBA": "Basketball"};
		    storage.put("sport", map);
		    //Assert that item was retrieved from storage
		    this.assertAfterGet(cmp, storage, "sport",
			    		function(){
						var item = cmp["sport"];
						$A.test.assertEquals("Basketball", item["NBA"], "Failed to retrieve map value");
					});
		},
		/**
		 * Insert a literal value
		 */
		function(cmp){
		    var storage = $A.storageService.getStorage("browserdb");
		    storage.put("sounds", "Boogaboo");
		    //Assert that item was retrieved from storage
		    this.assertAfterGet(cmp, storage, "sounds",
			    		function(){
						$A.test.assertEquals("Boogaboo", cmp["sounds"], "Failed to retrieve string value");
					});
		    /*TODO: W-1620507 cannot put boolean values
		     * storage.put("flag", false);
		    this.assertAfterGet(cmp, storage, "flag",
		    		function(){
					$A.test.assertFalse(cmp["flag"], "Failed to retrieve string value")
				});*/
		    storage.put("array", ["foo","bar"]);
		    this.assertAfterGet(cmp, storage, "array",
			    		function(){
						$A.test.assertEquals("foo", cmp["array"][0], "Failed to retrieve array value");
						$A.test.assertEquals("bar", cmp["array"][1], "Failed to retrieve all array values");
					});
		    storage.put("score", 999);
		    this.assertAfterGet(cmp, storage, "score",
			    		function(){
						$A.test.assertEquals(999, cmp["score"], "Failed to retrieve numeric value");
					});
		},
		/**
		 * Insert a action return value
		 */
		function(cmp){
		    var storage = $A.storageService.getStorage("browserdb");
		    var a = $A.get("c.aura://ComponentController.getComponent");
		    a.setParams({
	            "name" : 'auraStorageTest:teamFacet'
		    });
		    a.setCallback(cmp,function(a){
	    		//Verify that original action is usable
	    		$A.test.assertEquals("SUCCESS", a.getState())
	    		$A.test.assertDefined(a.getReturnValue);
	            $A.newCmpAsync(
	                    this,
	                    function(newCmp){
	                        $A.test.assertEquals("markup://auraStorageTest:teamFacet", newCmp.getDef().getDescriptor().toString());
	                        storage.put("actionResponse", a);
	                    },
	                    a.getReturnValue()
	            );
		    });
		    $A.enqueueAction(a);
		    $A.eventService.finishFiring();
		    this.assertAfterGet(cmp, storage, "actionResponse",
		    		function(){
					/*TODO: W-1620511 - actions are not flattened correctly, most properties are ommited
					 * //Verify that action is usable after it was retrieved from cache
					var item = cmp["actionResponse"];
					$A.test.assertEquals("SUCCESS", item.getState());
					$A.test.assertDefined(item.getReturnValue);
					$A.newCmpAsync(
	                    this,
	                    function(newCmp){
	                        $A.test.assertEquals("markup://auraStorageTest:teamFacet", newCmp.getDef().getDescriptor().toString());
	                    },
	                    a.getReturnValue()
	                );*/
				});
		}
		]

    },

    /**
     * Utillity method to verify cached value. Very helpful when insertion happens in an asynchronous fashion.
     */
    assertAfterGet:function(cmp, storage, key, callback){
		$A.test.addWaitFor(true, function(){
			storage.get(key, function(item) {
			    //Value
			    cmp[key] = item;
			    //Flag to signal item retrieval, cannot use the item itself as flag because item can be null
			    cmp[key+"flag"] = true;
			});
			return !$A.util.isUndefinedOrNull(cmp[key+"flag"]);
	     }, callback);
    },

    testPut:{
		test:[
		/**
		 * Insert bad values
		 */
		function(cmp){
		    $A.test.setTestTimeout(15000);
		    var storage = $A.storageService.getStorage("browserdb");
		    /*TODO: W-1620514 - TypeError: Cannot read property 'length' of undefined WebSQLAdapter.setItem()
		     * storage.put("unDefined", undefined);
		    this.assertAfterGet(cmp, storage, "unDefined",
			    		function(){$A.test.assertUndefinedOrNull(cmp["unDefined"], "Failed to put undefined value")});*/
		    storage.put("NULL", null);
		    this.assertAfterGet(cmp, storage, "NULL",
			    		function(){$A.test.assertUndefinedOrNull(cmp["NULL"], "Failed to put null value")});
		    /* TODO - W-1620514 Can't seem to insert an empty string as value
		     * storage.put("EMPTY", "");
		    this.assertAfterGet(cmp, storage, "EMPTY",
			    		function(){debugger; $A.test.assertEquals("", cmp["EMPTY"], "Failed to put an empty string")});*/
		},
		/**
		 * Insert bad keys
		 */
		function(cmp){
		    var storage = $A.storageService.getStorage("browserdb");
		    storage.put(null, "NULL");
		    storage.get(null, function(item) { $A.test.assertUndefinedOrNull(item);});
		    storage.put(undefined, "UNDEFINED");
		    storage.get(undefined,
		    		function(item) {
		    			//iOS implementation of WebSql doesn't store an entry if key is undefined but google chrome's implementation does
		    			// Test makes sure, if a browser does store, it stores the right value
		    			if(item){
		    				$A.test.assertEquals("UNDEFINED", item);
		    			}
		    		});
		    storage.put("", "EMPTY");
		    storage.get("", function(item) { $A.test.assertEquals("EMPTY", item);});
		},
		/**
		 * Inset duplicate keys
		 */
		function(cmp){
		    var storage = $A.storageService.getStorage("browserdb");
		    storage.put("dup", "ORIGINAL");
		    storage.get("dup", function(item) {
					    $A.test.assertEquals("ORIGINAL", item);
					    storage.put("dup", "DUPLICATE");
					    storage.get("dup", function(item) { $A.test.assertEquals("DUPLICATE", item);});
					});
		}
		]
    },

    testExpiry:{
    	//iOS has a size limit of how much a WebSql Db can take up. We have a fixed size implementation in WebSqlAdapter: W-1766465
    	browsers:["GOOGLECHROME", "SAFARI", "ANDROID_PHONE", "ANDROID_TABLET"],
		test:[function(cmp){
		    $A.test.setTestTimeout(30000);
		    //defaultExpiration of 5 seconds
		    $A.storageService.initStorage("testExpiry", false, true, 1024, 5, 300, true, true);
		    var storage = $A.storageService.getStorage("testExpiry");
		    storage.put("key", "Value");
		    this.assertAfterGet(cmp, storage, "key", function(){
			var item = cmp["key"];
			$A.test.assertDefined(item);
			cmp._time = new Date().getTime();
			$A.log(cmp._time);
			//Assert after 5 seconds
			$A.test.addWaitFor(true, function(){
			    			     return (new Date().getTime() - cmp._time)/1000 > 5;
			    			 },function(){
						     $A.log((new Date().getTime() - cmp._time)/1000);
			    			     storage.get("key",function(item){
			    				 $A.log("Even after expiry:" + item)
			    				 //TODO-W-1620521 Expiry is not synchronous, get() still fetches expired items
			    				 //$A.test.assertUndefinedOrNull(item, "Failed to expire items in cache");
			    			     });
			    			 })
		    });
		},function(cmp){
		    var storage = $A.storageService.getStorage("testExpiry");
		    $A.test.addWaitFor(0, function(){return storage.getSize()});
		}]
    }
})
<commit_msg>Merging origin/tbliss/master-pending-safarifix-webSqlAdapterTest into master<commit_after>({
    //WebSQL is supported in only these modern browsers. http://caniuse.com/sql-storage
    // TODO(W-1766465): Currently we hardcode the size of the websql database. This pops up a box in Safari that we
    //                  can't accept or override from the test.
    browsers:["GOOGLECHROME", "IPAD", "IPHONE", "ANDROID_PHONE", "ANDROID_TABLET"],
    setUp : function(cmp) {
		$A.test.overrideFunction($A.storageService, "selectAdapter", function() {
			return "websql";});
		$A.storageService.initStorage("browserdb", false, true, 1024, 200, 300, true, true);
    },

    resetCounters:function(cmp){
		cmp._storageModified = false;
		cmp._storageModifiedCounter = 0;
    },

    assertAfterStorageChange:function(cmp, callback){
		$A.test.addWaitFor(2, function() {
		    return cmp._storageModified?cmp._storageModifiedCounter:0;
		}, callback);
    },

    testGetName : {
		test : function(cmp) {
		    var storage = $A.storageService.getStorage("browserdb");
		    $A.test.assertEquals("websql", storage.getName());
		}
    },

    testClear:{
		test:[function(cmp){
		    $A.test.setTestTimeout(5000);
		    var storage = $A.storageService.getStorage("browserdb");
		    this.resetCounters(cmp);
		    storage.clear();
		    /*TODO: W-1620503 - auraStorage:modified is fired twice, once by AuraStorage and once by the Adapter
		     * this.assertAfterStorageChange(cmp, function(){
			    			$A.test.assertEquals(0, storage.getSize(),
			    				"Storage size not 0 after clear()");
						});*/
		    $A.test.addWaitFor(0, function(){return storage.getSize()});
		},
		/**
		 * Call clear() after the cache has some values
		 */
		function(cmp){
		    var storage = $A.storageService.getStorage("browserdb");
		    this.resetCounters(cmp);
		    storage.put("key1" , new Array(1024).join("x"));
		    /*W-1620503
		     * this.assertAfterStorageChange(cmp, function(){
						    debugger;
		    				    $A.test.assertEquals(1.00, storage.getSize().toFixed(2), "Storage size doesn't reflect size of items stored");
		    				} );*/
		    $A.test.addWaitFor(true, function(){
				return storage.getSize()>=1 && storage.getSize()<1.001
				}, function(){
				    storage.clear();
				    $A.test.addWaitFor(0, function(){return storage.getSize()});
				});
		},
		]
    },

    testGetSize:{
		test:[function(cmp){
		    $A.test.setTestTimeout(15000);
		    var storage = $A.storageService.getStorage("browserdb");
		    //One value
		    storage.put("key1" , new Array(1024).join("x")); //1kb
		    this.assertAfterGet(cmp, storage, "key1", function(){
			var item = cmp["key1"];
			$A.test.assertDefined(item);
			$A.test.assertTrue(storage.getSize()>=1 && storage.getSize()<1.001);
		    });
		},function(cmp){
		    var storage = $A.storageService.getStorage("browserdb");
		    //Two value to see that size is recalculated
		    storage.put("key2" , new Array(1024*5).join("y")); //5kb
		    this.assertAfterGet(cmp, storage, "key2", function(){
			var item = cmp["key2"];
			$A.test.assertDefined(item);
			$A.test.assertTrue(storage.getSize()>=6 && storage.getSize()<6.002);
		    });
		},function(cmp){
		    var storage = $A.storageService.getStorage("browserdb");
		    //Reset flags
		    cmp["key2"] = undefined;
		    cmp["key2flag"] = undefined;
		    //Dup value to see that only incremental value
		    storage.put("key2" , new Array(1024).join("z")); //1kb
		    this.assertAfterGet(cmp, storage, "key2", function(){
			var item = cmp["key2"];
			$A.test.assertDefined(item);
	        //waitFor size to change as an attempt to fix flappiness on autobuilds
	        $A.test.addWaitForWithFailureMessage(true, function(){
	            return storage.getSize()>=2 && storage.getSize()<2.002;
	        }, "Expected value of approx. 2, got: "+storage.getSize());
		    });
		}
		]
    },

    testGetMaxSize:{
		test:function(cmp){
		    //Max Size doesn't seem to mean anything incase of WebSQLAdapter. It just a transient variable.
		    $A.test.assertEquals(1, $A.storageService.getStorage("browserdb").getMaxSize(),
			    "Failed to configure max size of storage");
		}

    },

    testGet:{
		test:[
		 /**
		  * Bad key values
		  */
		 function(cmp){
		    $A.test.setTestTimeout(15000);
		    var storage = $A.storageService.getStorage("browserdb");
		    storage.get("key1", function(item) {$A.test.assertUndefinedOrNull(item);});
		    storage.get(undefined, function(item) { $A.test.assertUndefinedOrNull(item);});
		    storage.get(null, function(item) { $A.test.assertUndefinedOrNull(item);});
		    storage.get("", function(item) { $A.test.assertUndefinedOrNull(item);});
		},
		/**
		 * Insert a map as value.
		 */
		function(cmp){
		    var storage = $A.storageService.getStorage("browserdb");
		    var map = {"NBA": "Basketball"};
		    storage.put("sport", map);
		    //Assert that item was retrieved from storage
		    this.assertAfterGet(cmp, storage, "sport",
			    		function(){
						var item = cmp["sport"];
						$A.test.assertEquals("Basketball", item["NBA"], "Failed to retrieve map value");
					});
		},
		/**
		 * Insert a literal value
		 */
		function(cmp){
		    var storage = $A.storageService.getStorage("browserdb");
		    storage.put("sounds", "Boogaboo");
		    //Assert that item was retrieved from storage
		    this.assertAfterGet(cmp, storage, "sounds",
			    		function(){
						$A.test.assertEquals("Boogaboo", cmp["sounds"], "Failed to retrieve string value");
					});
		    /*TODO: W-1620507 cannot put boolean values
		     * storage.put("flag", false);
		    this.assertAfterGet(cmp, storage, "flag",
		    		function(){
					$A.test.assertFalse(cmp["flag"], "Failed to retrieve string value")
				});*/
		    storage.put("array", ["foo","bar"]);
		    this.assertAfterGet(cmp, storage, "array",
			    		function(){
						$A.test.assertEquals("foo", cmp["array"][0], "Failed to retrieve array value");
						$A.test.assertEquals("bar", cmp["array"][1], "Failed to retrieve all array values");
					});
		    storage.put("score", 999);
		    this.assertAfterGet(cmp, storage, "score",
			    		function(){
						$A.test.assertEquals(999, cmp["score"], "Failed to retrieve numeric value");
					});
		},
		/**
		 * Insert a action return value
		 */
		function(cmp){
		    var storage = $A.storageService.getStorage("browserdb");
		    var a = $A.get("c.aura://ComponentController.getComponent");
		    a.setParams({
	            "name" : 'auraStorageTest:teamFacet'
		    });
		    a.setCallback(cmp,function(a){
	    		//Verify that original action is usable
	    		$A.test.assertEquals("SUCCESS", a.getState())
	    		$A.test.assertDefined(a.getReturnValue);
	            $A.newCmpAsync(
	                    this,
	                    function(newCmp){
	                        $A.test.assertEquals("markup://auraStorageTest:teamFacet", newCmp.getDef().getDescriptor().toString());
	                        storage.put("actionResponse", a);
	                    },
	                    a.getReturnValue()
	            );
		    });
		    $A.enqueueAction(a);
		    $A.eventService.finishFiring();
		    this.assertAfterGet(cmp, storage, "actionResponse",
		    		function(){
					/*TODO: W-1620511 - actions are not flattened correctly, most properties are ommited
					 * //Verify that action is usable after it was retrieved from cache
					var item = cmp["actionResponse"];
					$A.test.assertEquals("SUCCESS", item.getState());
					$A.test.assertDefined(item.getReturnValue);
					$A.newCmpAsync(
	                    this,
	                    function(newCmp){
	                        $A.test.assertEquals("markup://auraStorageTest:teamFacet", newCmp.getDef().getDescriptor().toString());
	                    },
	                    a.getReturnValue()
	                );*/
				});
		}
		]

    },

    /**
     * Utillity method to verify cached value. Very helpful when insertion happens in an asynchronous fashion.
     */
    assertAfterGet:function(cmp, storage, key, callback){
		$A.test.addWaitFor(true, function(){
			storage.get(key, function(item) {
			    //Value
			    cmp[key] = item;
			    //Flag to signal item retrieval, cannot use the item itself as flag because item can be null
			    cmp[key+"flag"] = true;
			});
			return !$A.util.isUndefinedOrNull(cmp[key+"flag"]);
	     }, callback);
    },

    testPut:{
		test:[
		/**
		 * Insert bad values
		 */
		function(cmp){
		    $A.test.setTestTimeout(15000);
		    var storage = $A.storageService.getStorage("browserdb");
		    /*TODO: W-1620514 - TypeError: Cannot read property 'length' of undefined WebSQLAdapter.setItem()
		     * storage.put("unDefined", undefined);
		    this.assertAfterGet(cmp, storage, "unDefined",
			    		function(){$A.test.assertUndefinedOrNull(cmp["unDefined"], "Failed to put undefined value")});*/
		    storage.put("NULL", null);
		    this.assertAfterGet(cmp, storage, "NULL",
			    		function(){$A.test.assertUndefinedOrNull(cmp["NULL"], "Failed to put null value")});
		    /* TODO - W-1620514 Can't seem to insert an empty string as value
		     * storage.put("EMPTY", "");
		    this.assertAfterGet(cmp, storage, "EMPTY",
			    		function(){debugger; $A.test.assertEquals("", cmp["EMPTY"], "Failed to put an empty string")});*/
		},
		/**
		 * Insert bad keys
		 */
		function(cmp){
		    var storage = $A.storageService.getStorage("browserdb");
		    storage.put(null, "NULL");
		    storage.get(null, function(item) { $A.test.assertUndefinedOrNull(item);});
		    storage.put(undefined, "UNDEFINED");
		    storage.get(undefined,
		    		function(item) {
		    			//iOS implementation of WebSql doesn't store an entry if key is undefined but google chrome's implementation does
		    			// Test makes sure, if a browser does store, it stores the right value
		    			if(item){
		    				$A.test.assertEquals("UNDEFINED", item);
		    			}
		    		});
		    storage.put("", "EMPTY");
		    storage.get("", function(item) { $A.test.assertEquals("EMPTY", item);});
		},
		/**
		 * Inset duplicate keys
		 */
		function(cmp){
		    var storage = $A.storageService.getStorage("browserdb");
		    storage.put("dup", "ORIGINAL");
		    storage.get("dup", function(item) {
					    $A.test.assertEquals("ORIGINAL", item);
					    storage.put("dup", "DUPLICATE");
					    storage.get("dup", function(item) { $A.test.assertEquals("DUPLICATE", item);});
					});
		}
		]
    },

    testExpiry:{
    	//iOS has a size limit of how much a WebSql Db can take up. We have a fixed size implementation in WebSqlAdapter: W-1766465
    	browsers:["GOOGLECHROME", "SAFARI", "ANDROID_PHONE", "ANDROID_TABLET"],
		test:[function(cmp){
		    $A.test.setTestTimeout(30000);
		    //defaultExpiration of 5 seconds
		    $A.storageService.initStorage("testExpiry", false, true, 1024, 5, 300, true, true);
		    var storage = $A.storageService.getStorage("testExpiry");
		    storage.put("key", "Value");
		    this.assertAfterGet(cmp, storage, "key", function(){
			var item = cmp["key"];
			$A.test.assertDefined(item);
			cmp._time = new Date().getTime();
			$A.log(cmp._time);
			//Assert after 5 seconds
			$A.test.addWaitFor(true, function(){
			    			     return (new Date().getTime() - cmp._time)/1000 > 5;
			    			 },function(){
						     $A.log((new Date().getTime() - cmp._time)/1000);
			    			     storage.get("key",function(item){
			    				 $A.log("Even after expiry:" + item)
			    				 //TODO-W-1620521 Expiry is not synchronous, get() still fetches expired items
			    				 //$A.test.assertUndefinedOrNull(item, "Failed to expire items in cache");
			    			     });
			    			 })
		    });
		},function(cmp){
		    var storage = $A.storageService.getStorage("testExpiry");
		    $A.test.addWaitFor(0, function(){return storage.getSize()});
		}]
    }
})
<|endoftext|>