Compare commits
24 Commits
create-but
...
couchdb-te
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5c61ec1b7 | ||
|
|
7e7141a5a0 | ||
|
|
f6ecabb053 | ||
|
|
831f207b10 | ||
|
|
42e474ca60 | ||
|
|
d767932be2 | ||
|
|
0238a86be5 | ||
|
|
7abbd7cb20 | ||
|
|
6e2b12b00d | ||
|
|
6a2520f39b | ||
|
|
5b7a011069 | ||
|
|
d216117b30 | ||
|
|
5c520bfce9 | ||
|
|
cba9670823 | ||
|
|
27651e9eaa | ||
|
|
f096d54dd0 | ||
|
|
8258f21f7b | ||
|
|
44bfcf33ef | ||
|
|
669415d362 | ||
|
|
8601ec441f | ||
|
|
9a57a20404 | ||
|
|
1a3bff9813 | ||
|
|
baa5f21640 | ||
|
|
af9dceee3c |
@@ -11,12 +11,12 @@ jobs:
|
||||
name: Update npm
|
||||
command: 'sudo npm install -g npm@latest'
|
||||
- restore_cache:
|
||||
key: dependency-cache-13-{{ checksum "package.json" }}
|
||||
key: dependency-cache-{{ checksum "package.json" }}
|
||||
- run:
|
||||
name: Installing dependencies (npm install)
|
||||
command: npm install
|
||||
- save_cache:
|
||||
key: dependency-cache-13-{{ checksum "package.json" }}
|
||||
key: dependency-cache-{{ checksum "package.json" }}
|
||||
paths:
|
||||
- node_modules
|
||||
- run:
|
||||
|
||||
3
indexTest.js
Normal file
3
indexTest.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const testsContext = require.context('.', true, /\/(src|platform)\/.*Spec.js$/);
|
||||
|
||||
testsContext.keys().forEach(testsContext);
|
||||
@@ -23,7 +23,7 @@
|
||||
/*global module,process*/
|
||||
|
||||
const devMode = process.env.NODE_ENV !== 'production';
|
||||
const browsers = [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'FirefoxHeadless'];
|
||||
const browsers = [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'ChromeHeadless'];
|
||||
const coverageEnabled = process.env.COVERAGE === 'true';
|
||||
const reporters = ['progress', 'html'];
|
||||
|
||||
@@ -52,12 +52,16 @@ module.exports = (config) => {
|
||||
basePath: '',
|
||||
frameworks: ['jasmine'],
|
||||
files: [
|
||||
'platform/**/*Spec.js',
|
||||
'src/**/*Spec.js'
|
||||
'indexTest.js'
|
||||
],
|
||||
port: 9876,
|
||||
reporters: reporters,
|
||||
browsers: browsers,
|
||||
client: {
|
||||
jasmine: {
|
||||
random: false
|
||||
}
|
||||
},
|
||||
customLaunchers: {
|
||||
ChromeDebugging: {
|
||||
base: 'Chrome',
|
||||
@@ -66,7 +70,7 @@ module.exports = (config) => {
|
||||
}
|
||||
},
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
logLevel: config.LOG_DEBUG,
|
||||
autoWatch: true,
|
||||
// HTML test reporting.
|
||||
htmlReporter: {
|
||||
@@ -82,20 +86,20 @@ module.exports = (config) => {
|
||||
reports: ['html', 'lcovonly', 'text-summary'],
|
||||
thresholds: {
|
||||
global: {
|
||||
lines: 62
|
||||
lines: 64
|
||||
}
|
||||
}
|
||||
},
|
||||
preprocessors: {
|
||||
'platform/**/*Spec.js': ['webpack', 'sourcemap'],
|
||||
'src/**/*Spec.js': ['webpack', 'sourcemap']
|
||||
'indexTest.js': ['webpack', 'sourcemap']
|
||||
},
|
||||
webpack: webpackConfig,
|
||||
webpackMiddleware: {
|
||||
stats: 'errors-only',
|
||||
logLevel: 'warn'
|
||||
},
|
||||
concurrency: 1,
|
||||
singleRun: true,
|
||||
browserNoActivityTimeout: 90000
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
20
package.json
20
package.json
@@ -39,16 +39,16 @@
|
||||
"istanbul-instrumenter-loader": "^3.0.1",
|
||||
"jasmine-core": "^3.1.0",
|
||||
"jsdoc": "^3.3.2",
|
||||
"karma": "^2.0.3",
|
||||
"karma-chrome-launcher": "^2.2.0",
|
||||
"karma-firefox-launcher": "^1.3.0",
|
||||
"karma-cli": "^1.0.1",
|
||||
"karma-coverage": "^1.1.2",
|
||||
"karma-coverage-istanbul-reporter": "^2.1.1",
|
||||
"karma-html-reporter": "^0.2.7",
|
||||
"karma-jasmine": "^1.1.2",
|
||||
"karma-sourcemap-loader": "^0.3.7",
|
||||
"karma-webpack": "^3.0.0",
|
||||
"karma": "5.1.1",
|
||||
"karma-chrome-launcher": "3.1.0",
|
||||
"karma-firefox-launcher": "1.3.0",
|
||||
"karma-cli": "2.0.0",
|
||||
"karma-coverage": "2.0.3",
|
||||
"karma-coverage-istanbul-reporter": "3.0.3",
|
||||
"karma-html-reporter": "0.2.7",
|
||||
"karma-jasmine": "3.3.1",
|
||||
"karma-sourcemap-loader": "0.3.7",
|
||||
"karma-webpack": "4.0.2",
|
||||
"location-bar": "^3.0.1",
|
||||
"lodash": "^4.17.12",
|
||||
"markdown-toc": "^0.11.7",
|
||||
|
||||
@@ -79,12 +79,6 @@ function (
|
||||
return this.objectService;
|
||||
};
|
||||
|
||||
function resolveWith(object) {
|
||||
return function () {
|
||||
return object;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Save changes and conclude editing.
|
||||
*
|
||||
@@ -102,7 +96,6 @@ function (
|
||||
SaveAsAction.prototype.save = function () {
|
||||
var self = this,
|
||||
domainObject = this.domainObject,
|
||||
copyService = this.copyService,
|
||||
dialog = new SaveInProgressDialog(this.dialogService),
|
||||
toUndirty = [];
|
||||
|
||||
@@ -139,19 +132,22 @@ function (
|
||||
return fetchObject(object.getModel().location);
|
||||
}
|
||||
|
||||
function allowClone(objectToClone) {
|
||||
var allowed =
|
||||
(objectToClone.getId() === domainObject.getId())
|
||||
|| objectToClone.getCapability('location').isOriginal();
|
||||
if (allowed) {
|
||||
toUndirty.push(objectToClone);
|
||||
}
|
||||
|
||||
return allowed;
|
||||
function saveObject(parent) {
|
||||
return this.openmct.editor.save().then(() => {
|
||||
// Force mutation for search indexing
|
||||
return parent;
|
||||
});
|
||||
}
|
||||
|
||||
function cloneIntoParent(parent) {
|
||||
return copyService.perform(domainObject, parent, allowClone);
|
||||
function addSavedObjectToParent(parent) {
|
||||
return parent.getCapability("composition")
|
||||
.add(domainObject)
|
||||
.then(function (addedObject) {
|
||||
return parent.getCapability("persistence").persist()
|
||||
.then(function () {
|
||||
return addedObject;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function undirty(object) {
|
||||
@@ -160,26 +156,17 @@ function (
|
||||
|
||||
function undirtyOriginals(object) {
|
||||
return Promise.all(toUndirty.map(undirty))
|
||||
.then(resolveWith(object));
|
||||
.then(() => {
|
||||
return object;
|
||||
});
|
||||
}
|
||||
|
||||
function saveAfterClone(clonedObject) {
|
||||
return this.openmct.editor.save().then(() => {
|
||||
// Force mutation for search indexing
|
||||
return clonedObject;
|
||||
});
|
||||
}
|
||||
|
||||
function finishEditing(clonedObject) {
|
||||
return fetchObject(clonedObject.getId());
|
||||
}
|
||||
|
||||
function indexForSearch(savedObject) {
|
||||
savedObject.useCapability('mutation', (model) => {
|
||||
function indexForSearch(addedObject) {
|
||||
addedObject.useCapability('mutation', (model) => {
|
||||
return model;
|
||||
});
|
||||
|
||||
return savedObject;
|
||||
return addedObject;
|
||||
}
|
||||
|
||||
function onSuccess(object) {
|
||||
@@ -201,10 +188,12 @@ function (
|
||||
.then(doWizardSave)
|
||||
.then(showBlockingDialog)
|
||||
.then(getParent)
|
||||
.then(cloneIntoParent)
|
||||
.then(saveObject)
|
||||
.then(addSavedObjectToParent)
|
||||
.then(undirtyOriginals)
|
||||
.then(saveAfterClone)
|
||||
.then(finishEditing)
|
||||
.then((addedObject) => {
|
||||
return fetchObject(addedObject.getId());
|
||||
})
|
||||
.then(indexForSearch)
|
||||
.then(hideBlockingDialog)
|
||||
.then(onSuccess)
|
||||
|
||||
@@ -28,6 +28,7 @@ define(
|
||||
describe("The Transaction Service", function () {
|
||||
var mockQ,
|
||||
mockLog,
|
||||
mockCacheService,
|
||||
transactionService;
|
||||
|
||||
function fastPromise(val) {
|
||||
@@ -40,9 +41,10 @@ define(
|
||||
|
||||
beforeEach(function () {
|
||||
mockQ = jasmine.createSpyObj("$q", ["all"]);
|
||||
mockCacheService = jasmine.createSpyObj("cacheService", ["flush"]);
|
||||
mockQ.all.and.returnValue(fastPromise());
|
||||
mockLog = jasmine.createSpyObj("$log", ["error"]);
|
||||
transactionService = new TransactionService(mockQ, mockLog);
|
||||
transactionService = new TransactionService(mockQ, mockLog, mockCacheService);
|
||||
});
|
||||
|
||||
it("isActive returns true if a transaction is in progress", function () {
|
||||
@@ -85,17 +87,20 @@ define(
|
||||
|
||||
it("commit calls all queued commit functions", function () {
|
||||
expect(transactionService.size()).toBe(3);
|
||||
transactionService.commit();
|
||||
onCommits.forEach(function (spy) {
|
||||
expect(spy).toHaveBeenCalled();
|
||||
|
||||
return transactionService.commit().then(() => {
|
||||
onCommits.forEach(function (spy) {
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("commit resets active state and clears queues", function () {
|
||||
transactionService.commit();
|
||||
expect(transactionService.isActive()).toBe(false);
|
||||
expect(transactionService.size()).toBe(0);
|
||||
expect(transactionService.size()).toBe(0);
|
||||
return transactionService.commit().then(() => {
|
||||
expect(transactionService.isActive()).toBe(false);
|
||||
expect(transactionService.size()).toBe(0);
|
||||
expect(transactionService.size()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -46,25 +46,34 @@ define([
|
||||
spyOn(provider, 'cleanTerm').and.returnValue('cleanedTerm');
|
||||
spyOn(provider, 'fuzzyMatchUnquotedTerms').and.returnValue('fuzzy');
|
||||
spyOn(provider, 'parseResponse').and.returnValue('parsedResponse');
|
||||
$http.and.returnValue(Promise.resolve({}));
|
||||
$http.and.returnValue(Promise.resolve({
|
||||
data: {
|
||||
hits: {
|
||||
hits: []
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
it('cleans terms and adds fuzzyness', function () {
|
||||
provider.query('hello', 10);
|
||||
expect(provider.cleanTerm).toHaveBeenCalledWith('hello');
|
||||
expect(provider.fuzzyMatchUnquotedTerms)
|
||||
.toHaveBeenCalledWith('cleanedTerm');
|
||||
return provider.query('hello', 10)
|
||||
.then(() => {
|
||||
expect(provider.cleanTerm).toHaveBeenCalledWith('hello');
|
||||
expect(provider.fuzzyMatchUnquotedTerms)
|
||||
.toHaveBeenCalledWith('cleanedTerm');
|
||||
});
|
||||
});
|
||||
|
||||
it('calls through to $http', function () {
|
||||
provider.query('hello', 10);
|
||||
expect($http).toHaveBeenCalledWith({
|
||||
method: 'GET',
|
||||
params: {
|
||||
q: 'fuzzy',
|
||||
size: 10
|
||||
},
|
||||
url: 'http://localhost:9200/_search/'
|
||||
return provider.query('hello', 10).then(() => {
|
||||
expect($http).toHaveBeenCalledWith({
|
||||
method: 'GET',
|
||||
params: {
|
||||
q: 'fuzzy',
|
||||
size: 10
|
||||
},
|
||||
url: 'http://localhost:9200/_search/'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ define([
|
||||
"../../src/services/SearchAggregator"
|
||||
], function (SearchAggregator) {
|
||||
|
||||
describe("SearchAggregator", function () {
|
||||
xdescribe("SearchAggregator", function () {
|
||||
var $q,
|
||||
objectService,
|
||||
providers,
|
||||
|
||||
@@ -422,7 +422,10 @@ define([
|
||||
|
||||
this.router.start();
|
||||
this.emit('start');
|
||||
}.bind(this));
|
||||
}.bind(this), function () {
|
||||
console.log('startPromise failed');
|
||||
this.emit('start');
|
||||
});
|
||||
};
|
||||
|
||||
MCT.prototype.startHeadless = function () {
|
||||
@@ -444,6 +447,7 @@ define([
|
||||
|
||||
MCT.prototype.destroy = function () {
|
||||
this.emit('destroy');
|
||||
this.router.destroy();
|
||||
};
|
||||
|
||||
MCT.prototype.plugins = plugins;
|
||||
|
||||
@@ -32,10 +32,6 @@ define([
|
||||
var mockListener;
|
||||
var oldBundles;
|
||||
|
||||
beforeAll(() => {
|
||||
testUtils.resetApplicationState();
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
mockPlugin = jasmine.createSpy('plugin');
|
||||
mockPlugin2 = jasmine.createSpy('plugin2');
|
||||
@@ -56,7 +52,8 @@ define([
|
||||
legacyRegistry.delete(bundle);
|
||||
}
|
||||
});
|
||||
testUtils.resetApplicationState(openmct);
|
||||
|
||||
return testUtils.resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
it("exposes plugins", function () {
|
||||
|
||||
@@ -35,7 +35,8 @@ define([
|
||||
'./services/LegacyObjectAPIInterceptor',
|
||||
'./views/installLegacyViews',
|
||||
'./policies/LegacyCompositionPolicyAdapter',
|
||||
'./actions/LegacyActionAdapter'
|
||||
'./actions/LegacyActionAdapter',
|
||||
'./services/LegacyPersistenceAdapter'
|
||||
], function (
|
||||
ActionDialogDecorator,
|
||||
AdapterCapability,
|
||||
@@ -51,7 +52,8 @@ define([
|
||||
LegacyObjectAPIInterceptor,
|
||||
installLegacyViews,
|
||||
legacyCompositionPolicyAdapter,
|
||||
LegacyActionAdapter
|
||||
LegacyActionAdapter,
|
||||
LegacyPersistenceAdapter
|
||||
) {
|
||||
return {
|
||||
name: 'src/adapter',
|
||||
@@ -114,6 +116,13 @@ define([
|
||||
"instantiate",
|
||||
"topic"
|
||||
]
|
||||
},
|
||||
{
|
||||
provides: "persistenceService",
|
||||
type: "provider",
|
||||
priority: "fallback",
|
||||
implementation: LegacyPersistenceAdapter.default,
|
||||
depends: ["openmct"]
|
||||
}
|
||||
],
|
||||
policies: [
|
||||
|
||||
29
src/adapter/services/LegacyPersistenceAdapter.js
Normal file
29
src/adapter/services/LegacyPersistenceAdapter.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import objectUtils from 'objectUtils';
|
||||
|
||||
function LegacyPersistenceProvider(openmct) {
|
||||
this.openmct = openmct;
|
||||
}
|
||||
|
||||
LegacyPersistenceProvider.prototype.listObjects = function () {
|
||||
return Promise.resolve([]);
|
||||
};
|
||||
|
||||
LegacyPersistenceProvider.prototype.listSpaces = function () {
|
||||
return Promise.resolve(Object.keys(this.openmct.objects.providers));
|
||||
};
|
||||
|
||||
LegacyPersistenceProvider.prototype.updateObject = function (legacyDomainObject) {
|
||||
return this.openmct.objects.save(legacyDomainObject.useCapability('adapter'));
|
||||
};
|
||||
|
||||
LegacyPersistenceProvider.prototype.updateObject = function (legacyDomainObject) {
|
||||
return this.openmct.objects.save(legacyDomainObject.useCapability('adapter'));
|
||||
};
|
||||
|
||||
LegacyPersistenceProvider.prototype.readObject = function (keystring) {
|
||||
let identifier = objectUtils.parseKeyString(keystring);
|
||||
|
||||
return this.openmct.legacyObject(this.openmct.objects.get(identifier));
|
||||
};
|
||||
|
||||
export default LegacyPersistenceProvider;
|
||||
@@ -94,7 +94,7 @@ describe("The LAD Table", () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetApplicationState(openmct);
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
it("should provide a table view only for lad table objects", () => {
|
||||
@@ -284,7 +284,7 @@ describe("The LAD Table Set", () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetApplicationState(openmct);
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
it("should provide a lad table set view only for lad table set objects", () => {
|
||||
|
||||
@@ -35,7 +35,7 @@ define(
|
||||
) {
|
||||
var defaultAjaxFunction = $.ajax;
|
||||
|
||||
xdescribe("The URLIndicator", function () {
|
||||
describe("The URLIndicator", function () {
|
||||
var openmct;
|
||||
var indicatorElement;
|
||||
var pluginOptions;
|
||||
|
||||
@@ -66,7 +66,7 @@ export default class URLTimeSettingsSynchronizer {
|
||||
TIME_EVENTS.forEach(event => {
|
||||
this.openmct.time.off(event, this.setUrlFromTimeApi);
|
||||
});
|
||||
this.openmct.time.on('bounds', this.updateBounds);
|
||||
this.openmct.time.off('bounds', this.updateBounds);
|
||||
}
|
||||
|
||||
updateTimeSettings() {
|
||||
|
||||
@@ -27,7 +27,6 @@ import {
|
||||
describe("The URLTimeSettingsSynchronizer", () => {
|
||||
let openmct;
|
||||
let testClock;
|
||||
beforeAll(() => resetApplicationState());
|
||||
|
||||
beforeEach((done) => {
|
||||
openmct = createOpenMct();
|
||||
@@ -82,17 +81,17 @@ describe("The URLTimeSettingsSynchronizer", () => {
|
||||
expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
|
||||
});
|
||||
describe("when set in the url", () => {
|
||||
it("will change from fixed to realtime mode when the mode changes", () => {
|
||||
it("will change from fixed to realtime mode when the mode changes", (done) => {
|
||||
expectLocationToBeInFixedMode();
|
||||
|
||||
return switchToRealtimeMode().then(() => {
|
||||
let clock = openmct.time.clock();
|
||||
|
||||
expect(clock).toBeDefined();
|
||||
expect(clock.key).toBe('local');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it("the clock is correctly set in the API from the URL parameters", () => {
|
||||
it("the clock is correctly set in the API from the URL parameters", (done) => {
|
||||
return switchToRealtimeMode().then(() => {
|
||||
let resolveFunction;
|
||||
|
||||
@@ -110,10 +109,11 @@ describe("The URLTimeSettingsSynchronizer", () => {
|
||||
expect(clock).toBeDefined();
|
||||
expect(clock.key).toBe('test-clock');
|
||||
openmct.time.off('clock', resolveFunction);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it("the clock offsets are correctly set in the API from the URL parameters", () => {
|
||||
it("the clock offsets are correctly set in the API from the URL parameters", (done) => {
|
||||
return switchToRealtimeMode().then(() => {
|
||||
let resolveFunction;
|
||||
|
||||
@@ -132,10 +132,11 @@ describe("The URLTimeSettingsSynchronizer", () => {
|
||||
expect(clockOffsets.start).toBe(-2000);
|
||||
expect(clockOffsets.end).toBe(200);
|
||||
openmct.time.off('clockOffsets', resolveFunction);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it("the time system is correctly set in the API from the URL parameters", () => {
|
||||
it("the time system is correctly set in the API from the URL parameters", (done) => {
|
||||
return switchToRealtimeMode().then(() => {
|
||||
let resolveFunction;
|
||||
|
||||
@@ -153,6 +154,7 @@ describe("The URLTimeSettingsSynchronizer", () => {
|
||||
expect(timeSystem).toBeDefined();
|
||||
expect(timeSystem.key).toBe('local');
|
||||
openmct.time.off('timeSystem', resolveFunction);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -200,7 +202,7 @@ describe("The URLTimeSettingsSynchronizer", () => {
|
||||
expect(window.location.hash.includes('tc.timeSystem=utc')).toBe(false);
|
||||
});
|
||||
describe("when set in the url", () => {
|
||||
it("time system changes are reflected in the API", () => {
|
||||
it("time system changes are reflected in the API", (done) => {
|
||||
let resolveFunction;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
@@ -216,9 +218,10 @@ describe("The URLTimeSettingsSynchronizer", () => {
|
||||
expect(timeSystem.key).toBe('local');
|
||||
|
||||
openmct.time.off('timeSystem', resolveFunction);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it("mode can be changed from realtime to fixed", () => {
|
||||
it("mode can be changed from realtime to fixed", (done) => {
|
||||
return switchToRealtimeMode().then(() => {
|
||||
expectLocationToBeInRealtimeMode();
|
||||
|
||||
@@ -226,9 +229,10 @@ describe("The URLTimeSettingsSynchronizer", () => {
|
||||
}).then(switchToFixedMode).then(() => {
|
||||
let clock = openmct.time.clock();
|
||||
expect(clock).not.toBeDefined();
|
||||
done();
|
||||
});
|
||||
});
|
||||
it("bounds are correctly set in the API from the URL parameters", () => {
|
||||
it("bounds are correctly set in the API from the URL parameters", (done) => {
|
||||
let resolveFunction;
|
||||
|
||||
expectLocationToBeInFixedMode();
|
||||
@@ -246,9 +250,10 @@ describe("The URLTimeSettingsSynchronizer", () => {
|
||||
expect(bounds).toBeDefined();
|
||||
expect(bounds.start).toBe(222);
|
||||
expect(bounds.end).toBe(333);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it("bounds are correctly set in the API from the URL parameters where only the end bound changes", () => {
|
||||
it("bounds are correctly set in the API from the URL parameters where only the end bound changes", (done) => {
|
||||
let resolveFunction;
|
||||
|
||||
expectLocationToBeInFixedMode();
|
||||
@@ -265,6 +270,7 @@ describe("The URLTimeSettingsSynchronizer", () => {
|
||||
expect(bounds).toBeDefined();
|
||||
expect(bounds.start).toBe(0);
|
||||
expect(bounds.end).toBe(333);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,7 +27,7 @@ define([
|
||||
'zepto',
|
||||
'./dom-observer'
|
||||
], function (AutoflowTabularPlugin, AutoflowTabularConstants, MCT, $, DOMObserver) {
|
||||
xdescribe("AutoflowTabularPlugin", function () {
|
||||
describe("AutoflowTabularPlugin", function () {
|
||||
var testType;
|
||||
var testObject;
|
||||
var mockmct;
|
||||
|
||||
@@ -36,10 +36,6 @@ describe('the plugin', function () {
|
||||
let openmct;
|
||||
let testTelemetryObject;
|
||||
|
||||
beforeAll(() => {
|
||||
resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
beforeEach((done) => {
|
||||
testTelemetryObject = {
|
||||
identifier: {
|
||||
@@ -98,7 +94,7 @@ describe('the plugin', function () {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetApplicationState(openmct);
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
let mockConditionSetObject = {
|
||||
@@ -462,7 +458,7 @@ describe('the plugin', function () {
|
||||
};
|
||||
});
|
||||
|
||||
it('should evaluate as stale when telemetry is not received in the allotted time', (done) => {
|
||||
xit('should evaluate as stale when telemetry is not received in the allotted time', (done) => {
|
||||
|
||||
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
|
||||
conditionMgr.on('conditionSetResultUpdated', mockListener);
|
||||
@@ -481,10 +477,10 @@ describe('the plugin', function () {
|
||||
utc: undefined
|
||||
});
|
||||
done();
|
||||
}, 300);
|
||||
}, 400);
|
||||
});
|
||||
|
||||
it('should not evaluate as stale when telemetry is received in the allotted time', (done) => {
|
||||
xit('should not evaluate as stale when telemetry is received in the allotted time', (done) => {
|
||||
const date = Date.now();
|
||||
conditionSetDomainObject.configuration.conditionCollection[0].configuration.criteria[0].input = ["0.4"];
|
||||
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
|
||||
|
||||
@@ -29,10 +29,6 @@ describe('the plugin', function () {
|
||||
let openmct;
|
||||
let displayLayoutDefinition;
|
||||
|
||||
beforeAll(() => {
|
||||
resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
beforeEach((done) => {
|
||||
openmct = createOpenMct();
|
||||
openmct.install(new DisplayLayoutPlugin({
|
||||
@@ -49,7 +45,7 @@ describe('the plugin', function () {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetApplicationState(openmct);
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
it('defines a display layout object type with the correct key', () => {
|
||||
|
||||
@@ -46,7 +46,7 @@ describe("the plugin", () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetApplicationState(openmct);
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
it('installs the new folder action', () => {
|
||||
|
||||
@@ -35,10 +35,6 @@ describe('the plugin', () => {
|
||||
parentElement,
|
||||
mockMessages = ['error', 'test', 'notifications'];
|
||||
|
||||
beforeAll(() => {
|
||||
resetApplicationState();
|
||||
});
|
||||
|
||||
beforeEach((done) => {
|
||||
openmct = createOpenMct();
|
||||
|
||||
@@ -61,7 +57,7 @@ describe('the plugin', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetApplicationState(openmct);
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
describe('the indicator plugin element', () => {
|
||||
|
||||
53
src/plugins/persistence/couch/CouchDocument.js
Normal file
53
src/plugins/persistence/couch/CouchDocument.js
Normal file
@@ -0,0 +1,53 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* A CouchDocument describes domain object model in a format
|
||||
* which is easily read-written to CouchDB. This includes
|
||||
* Couch's _id and _rev fields, as well as a separate
|
||||
* metadata field which contains a subset of information found
|
||||
* in the model itself (to support search optimization with
|
||||
* CouchDB views.)
|
||||
* @memberof platform/persistence/couch
|
||||
* @constructor
|
||||
* @param {string} id the id under which to store this mode
|
||||
* @param {object} model the model to store
|
||||
* @param {string} rev the revision to include (or undefined,
|
||||
* if no revision should be noted for couch)
|
||||
* @param {boolean} whether or not to mark this document as
|
||||
* deleted (see CouchDB docs for _deleted)
|
||||
*/
|
||||
export default function CouchDocument(id, model, rev, markDeleted) {
|
||||
return {
|
||||
"_id": id,
|
||||
"_rev": rev,
|
||||
"_deleted": markDeleted,
|
||||
"metadata": {
|
||||
"category": "domain object",
|
||||
"type": model.type,
|
||||
"owner": "admin",
|
||||
"name": model.name,
|
||||
"created": Date.now()
|
||||
},
|
||||
"model": model
|
||||
};
|
||||
}
|
||||
134
src/plugins/persistence/couch/CouchObjectProvider.js
Normal file
134
src/plugins/persistence/couch/CouchObjectProvider.js
Normal file
@@ -0,0 +1,134 @@
|
||||
import CouchDocument from "./CouchDocument";
|
||||
import CouchObjectQueue from "./CouchObjectQueue";
|
||||
|
||||
const REV = "_rev";
|
||||
const ID = "_id";
|
||||
|
||||
export default class CouchObjectProvider {
|
||||
constructor(openmct, url, namespace) {
|
||||
this.openmct = openmct;
|
||||
this.url = url;
|
||||
this.namespace = namespace;
|
||||
this.objectQueue = {};
|
||||
}
|
||||
|
||||
request(subPath, method, value) {
|
||||
return fetch(this.url + '/' + subPath, {
|
||||
method: method,
|
||||
body: JSON.stringify(value)
|
||||
}).then(response => response.json())
|
||||
.then(function (response) {
|
||||
return response;
|
||||
}, function () {
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
// Check the response to a create/update/delete request;
|
||||
// track the rev if it's valid, otherwise return false to
|
||||
// indicate that the request failed.
|
||||
// persist any queued objects
|
||||
checkResponse(response, intermediateResponse) {
|
||||
let requestSuccess = false;
|
||||
const id = response.id;
|
||||
let rev;
|
||||
if (response && response.ok) {
|
||||
rev = response.rev;
|
||||
requestSuccess = true;
|
||||
}
|
||||
|
||||
intermediateResponse.resolve(requestSuccess);
|
||||
|
||||
if (id) {
|
||||
if (!this.objectQueue[id]) {
|
||||
this.objectQueue[id] = new CouchObjectQueue(undefined, rev);
|
||||
}
|
||||
|
||||
this.objectQueue[id].updateRevision(rev);
|
||||
this.objectQueue[id].pending = false;
|
||||
if (this.objectQueue[id].hasNext()) {
|
||||
this.updateQueued(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getModel(response) {
|
||||
if (response && response.model) {
|
||||
let key = response[ID];
|
||||
let object = response.model;
|
||||
object.identifier = {
|
||||
namespace: this.namespace,
|
||||
key: key
|
||||
};
|
||||
if (!this.objectQueue[key]) {
|
||||
this.objectQueue[key] = new CouchObjectQueue(undefined, response[REV]);
|
||||
}
|
||||
|
||||
this.objectQueue[key].updateRevision(response[REV]);
|
||||
|
||||
return object;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
get(identifier) {
|
||||
return this.request(identifier.key, "GET").then(this.getModel.bind(this));
|
||||
}
|
||||
|
||||
getIntermediateResponse() {
|
||||
let intermediateResponse = {};
|
||||
intermediateResponse.promise = new Promise(function (resolve, reject) {
|
||||
intermediateResponse.resolve = resolve;
|
||||
intermediateResponse.reject = reject;
|
||||
});
|
||||
|
||||
return intermediateResponse;
|
||||
}
|
||||
|
||||
enqueueObject(key, model, intermediateResponse) {
|
||||
if (this.objectQueue[key]) {
|
||||
this.objectQueue[key].enqueue({
|
||||
model,
|
||||
intermediateResponse
|
||||
});
|
||||
} else {
|
||||
this.objectQueue[key] = new CouchObjectQueue({
|
||||
model,
|
||||
intermediateResponse
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
create(model) {
|
||||
let intermediateResponse = this.getIntermediateResponse();
|
||||
const key = model.identifier.key;
|
||||
this.enqueueObject(key, model, intermediateResponse);
|
||||
this.objectQueue[key].pending = true;
|
||||
const queued = this.objectQueue[key].dequeue();
|
||||
this.request(key, "PUT", new CouchDocument(key, queued.model)).then((response) => {
|
||||
this.checkResponse(response, queued.intermediateResponse);
|
||||
});
|
||||
|
||||
return intermediateResponse.promise;
|
||||
}
|
||||
|
||||
updateQueued(key) {
|
||||
if (!this.objectQueue[key].pending) {
|
||||
this.objectQueue[key].pending = true;
|
||||
const queued = this.objectQueue[key].dequeue();
|
||||
this.request(key, "PUT", new CouchDocument(key, queued.model, this.objectQueue[key].rev)).then((response) => {
|
||||
this.checkResponse(response, queued.intermediateResponse);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
update(model) {
|
||||
let intermediateResponse = this.getIntermediateResponse();
|
||||
const key = model.identifier.key;
|
||||
this.enqueueObject(key, model, intermediateResponse);
|
||||
this.updateQueued(key);
|
||||
|
||||
return intermediateResponse.promise;
|
||||
}
|
||||
}
|
||||
32
src/plugins/persistence/couch/CouchObjectQueue.js
Normal file
32
src/plugins/persistence/couch/CouchObjectQueue.js
Normal file
@@ -0,0 +1,32 @@
|
||||
//TODO: should we limit the queue size?
|
||||
//const MAX_QUEUE_SIZE = 10;
|
||||
|
||||
export default class CouchObjectQueue {
|
||||
constructor(object, rev) {
|
||||
this.rev = rev;
|
||||
this.objects = object ? [object] : [];
|
||||
this.pending = false;
|
||||
}
|
||||
|
||||
updateRevision(rev) {
|
||||
this.rev = rev;
|
||||
}
|
||||
|
||||
hasNext() {
|
||||
return this.objects.length;
|
||||
}
|
||||
|
||||
enqueue(item) {
|
||||
this.objects.push(item);
|
||||
}
|
||||
|
||||
dequeue() {
|
||||
return this.objects.shift();
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.rev = undefined;
|
||||
this.objects = [];
|
||||
}
|
||||
|
||||
}
|
||||
8
src/plugins/persistence/couch/plugin.js
Normal file
8
src/plugins/persistence/couch/plugin.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import CouchObjectProvider from './CouchObjectProvider';
|
||||
const NAMESPACE = '';
|
||||
|
||||
export default function CouchPlugin(url) {
|
||||
return function install(openmct) {
|
||||
openmct.objects.addProvider(NAMESPACE, new CouchObjectProvider(openmct, url, NAMESPACE));
|
||||
};
|
||||
}
|
||||
102
src/plugins/persistence/couch/pluginSpec.js
Normal file
102
src/plugins/persistence/couch/pluginSpec.js
Normal file
@@ -0,0 +1,102 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
import CouchPlugin from './plugin.js';
|
||||
import {
|
||||
createOpenMct,
|
||||
resetApplicationState
|
||||
} from 'utils/testing';
|
||||
import CouchObjectProvider from "@/plugins/persistence/couch/CouchObjectProvider";
|
||||
import CouchObjectQueue from "@/plugins/persistence/couch/CouchObjectQueue";
|
||||
|
||||
describe("the plugin", () => {
|
||||
let openmct;
|
||||
let element;
|
||||
let child;
|
||||
let provider;
|
||||
let testSpace = "testSpace";
|
||||
let testPath = "/test/db";
|
||||
let mockDomainObject = {
|
||||
identifier: {
|
||||
namespace: '',
|
||||
key: "some-value"
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach((done) => {
|
||||
openmct = createOpenMct(false);
|
||||
openmct.install(new CouchPlugin(testSpace, testPath));
|
||||
|
||||
element = document.createElement('div');
|
||||
child = document.createElement('div');
|
||||
element.appendChild(child);
|
||||
|
||||
openmct.on('start', done);
|
||||
openmct.startHeadless();
|
||||
|
||||
provider = openmct.objects.getProvider(mockDomainObject.identifier);
|
||||
});
|
||||
|
||||
it('registers a provider for objects', () => {
|
||||
expect(provider).toBeDefined();
|
||||
});
|
||||
|
||||
it('gets an object', () => {
|
||||
openmct.objects.get(mockDomainObject.identifier).then((result) => {
|
||||
expect(provider.get).toHaveBeenCalled();
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('creates an object', () => {
|
||||
openmct.objects.save(mockDomainObject).then((result) => {
|
||||
expect(provider.create).toHaveBeenCalled();
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('updates an object', () => {
|
||||
openmct.objects.save(mockDomainObject).then((result) => {
|
||||
expect(provider.create).toHaveBeenCalled();
|
||||
openmct.objects.save(mockDomainObject).then((updatedResult) => {
|
||||
expect(provider.update).toHaveBeenCalled();
|
||||
expect(updatedResult).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('updates queued objects', () => {
|
||||
let couchProvider = new CouchObjectProvider(openmct, 'http://localhost', '');
|
||||
let intermediateResponse = couchProvider.getIntermediateResponse();
|
||||
spyOn(couchProvider, 'updateQueued');
|
||||
couchProvider.enqueueObject(mockDomainObject.identifier.key, mockDomainObject, intermediateResponse);
|
||||
couchProvider.objectQueue[mockDomainObject.identifier.key].updateRevision(1);
|
||||
couchProvider.update(mockDomainObject);
|
||||
expect(couchProvider.objectQueue[mockDomainObject.identifier.key].hasNext()).toBe(2);
|
||||
couchProvider.checkResponse({
|
||||
ok: true,
|
||||
rev: 2,
|
||||
id: mockDomainObject.identifier.key
|
||||
}, intermediateResponse);
|
||||
|
||||
expect(couchProvider.updateQueued).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
@@ -54,7 +54,8 @@ define([
|
||||
'./themes/snow',
|
||||
'./URLTimeSettingsSynchronizer/plugin',
|
||||
'./notificationIndicator/plugin',
|
||||
'./newFolderAction/plugin'
|
||||
'./newFolderAction/plugin',
|
||||
'./persistence/couch/plugin'
|
||||
], function (
|
||||
_,
|
||||
UTCTimeSystem,
|
||||
@@ -89,12 +90,12 @@ define([
|
||||
Snow,
|
||||
URLTimeSettingsSynchronizer,
|
||||
NotificationIndicator,
|
||||
NewFolderAction
|
||||
NewFolderAction,
|
||||
CouchDBPlugin
|
||||
) {
|
||||
var bundleMap = {
|
||||
LocalStorage: 'platform/persistence/local',
|
||||
MyItems: 'platform/features/my-items',
|
||||
CouchDB: 'platform/persistence/couch',
|
||||
Elasticsearch: 'platform/persistence/elastic'
|
||||
};
|
||||
|
||||
@@ -126,27 +127,7 @@ define([
|
||||
|
||||
plugins.Conductor = TimeConductorPlugin.default;
|
||||
|
||||
plugins.CouchDB = function (url) {
|
||||
return function (openmct) {
|
||||
if (url) {
|
||||
var bundleName = "config/couch";
|
||||
openmct.legacyRegistry.register(bundleName, {
|
||||
"extensions": {
|
||||
"constants": [
|
||||
{
|
||||
"key": "COUCHDB_PATH",
|
||||
"value": url,
|
||||
"priority": "mandatory"
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
openmct.legacyRegistry.enable(bundleName);
|
||||
}
|
||||
|
||||
openmct.legacyRegistry.enable(bundleMap.CouchDB);
|
||||
};
|
||||
};
|
||||
plugins.CouchDB = CouchDBPlugin.default;
|
||||
|
||||
plugins.Elasticsearch = function (url) {
|
||||
return function (openmct) {
|
||||
|
||||
@@ -28,16 +28,27 @@ import {
|
||||
resetApplicationState
|
||||
} from 'utils/testing';
|
||||
|
||||
class MockDataTransfer {
|
||||
constructor() {
|
||||
this.data = {};
|
||||
}
|
||||
get types() {
|
||||
return Object.keys(this.data);
|
||||
}
|
||||
setData(format, data) {
|
||||
this.data[format] = data;
|
||||
}
|
||||
getData(format) {
|
||||
return this.data[format];
|
||||
}
|
||||
}
|
||||
|
||||
describe("the plugin", () => {
|
||||
let openmct;
|
||||
let tablePlugin;
|
||||
let element;
|
||||
let child;
|
||||
|
||||
beforeAll(() => {
|
||||
resetApplicationState();
|
||||
});
|
||||
|
||||
beforeEach((done) => {
|
||||
openmct = createOpenMct();
|
||||
|
||||
@@ -67,7 +78,7 @@ describe("the plugin", () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetApplicationState(openmct);
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
describe("defines a table object", function () {
|
||||
@@ -167,7 +178,7 @@ describe("the plugin", () => {
|
||||
|
||||
it("Renders a row for every telemetry datum returned", () => {
|
||||
let rows = element.querySelectorAll('table.c-telemetry-table__body tr');
|
||||
expect(rows.length).toBe(3);
|
||||
expect(rows.length).toBe(1);
|
||||
});
|
||||
|
||||
it("Renders a column for every item in telemetry metadata", () => {
|
||||
@@ -191,7 +202,7 @@ describe("the plugin", () => {
|
||||
|
||||
dragStartEvent.dataTransfer =
|
||||
dragOverEvent.dataTransfer =
|
||||
dropEvent.dataTransfer = new DataTransfer();
|
||||
dropEvent.dataTransfer = new MockDataTransfer();
|
||||
|
||||
fromColumn.dispatchEvent(dragStartEvent);
|
||||
toColumn.dispatchEvent(dragOverEvent);
|
||||
|
||||
@@ -81,7 +81,12 @@ export default {
|
||||
this.isLoading = true;
|
||||
this.openmct.objects.get('ROOT')
|
||||
.then(root => {
|
||||
return this.openmct.composition.get(root).load();
|
||||
let composition = this.openmct.composition.get(root);
|
||||
if (composition !== undefined) {
|
||||
return composition.load();
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
})
|
||||
.then(children => {
|
||||
this.isLoading = false;
|
||||
|
||||
@@ -45,6 +45,7 @@ class ApplicationRouter extends EventEmitter {
|
||||
super();
|
||||
this.routes = [];
|
||||
this.started = false;
|
||||
this.locationBar = new LocationBar();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,13 +57,17 @@ class ApplicationRouter extends EventEmitter {
|
||||
}
|
||||
|
||||
this.started = true;
|
||||
let locationBar = new LocationBar();
|
||||
locationBar.onChange(p => this.handleLocationChange(p));
|
||||
locationBar.start({
|
||||
|
||||
this.locationBar.onChange(p => this.handleLocationChange(p));
|
||||
this.locationBar.start({
|
||||
root: location.pathname
|
||||
});
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.locationBar.stop();
|
||||
}
|
||||
|
||||
handleLocationChange(pathString) {
|
||||
if (pathString[0] !== '/') {
|
||||
pathString = '/' + pathString;
|
||||
|
||||
@@ -32,7 +32,6 @@ import {
|
||||
import {resetApplicationState} from 'utils/testing';
|
||||
|
||||
describe('the openmct location utility functions', () => {
|
||||
beforeAll(() => resetApplicationState());
|
||||
afterEach(() => resetApplicationState());
|
||||
|
||||
it('The setSearchParam function sets an individual search parameters in the window location hash', () => {
|
||||
|
||||
@@ -65,12 +65,29 @@ export function clearBuiltinSpies() {
|
||||
}
|
||||
|
||||
export function resetApplicationState(openmct) {
|
||||
let promise;
|
||||
|
||||
clearBuiltinSpies();
|
||||
window.location.hash = '#';
|
||||
|
||||
if (openmct !== undefined) {
|
||||
openmct.destroy();
|
||||
}
|
||||
|
||||
if (window.location.hash !== '#' && window.location.hash !== '') {
|
||||
promise = new Promise((resolve, reject) => {
|
||||
window.addEventListener('hashchange', cleanup);
|
||||
window.location.hash = '#';
|
||||
|
||||
function cleanup() {
|
||||
window.removeEventListener('hashchange', cleanup);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
promise = Promise.resolve();
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
function clearBuiltinSpy(funcDefinition) {
|
||||
|
||||
Reference in New Issue
Block a user