diff --git a/spec/n1-spec-runner.es6 b/spec/n1-spec-runner.es6 index 791b660d7..dec73b929 100644 --- a/spec/n1-spec-runner.es6 +++ b/spec/n1-spec-runner.es6 @@ -10,7 +10,7 @@ class N1SpecRunner { this.loadSettings = loadSettings this._extendGlobalWindow(); this._setupJasmine(); - N1SpecLoader.loadSpecs(loadSettings, this.jasmineEnv); + N1SpecLoader.loadSpecs(this.loadSettings, this.jasmineEnv); this.jasmineEnv.execute(); } diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee deleted file mode 100644 index 0ffda970b..000000000 --- a/spec/spec-helper.coffee +++ /dev/null @@ -1,424 +0,0 @@ -_ = require 'underscore' -_str = require 'underscore.string' -_ = _.extend(_, require('../src/config-utils')) - -fs = require 'fs-plus' -path = require 'path' - -require '../src/window' -NylasEnv.restoreWindowDimensions() - -require 'jasmine-json' - -Grim = require 'grim' -TimeOverride = require './time-override' -KeymapManager = require('../src/keymap-manager').default - -Config = require '../src/config' -pathwatcher = require 'pathwatcher' -{clipboard} = require 'electron' - -{Account, - Contact, - TaskQueue, - AccountStore, - DatabaseStore, - MailboxPerspective, - FocusedPerspectiveStore, - ComponentRegistry} = require "nylas-exports" - -NylasEnv.themes.loadBaseStylesheets() -NylasEnv.themes.requireStylesheet '../static/jasmine' -NylasEnv.themes.initialLoadComplete = true - -NylasEnv.keymaps.loadKeymaps() -styleElementsToRestore = NylasEnv.styles.getSnapshot() - -window.addEventListener 'core:close', -> window.close() -window.addEventListener 'beforeunload', -> - NylasEnv.storeWindowDimensions() - NylasEnv.saveSync() - -document.querySelector('html').style.overflow = 'initial' -document.querySelector('body').style.overflow = 'initial' - -# Allow document.title to be assigned in specs without screwing up spec window title -documentTitle = null -Object.defineProperty document, 'title', - get: -> documentTitle - set: (title) -> documentTitle = title - -jasmine.getEnv().addEqualityTester(_.isEqual) # Use underscore's definition of equality for toEqual assertions - -# ---------------------------------------------------- -# Custom log reporter to pointpoint console statements -# ---------------------------------------------------- - -# util = require('util') -# console.inspect = (args...) -> -# arg = args -# if (args.length is 1) -# arg = args[0] -# console.log(util.inspect(arg)) -# -# original_log = console.log -# original_warn = console.warn -# original_error = console.error -# -# class JasmineConsoleReporter -# reportSpecStarting: (spec) -> -# withContext = (log) -> -# return -> -# if arguments[0] is '.' -# log(arguments...) -# else -# log("[#{spec.getFullName()}] #{arguments[0]}", Array(arguments)[1..-1]...) -# console.log = withContext(original_log) -# console.warn = withContext(original_warn) -# console.error = withContext(original_error) -# -# reportSpecResults: (result) -> -# if console.log isnt original_log -# console.log = original_log -# if console.warn isnt original_warn -# console.warn = original_warn -# if console.error isnt original_error -# console.error = original_error -# -# jasmine.getEnv().addReporter(new JasmineConsoleReporter()) - -# -# - -if process.env.JANKY_SHA1 and process.platform is 'win32' - jasmine.getEnv().defaultTimeoutInterval = 60000 -else - jasmine.getEnv().defaultTimeoutInterval = 250 - -specPackageName = null -specPackagePath = null -isCoreSpec = false - -{specDirectory, resourcePath} = NylasEnv.getLoadSettings() - -if specDirectory - specPackagePath = path.resolve(specDirectory, '..') - try - specPackageName = JSON.parse(fs.readFileSync(path.join(specPackagePath, 'package.json')))?.name - -isCoreSpec = specDirectory == fs.realpathSync(__dirname) - -# Override ReactTestUtils.renderIntoDocument so that -# we can remove all the created elements after the test completes. -React = require "react" -ReactDOM = require "react-dom" - -ReactTestUtils = require('react-addons-test-utils') -ReactTestUtils.scryRenderedComponentsWithTypeAndProps = (root, type, props) -> - if not root then throw new Error("Must supply a root to scryRenderedComponentsWithTypeAndProps") - _.compact _.map ReactTestUtils.scryRenderedComponentsWithType(root, type), (el) -> - if _.isEqual(_.pick(el.props, Object.keys(props)), props) - return el - else - return false - -ReactTestUtils.scryRenderedDOMComponentsWithAttr = (root, attrName, attrValue) -> - ReactTestUtils.findAllInRenderedTree root, (inst) -> - inst[attrName] and (!attrValue or inst[attrName] is attrValue) - -ReactTestUtils.findRenderedDOMComponentWithAttr = (root, attrName, attrValue) -> - all = ReactTestUtils.scryRenderedDOMComponentsWithAttr(root, attrName, attrValue) - if all.length is not 1 - throw new Error("Did not find exactly one match for data attribute: #{attrName} with value: #{attrValue}") - all[0] - -ReactElementContainers = [] -ReactTestUtils.renderIntoDocument = (element) -> - container = document.createElement('div') - ReactElementContainers.push(container) - ReactDOM.render(element, container) - -ReactTestUtils.unmountAll = -> - for container in ReactElementContainers - ReactDOM.unmountComponentAtNode(container) - ReactElementContainers = [] - -# So it passes the Utils.isTempId test -window.TEST_ACCOUNT_CLIENT_ID = "local-test-account-client-id" -window.TEST_ACCOUNT_ID = "test-account-server-id" -window.TEST_ACCOUNT_EMAIL = "tester@nylas.com" -window.TEST_ACCOUNT_NAME = "Nylas Test" -window.TEST_PLUGIN_ID = "test-plugin-id-123" -window.TEST_ACCOUNT_ALIAS_EMAIL = "tester+alternative@nylas.com" - -window.TEST_TIME_ZONE = "America/Los_Angeles" -moment = require('moment-timezone') -# moment-round upon require patches `moment` with new functions. -require('moment-round') - -# This date was chosen because it's close to a DST boundary -window.testNowMoment = -> - moment.tz("2016-03-15 12:00", TEST_TIME_ZONE) - -# We need to mock the config even before `beforeEach` runs because it gets -# accessed on module definitions -fakePersistedConfig = {env: 'production'} -NylasEnv.config = new Config() -NylasEnv.config.settings = fakePersistedConfig - -beforeEach -> - NylasEnv.testOrganizationUnit = null - Grim.clearDeprecations() if isCoreSpec - ComponentRegistry._clear() - global.localStorage.clear() - - DatabaseStore._transactionQueue = undefined - - ## If we don't spy on DatabaseStore._query, then - #`DatabaseStore.inTransaction` will never complete and cause all tests - #that depend on transactions to hang. - # - # @_query("BEGIN IMMEDIATE TRANSACTION") never resolves because - # DatabaseStore._query never runs because the @_open flag is always - # false because we never setup the DB when `NylasEnv.inSpecMode` is - # true. - spyOn(DatabaseStore, '_query').andCallFake => Promise.resolve([]) - - TaskQueue._queue = [] - TaskQueue._completed = [] - TaskQueue._onlineStatus = true - - documentTitle = null - NylasEnv.styles.restoreSnapshot(styleElementsToRestore) - NylasEnv.workspaceViewParentSelector = '#jasmine-content' - - NylasEnv.packages.packageStates = {} - - serializedWindowState = null - - spyOn(NylasEnv, 'saveSync') - - TimeOverride.resetTime() - TimeOverride.enableSpies() - - spy = spyOn(NylasEnv.packages, 'resolvePackagePath').andCallFake (packageName) -> - if specPackageName and packageName is specPackageName - resolvePackagePath(specPackagePath) - else - resolvePackagePath(packageName) - resolvePackagePath = _.bind(spy.originalValue, NylasEnv.packages) - - # prevent specs from modifying N1's menus - spyOn(NylasEnv.menu, 'sendToBrowserProcess') - - # Log in a fake user, and ensure that accountForId, etc. work - AccountStore._accounts = [ - new Account({ - provider: "gmail" - name: TEST_ACCOUNT_NAME - emailAddress: TEST_ACCOUNT_EMAIL - organizationUnit: NylasEnv.testOrganizationUnit || 'label' - clientId: TEST_ACCOUNT_CLIENT_ID - serverId: TEST_ACCOUNT_ID, - aliases: [ - "#{TEST_ACCOUNT_NAME} Alternate <#{TEST_ACCOUNT_ALIAS_EMAIL}>" - ] - }), - new Account({ - provider: "gmail" - name: 'Second' - emailAddress: 'second@gmail.com' - organizationUnit: NylasEnv.testOrganizationUnit || 'label' - clientId: 'second-test-account-id' - serverId: 'second-test-account-id' - aliases: [ - 'Second Support ' - 'Second Alternate ' - 'Second ' - ] - }) - ] - - FocusedPerspectiveStore._current = MailboxPerspective.forNothing() - - # reset config before each spec; don't load or save from/to `config.json` - fakePersistedConfig = {env: 'production'} - spyOn(Config::, 'getRawValues').andCallFake => - fakePersistedConfig - spyOn(Config::, 'setRawValue').andCallFake (keyPath, value) -> - if (keyPath) - _.setValueForKeyPath(fakePersistedConfig, keyPath, value) - else - fakePersistedConfig = value - this.load() - NylasEnv.config = new Config() - NylasEnv.loadConfig() - - spyOn(pathwatcher.File.prototype, "detectResurrectionAfterDelay").andCallFake -> - @detectResurrection() - - clipboardContent = 'initial clipboard content' - spyOn(clipboard, 'writeText').andCallFake (text) -> clipboardContent = text - spyOn(clipboard, 'readText').andCallFake -> clipboardContent - - advanceClock(1000) - addCustomMatchers(this) - - TimeOverride.resetSpyData() - - -afterEach -> - NylasEnv.packages.deactivatePackages() - NylasEnv.menu.template = [] - - NylasEnv.themes.removeStylesheet('global-editor-styles') - - if NylasEnv.state - delete NylasEnv.state.packageStates - - unless window.debugContent - document.getElementById('jasmine-content').innerHTML = '' - ReactTestUtils.unmountAll() - - jasmine.unspy(NylasEnv, 'saveSync') - ensureNoPathSubscriptions() - waits(0) # yield to ui thread to make screen update more frequently - -ensureNoPathSubscriptions = -> - watchedPaths = pathwatcher.getWatchedPaths() - pathwatcher.closeAllWatchers() - if watchedPaths.length > 0 - throw new Error("Leaking subscriptions for paths: " + watchedPaths.join(", ")) - -ensureNoDeprecatedFunctionsCalled = -> - deprecations = Grim.getDeprecations() - if deprecations.length > 0 - originalPrepareStackTrace = Error.prepareStackTrace - Error.prepareStackTrace = (error, stack) -> - output = [] - for deprecation in deprecations - output.push "#{deprecation.originName} is deprecated. #{deprecation.message}" - output.push _str.repeat("-", output[output.length - 1].length) - for stack in deprecation.getStacks() - for {functionName, location} in stack - output.push "#{functionName} -- #{location}" - output.push "" - output.join("\n") - - error = new Error("Deprecated function(s) #{deprecations.map(({originName}) -> originName).join ', '}) were called.") - error.stack - Error.prepareStackTrace = originalPrepareStackTrace - - throw error - -emitObject = jasmine.StringPrettyPrinter.prototype.emitObject -jasmine.StringPrettyPrinter.prototype.emitObject = (obj) -> - if obj.inspect - @append obj.inspect() - else - emitObject.call(this, obj) - -jasmine.unspy = (object, methodName) -> - throw new Error("Not a spy") unless object[methodName].hasOwnProperty('originalValue') - object[methodName] = object[methodName].originalValue - -jasmine.attachToDOM = (element) -> - jasmineContent = document.querySelector('#jasmine-content') - jasmineContent.appendChild(element) unless jasmineContent.contains(element) - -deprecationsSnapshot = null -jasmine.snapshotDeprecations = -> - deprecationsSnapshot = _.clone(Grim.deprecations) - -jasmine.restoreDeprecationsSnapshot = -> - Grim.deprecations = deprecationsSnapshot - -addCustomMatchers = (spec) -> - spec.addMatchers - toHaveLength: (expected) -> - if not @actual? - this.message = => "Expected object #{@actual} has no length method" - false - else - notText = if @isNot then " not" else "" - this.message = => "Expected object with length #{@actual.length} to#{notText} have length #{expected}" - @actual.length == expected - - -# ------------------------- -# Stubs for Window methods -# ------------------------- - -window.keyIdentifierForKey = (key) -> - if key.length > 1 # named key - key - else - charCode = key.toUpperCase().charCodeAt(0) - "U+00" + charCode.toString(16) - -window.keydownEvent = (key, properties={}) -> - originalEventProperties = {} - originalEventProperties.ctrl = properties.ctrlKey - originalEventProperties.alt = properties.altKey - originalEventProperties.shift = properties.shiftKey - originalEventProperties.cmd = properties.metaKey - originalEventProperties.target = properties.target?[0] ? properties.target - originalEventProperties.which = properties.which - originalEvent = KeymapManager.keydownEvent(key, originalEventProperties) - properties = _.extend({originalEvent}, properties) - new CustomEvent('keydown', properties) - -window.mouseEvent = (type, properties) -> - if properties.point - {point, editorView} = properties - {top, left} = @pagePixelPositionForPoint(editorView, point) - properties.pageX = left + 1 - properties.pageY = top + 1 - properties.originalEvent ?= {detail: 1} - new CustomEvent(type, properties) - -window.clickEvent = (properties={}) -> - window.mouseEvent("click", properties) - -window.mousedownEvent = (properties={}) -> - window.mouseEvent('mousedown', properties) - -window.mousemoveEvent = (properties={}) -> - window.mouseEvent('mousemove', properties) - -# See docs/writing-specs.md -window.waitsForPromise = (args...) -> - if args.length > 1 - { shouldReject, timeout } = args[0] - else - shouldReject = false - fn = _.last(args) - - window.waitsFor timeout, (moveOn) -> - promise = fn() - # Keep in mind we can't check `promise instanceof Promise` because parts of - # the app still use other Promise libraries Just see if it looks - # promise-like. - if not promise or not promise.then - jasmine.getEnv().currentSpec.fail("Expected callback to return a promise-like object, but it returned #{promise}") - moveOn() - else if shouldReject - promise.catch(moveOn) - promise.then -> - jasmine.getEnv().currentSpec.fail("Expected promise to be rejected, but it was resolved") - moveOn() - else - promise.then(moveOn) - promise.catch (error) -> - # I don't know what `pp` does, but for standard `new Error` objects, - # it sometimes returns "{ }". Catch this case and fall through to toString() - msg = jasmine.pp(error) - msg = error.toString() if msg is "{ }" - jasmine.getEnv().currentSpec.fail("Expected promise to be resolved, but it was rejected with #{msg}") - moveOn() - -window.pagePixelPositionForPoint = (editorView, point) -> - point = Point.fromObject point - top = editorView.renderedLines.offset().top + point.row * editorView.lineHeight - left = editorView.renderedLines.offset().left + point.column * editorView.charWidth - editorView.renderedLines.scrollLeft() - { top, left } diff --git a/spec/spec-helper.es6 b/spec/spec-helper.es6 new file mode 100644 index 000000000..41be1cf67 --- /dev/null +++ b/spec/spec-helper.es6 @@ -0,0 +1,342 @@ +import _ from 'underscore'; +import fs from 'fs-plus'; +import path from 'path'; + +import '../src/window'; +NylasEnv.restoreWindowDimensions(); + +import 'jasmine-json'; + +import Grim from 'grim'; +import TimeOverride from './time-override'; + +import Config from '../src/config'; +import pathwatcher from 'pathwatcher'; +import { clipboard } from 'electron'; + +import { Account, TaskQueue, AccountStore, DatabaseStore, MailboxPerspective, FocusedPerspectiveStore, ComponentRegistry } from "nylas-exports"; + +NylasEnv.themes.loadBaseStylesheets(); +NylasEnv.themes.requireStylesheet('../static/jasmine'); +NylasEnv.themes.initialLoadComplete = true; + +NylasEnv.keymaps.loadKeymaps(); +let styleElementsToRestore = NylasEnv.styles.getSnapshot(); + +window.addEventListener('core:close', () => window.close()); +window.addEventListener('beforeunload', function() { + NylasEnv.storeWindowDimensions(); + return NylasEnv.saveSync(); +} +); + +document.querySelector('html').style.overflow = 'initial'; +document.querySelector('body').style.overflow = 'initial'; + +// Allow document.title to be assigned in specs without screwing up spec window title +let documentTitle = null; +Object.defineProperty(document, 'title', { + get() { return documentTitle; }, + set(title) { return documentTitle = title; } +} +); + +jasmine.getEnv().addEqualityTester(_.isEqual); // Use underscore's definition of equality for toEqual assertions + +// +// + +if (process.env.JANKY_SHA1 && process.platform === 'win32') { + jasmine.getEnv().defaultTimeoutInterval = 60000; +} else { + jasmine.getEnv().defaultTimeoutInterval = 250; +} + +let specPackageName = null; +let specPackagePath = null; +let isCoreSpec = false; + +let {specDirectory, resourcePath} = NylasEnv.getLoadSettings(); + +if (specDirectory) { + specPackagePath = path.resolve(specDirectory, '..'); + try { + specPackageName = __guard__(JSON.parse(fs.readFileSync(path.join(specPackagePath, 'package.json'))), x => x.name); + } catch (error) {} +} + +isCoreSpec = specDirectory === fs.realpathSync(__dirname); + +// Override ReactTestUtils.renderIntoDocument so that +// we can remove all the created elements after the test completes. +import React from "react"; +import ReactDOM from "react-dom"; + +import ReactTestUtils from 'react-addons-test-utils'; +ReactTestUtils.scryRenderedComponentsWithTypeAndProps = function(root, type, props) { + if (!root) { throw new Error("Must supply a root to scryRenderedComponentsWithTypeAndProps"); } + return _.compact(_.map(ReactTestUtils.scryRenderedComponentsWithType(root, type), function(el) { + if (_.isEqual(_.pick(el.props, Object.keys(props)), props)) { + return el; + } else { + return false; + } + } + ) + ); +}; + +let ReactElementContainers = []; +ReactTestUtils.renderIntoDocument = function(element) { + let container = document.createElement('div'); + ReactElementContainers.push(container); + return ReactDOM.render(element, container); +}; + +ReactTestUtils.unmountAll = function() { + for (let i = 0; i < ReactElementContainers.length; i++) { + let container = ReactElementContainers[i]; + ReactDOM.unmountComponentAtNode(container); + } + return ReactElementContainers = []; +}; + +// So it passes the Utils.isTempId test +window.TEST_ACCOUNT_CLIENT_ID = "local-test-account-client-id"; +window.TEST_ACCOUNT_ID = "test-account-server-id"; +window.TEST_ACCOUNT_EMAIL = "tester@nylas.com"; +window.TEST_ACCOUNT_NAME = "Nylas Test"; +window.TEST_PLUGIN_ID = "test-plugin-id-123"; +window.TEST_ACCOUNT_ALIAS_EMAIL = "tester+alternative@nylas.com"; + +window.TEST_TIME_ZONE = "America/Los_Angeles"; +import moment from 'moment-timezone'; +// moment-round upon require patches `moment` with new functions. +import 'moment-round'; + +// This date was chosen because it's close to a DST boundary +window.testNowMoment = () => moment.tz("2016-03-15 12:00", TEST_TIME_ZONE); + +// We need to mock the config even before `beforeEach` runs because it gets +// accessed on module definitions +let fakePersistedConfig = {env: 'production'}; +NylasEnv.config = new Config(); +NylasEnv.config.settings = fakePersistedConfig; + +beforeEach(function() { + NylasEnv.testOrganizationUnit = null; + if (isCoreSpec) { Grim.clearDeprecations(); } + ComponentRegistry._clear(); + global.localStorage.clear(); + + DatabaseStore._transactionQueue = undefined; + + //# If we don't spy on DatabaseStore._query, then + //`DatabaseStore.inTransaction` will never complete and cause all tests + //that depend on transactions to hang. + // + // @_query("BEGIN IMMEDIATE TRANSACTION") never resolves because + // DatabaseStore._query never runs because the @_open flag is always + // false because we never setup the DB when `NylasEnv.inSpecMode` is + // true. + spyOn(DatabaseStore, '_query').andCallFake(() => Promise.resolve([])); + + TaskQueue._queue = []; + TaskQueue._completed = []; + TaskQueue._onlineStatus = true; + + documentTitle = null; + NylasEnv.styles.restoreSnapshot(styleElementsToRestore); + NylasEnv.workspaceViewParentSelector = '#jasmine-content'; + + NylasEnv.packages.packageStates = {}; + + let serializedWindowState = null; + + spyOn(NylasEnv, 'saveSync'); + + TimeOverride.resetTime(); + TimeOverride.enableSpies(); + + let spy = spyOn(NylasEnv.packages, 'resolvePackagePath').andCallFake(function(packageName) { + if (specPackageName && packageName === specPackageName) { + return resolvePackagePath(specPackagePath); + } else { + return resolvePackagePath(packageName); + } + }); + var resolvePackagePath = _.bind(spy.originalValue, NylasEnv.packages); + + // prevent specs from modifying N1's menus + spyOn(NylasEnv.menu, 'sendToBrowserProcess'); + + // Log in a fake user, and ensure that accountForId, etc. work + AccountStore._accounts = [ + new Account({ + provider: "gmail", + name: TEST_ACCOUNT_NAME, + emailAddress: TEST_ACCOUNT_EMAIL, + organizationUnit: NylasEnv.testOrganizationUnit || 'label', + clientId: TEST_ACCOUNT_CLIENT_ID, + serverId: TEST_ACCOUNT_ID, + aliases: [ + `${TEST_ACCOUNT_NAME} Alternate <${TEST_ACCOUNT_ALIAS_EMAIL}>` + ] + }), + new Account({ + provider: "gmail", + name: 'Second', + emailAddress: 'second@gmail.com', + organizationUnit: NylasEnv.testOrganizationUnit || 'label', + clientId: 'second-test-account-id', + serverId: 'second-test-account-id', + aliases: [ + 'Second Support ', + 'Second Alternate ', + 'Second ' + ] + }) + ]; + + FocusedPerspectiveStore._current = MailboxPerspective.forNothing(); + + // reset config before each spec; don't load or save from/to `config.json` + fakePersistedConfig = {env: 'production'}; + spyOn(Config.prototype, 'getRawValues').andCallFake(() => { + return fakePersistedConfig; + } + ); + spyOn(Config.prototype, 'setRawValue').andCallFake(function(keyPath, value) { + if (keyPath) { + _.setValueForKeyPath(fakePersistedConfig, keyPath, value); + } else { + fakePersistedConfig = value; + } + return this.load(); + }); + NylasEnv.config = new Config(); + NylasEnv.loadConfig(); + + spyOn(pathwatcher.File.prototype, "detectResurrectionAfterDelay").andCallFake(function() { + return this.detectResurrection(); + }); + + let clipboardContent = 'initial clipboard content'; + spyOn(clipboard, 'writeText').andCallFake(text => clipboardContent = text); + spyOn(clipboard, 'readText').andCallFake(() => clipboardContent); + + advanceClock(1000); + addCustomMatchers(this); + + return TimeOverride.resetSpyData(); +}); + + +afterEach(function() { + NylasEnv.packages.deactivatePackages(); + NylasEnv.menu.template = []; + + NylasEnv.themes.removeStylesheet('global-editor-styles'); + + if (NylasEnv.state) { + delete NylasEnv.state.packageStates; + } + + if (!window.debugContent) { + document.getElementById('jasmine-content').innerHTML = ''; + } + ReactTestUtils.unmountAll(); + + jasmine.unspy(NylasEnv, 'saveSync'); + ensureNoPathSubscriptions(); + return waits(0); +}); // yield to ui thread to make screen update more frequently + +var ensureNoPathSubscriptions = function() { + let watchedPaths = pathwatcher.getWatchedPaths(); + pathwatcher.closeAllWatchers(); + if (watchedPaths.length > 0) { + throw new Error(`Leaking subscriptions for paths: ${watchedPaths.join(", ")}`); + } +}; + +let { emitObject } = jasmine.StringPrettyPrinter.prototype; +jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { + if (obj.inspect) { + return this.append(obj.inspect()); + } else { + return emitObject.call(this, obj); + } +}; + +jasmine.unspy = function(object, methodName) { + if (!object[methodName].hasOwnProperty('originalValue')) { throw new Error("Not a spy"); } + return object[methodName] = object[methodName].originalValue; +}; + +jasmine.attachToDOM = function(element) { + let jasmineContent = document.querySelector('#jasmine-content'); + if (!jasmineContent.contains(element)) { return jasmineContent.appendChild(element); } +}; + +let deprecationsSnapshot = null; +jasmine.snapshotDeprecations = () => deprecationsSnapshot = _.clone(Grim.deprecations); + +jasmine.restoreDeprecationsSnapshot = () => Grim.deprecations = deprecationsSnapshot; + +var addCustomMatchers = spec => + spec.addMatchers({ + toHaveLength(expected) { + if (this.actual == null) { + this.message = () => `Expected object ${this.actual} has no length method`; + return false; + } else { + let notText = this.isNot ? " not" : ""; + this.message = () => `Expected object with length ${this.actual.length} to${notText} have length ${expected}`; + return this.actual.length === expected; + } + } + }) +; + +// See docs/writing-specs.md +window.waitsForPromise = function(...args) { + if (args.length > 1) { + var { shouldReject, timeout } = args[0]; + } else { + var shouldReject = false; + } + let fn = _.last(args); + + return window.waitsFor(timeout, function(moveOn) { + let promise = fn(); + // Keep in mind we can't check `promise instanceof Promise` because parts of + // the app still use other Promise libraries Just see if it looks + // promise-like. + if (!promise || !promise.then) { + jasmine.getEnv().currentSpec.fail(`Expected callback to return a promise-like object, but it returned ${promise}`); + return moveOn(); + } else if (shouldReject) { + promise.catch(moveOn); + return promise.then(function() { + jasmine.getEnv().currentSpec.fail("Expected promise to be rejected, but it was resolved"); + return moveOn(); + }); + } else { + promise.then(moveOn); + return promise.catch(function(error) { + // I don't know what `pp` does, but for standard `new Error` objects, + // it sometimes returns "{ }". Catch this case and fall through to toString() + let msg = jasmine.pp(error); + if (msg === "{ }") { msg = error.toString(); } + jasmine.getEnv().currentSpec.fail(`Expected promise to be resolved, but it was rejected with ${msg}`); + return moveOn(); + }); + } + } + ); +}; + +function __guard__(value, transform) { + return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; +}