@eclipse-emfcloud/model-service-theia
Version:
Model service Theia
227 lines • 9.94 kB
JavaScript
"use strict";
// *****************************************************************************
// Copyright (C) 2023-2024 STMicroelectronics.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: MIT License which is
// available at https://opensource.org/licenses/MIT.
//
// SPDX-License-Identifier: EPL-2.0 OR MIT
// *****************************************************************************
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const model_validation_1 = require("@eclipse-emfcloud/model-validation");
const inversify_1 = require("@theia/core/shared/inversify");
const chai_1 = require("chai");
const sinon_1 = __importDefault(require("sinon"));
const frontend_model_hub_1 = require("../frontend-model-hub");
const frontend_model_hub_subscriber_1 = require("../frontend-model-hub-subscriber");
const fake_model_hub_protocol_1 = require("./fake-model-hub-protocol");
const test_module_1 = require("./test-module");
function createTestContainer() {
const container = new inversify_1.Container();
container.load(test_module_1.testModule);
return container;
}
const MODEL1_ID = 'test.model1';
const MODEL1 = { name: 'Model 1' };
const MODEL2_ID = 'test.model2';
const MODEL2 = { name: 'Model 2' };
describe('FrontendModelHub', () => {
const appContext = 'test-app';
let sandbox;
let modelHub;
let fake;
let provider;
beforeEach(async () => {
sandbox = sinon_1.default.createSandbox();
const container = createTestContainer();
provider = container.get(frontend_model_hub_1.FrontendModelHubProvider);
modelHub = await provider(appContext);
const subscriber = container.get(frontend_model_hub_subscriber_1.FrontendModelHubSubscriber);
fake = container.get(fake_model_hub_protocol_1.FakeModelHubProtocol);
fake.setModel(MODEL1_ID, MODEL1);
(0, fake_model_hub_protocol_1.connectClient)(fake, subscriber);
});
describe('simple delegators', () => {
it('getModel', async () => {
const model = await modelHub.getModel(MODEL1_ID);
(0, chai_1.expect)(model).to.be.like(MODEL1);
});
const delegators = [
'validateModels',
'getValidationState',
'save',
'isDirty',
'undo',
'redo',
'flush',
];
const error = {
message: 'A test error.',
path: '/name',
severity: 'error',
source: 'test',
};
const results = [error, error, true, true, true, true, true];
delegators.forEach((methodName, index) => {
it(methodName, async () => {
const stub = sandbox.stub().resolves(results[index]);
const template = {};
template[methodName] = stub;
Object.assign(fake, template);
const result = await modelHub[methodName](MODEL1_ID);
(0, chai_1.expect)(result).to.eql(results[index]);
sinon_1.default.assert.calledWithExactly(stub, appContext, MODEL1_ID);
});
});
});
describe('subscriptions', () => {
let onModelChanged;
let onModelDirtyState;
let onModelValidated;
let onModelLoaded;
let onModelUnloaded;
let onModelHubDisposed;
beforeEach(async () => {
onModelChanged = sandbox.stub();
onModelDirtyState = sandbox.stub();
onModelLoaded = sandbox.stub();
onModelUnloaded = sandbox.stub();
onModelValidated = sandbox.stub();
onModelHubDisposed = sandbox.stub();
});
it('notifies model change', async () => {
const sub = await modelHub.subscribe(MODEL1_ID);
sub.onModelChanged = onModelChanged;
const patch = [
{ op: 'replace', path: '/name', value: MODEL1.name },
];
fake.fakeModelChange(MODEL1_ID, patch);
await asyncsResolved();
sinon_1.default.assert.calledWithMatch(onModelChanged, MODEL1_ID, MODEL1, patch);
});
it('subscription sees consistent model', async () => {
const sub = await modelHub.subscribe();
let sawCorrectModelId = false;
let sawCorrectModelObject = false;
const gatherAssertions = async (modelId, model) => {
sawCorrectModelId = modelId === MODEL1_ID;
const currentModel2 = await modelHub.getModel(modelId);
sawCorrectModelObject = currentModel2 === model;
};
let assertions = Promise.reject(new Error('onModelChange not called'));
sub.onModelChanged = (modelId, model) => (assertions = gatherAssertions(modelId, model));
const patch = [
{ op: 'replace', path: '/name', value: MODEL1.name },
];
fake.fakeModelChange(MODEL1_ID, patch);
await asyncsResolved();
await assertions;
(0, chai_1.expect)(sawCorrectModelId, 'incorrect model ID in subscription call-back')
.to.be.true;
(0, chai_1.expect)(sawCorrectModelObject, 'incorrect model retrieved from hub during subscription call-back').to.be.true;
});
it('notifies dirty state', async () => {
const sub = await modelHub.subscribe(MODEL1_ID);
sub.onModelDirtyState = onModelDirtyState;
fake.fakeModelDirtyState(MODEL1_ID, true);
await asyncsResolved();
sinon_1.default.assert.calledWithMatch(onModelDirtyState, MODEL1_ID, MODEL1, true);
});
it('notifies model validation', async () => {
const sub = await modelHub.subscribe(MODEL1_ID);
sub.onModelValidated = onModelValidated;
const diagnostic = {
message: 'This is a test',
path: '/name',
severity: 'error',
source: 'test',
};
fake.fakeModelValidated(MODEL1_ID, diagnostic);
await asyncsResolved();
sinon_1.default.assert.calledWithMatch(onModelValidated, MODEL1_ID, MODEL1, diagnostic);
});
it('notifies model loaded', async () => {
const sub = await modelHub.subscribe(MODEL2_ID);
sub.onModelLoaded = onModelLoaded;
fake.setModel(MODEL2_ID, MODEL2);
await asyncsResolved();
sinon_1.default.assert.calledWithExactly(onModelLoaded, MODEL2_ID);
});
it('notifies model unloaded', async () => {
await modelHub.getModel(MODEL1_ID);
const sub = await modelHub.subscribe(MODEL1_ID);
sub.onModelUnloaded = onModelUnloaded;
fake.removeModel(MODEL1_ID);
await asyncsResolved();
sinon_1.default.assert.calledWithMatch(onModelUnloaded, MODEL1_ID, MODEL1);
});
it('notifies hub disposal', async () => {
const sub = await modelHub.subscribe();
sub.onModelHubDisposed = onModelHubDisposed;
fake.fakeModelHubDisposed();
await asyncsResolved();
sinon_1.default.assert.called(onModelHubDisposed);
(0, chai_1.expect)(modelHub.isDisposed).to.be.true;
});
describe('closes subscriptions', () => {
const patch = [
{ op: 'replace', path: '/name', value: MODEL1.name },
];
let sub;
beforeEach(async () => {
sub = await modelHub.subscribe(MODEL1_ID);
sub.onModelChanged = onModelChanged;
fake.fakeModelChange(MODEL1_ID, patch);
return asyncsResolved();
});
it('from the frontend', async () => {
sub.close();
await asyncsResolved();
fake.fakeModelChange(MODEL1_ID, patch);
await asyncsResolved();
(0, chai_1.expect)(onModelChanged.callCount).to.be.equal(1);
});
it('from the backend', async () => {
fake.fakeSubscriptionClosed(MODEL1_ID);
await asyncsResolved();
fake.fakeModelChange(MODEL1_ID, patch);
await asyncsResolved();
(0, chai_1.expect)(onModelChanged.callCount).to.be.equal(1);
});
});
it("doesn't blow up on unassigned call-back", async () => {
await modelHub.subscribe(MODEL1_ID);
fake.fakeModelChange(MODEL1_ID, []);
fake.fakeModelDirtyState(MODEL1_ID, true);
fake.fakeModelValidated(MODEL1_ID, (0, model_validation_1.ok)());
await asyncsResolved();
});
});
describe('error cases', () => {
it('initialization failure', async () => {
const errorStub = sandbox.stub(console, 'error');
await provider('Boom!');
await asyncsResolved();
(0, chai_1.expect)(errorStub).to.have.been.calledWithMatch('Failed to initialize', 'Bomb context');
});
});
});
function asyncsResolved() {
// It only takes until the next tick because in the test fixture
// the promises involved are all a priori resolved
return new Promise((resolve) => {
setImmediate(() => {
resolve();
});
});
}
//# sourceMappingURL=frontend-model-hub.spec.js.map