import React from 'react';
import {
cleanup,
render,
screen,
waitFor,
fireEvent,
within,
} from '@testing-library/react';
import { expect } from 'chai';
import type { ConnectionOptions } from 'mongodb-data-service';
import type {
ConnectionInfo,
ConnectionStorage,
} from '@mongodb-js/connection-storage/renderer';
import { v4 as uuid } from 'uuid';
import sinon from 'sinon';
import Connections from './connections';
import { ToastArea } from '@mongodb-js/compass-components';
import preferencesAccess from 'compass-preferences-model';
function getMockConnectionStorage(mockConnections: ConnectionInfo[]) {
return {
loadAll: () => {
return Promise.resolve(mockConnections);
},
getLegacyConnections: () => Promise.resolve([]),
save: () => Promise.resolve(),
delete: () => Promise.resolve(),
load: (id: string) =>
Promise.resolve(mockConnections.find((conn) => conn.id === id)),
importConnections: () => Promise.resolve([]),
exportConnections: () => Promise.resolve('{}'),
deserializeConnections: () => Promise.resolve([]),
} as unknown as ConnectionStorage;
}
async function loadSavedConnectionAndConnect(connectionInfo: ConnectionInfo) {
const savedConnectionButton = screen.getByTestId(
`saved-connection-button-${connectionInfo.id}`
);
fireEvent.click(savedConnectionButton);
// Wait for the connection to load in the form.
await waitFor(() =>
expect(screen.queryByRole('textbox')?.textContent).to.equal(
connectionInfo.connectionOptions.connectionString
)
);
const connectButton = screen.getByText('Connect');
fireEvent.click(connectButton);
// Wait for the connecting... modal to hide.
await waitFor(() => expect(screen.queryByText('Cancel')).to.not.exist);
}
describe('Connections Component', function () {
let persistOIDCTokens: boolean | undefined;
let onConnectedSpy: sinon.SinonSpy;
before(async function () {
persistOIDCTokens = preferencesAccess.getPreferences().persistOIDCTokens;
await preferencesAccess.savePreferences({ persistOIDCTokens: false });
});
after(async function () {
await preferencesAccess.savePreferences({ persistOIDCTokens });
});
beforeEach(function () {
onConnectedSpy = sinon.spy();
});
afterEach(function () {
sinon.restore();
cleanup();
});
describe('when rendered', function () {
let loadConnectionsSpy: sinon.SinonSpy;
beforeEach(function () {
const mockStorage = getMockConnectionStorage([]);
loadConnectionsSpy = sinon.spy(mockStorage, 'loadAll');
render(
);
});
it('calls once to load the connections', function () {
expect(loadConnectionsSpy.callCount).to.equal(1);
});
it('renders the connect button from the connect-form', function () {
const button = screen.queryByText('Connect')?.closest('button');
expect(button).to.not.equal(null);
});
it('renders atlas cta button', function () {
const button = screen.getByTestId('atlas-cta-link');
expect(button.getAttribute('href')).to.equal(
'https://www.mongodb.com/cloud/atlas/lp/try4?utm_source=compass&utm_medium=product&utm_content=v1'
);
});
it('shows two connections lists', function () {
const listItems = screen.getAllByRole('list');
expect(listItems.length).to.equal(2);
});
it('should not show any banners', function () {
expect(screen.queryByRole('alert')).to.not.exist;
});
it('should load an empty connections list with no connections', function () {
const listItems = screen.queryAllByRole('listitem');
expect(listItems.length).to.equal(0);
const favorites = screen.queryAllByTestId('favorite-connection');
expect(favorites.length).to.equal(0);
const recents = screen.queryAllByTestId('recent-connection');
expect(recents.length).to.equal(0);
});
});
describe('when rendered with saved connections in storage', function () {
let mockConnectFn: sinon.SinonSpy;
let mockStorage: ConnectionStorage;
let savedConnectionId: string;
let savedConnectionWithAppNameId: string;
let saveConnectionSpy: sinon.SinonSpy;
let connections: ConnectionInfo[];
beforeEach(async function () {
mockConnectFn = sinon.fake.resolves({
mockDataService: 'yes',
});
savedConnectionId = uuid();
savedConnectionWithAppNameId = uuid();
saveConnectionSpy = sinon.spy();
connections = [
{
id: savedConnectionId,
connectionOptions: {
connectionString:
'mongodb://localhost:27018/?readPreference=primary&ssl=false',
},
},
{
id: savedConnectionWithAppNameId,
connectionOptions: {
connectionString:
'mongodb://localhost:27019/?appName=Some+App+Name',
},
},
];
mockStorage = getMockConnectionStorage(connections);
sinon.replace(mockStorage, 'save', saveConnectionSpy);
render(
);
await waitFor(() => expect(screen.queryAllByRole('listitem')).to.exist);
});
it('should render the saved connections', function () {
const listItems = screen.getAllByRole('listitem');
expect(listItems.length).to.equal(2);
const favorites = screen.queryAllByTestId('favorite-connection');
expect(favorites.length).to.equal(0);
const recents = screen.getAllByTestId('recent-connection');
expect(recents.length).to.equal(2);
});
it('renders the title of the saved connection', function () {
expect(screen.getByText('localhost:27018')).to.be.visible;
});
describe('when a saved connection is clicked on and connected to', function () {
const _Date = globalThis.Date;
beforeEach(async function () {
globalThis.Date = class {
constructor() {
return new _Date(0);
}
static now() {
return 0;
}
} as DateConstructor;
await loadSavedConnectionAndConnect(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
connections.find(({ id }) => id === savedConnectionId)!
);
});
afterEach(function () {
globalThis.Date = _Date;
});
it('should call the connect function with the connection options to connect', function () {
expect(mockConnectFn.callCount).to.equal(1);
expect(mockConnectFn.firstCall.args[0].connectionOptions).to.deep.equal(
{
connectionString:
'mongodb://localhost:27018/?readPreference=primary&ssl=false&appName=Test+App+Name',
oidc: {},
}
);
});
it('should call to save the connection with the connection config', function () {
expect(saveConnectionSpy.callCount).to.equal(1);
expect(saveConnectionSpy.firstCall.args[0].connectionInfo.id).to.equal(
savedConnectionId
);
expect(
saveConnectionSpy.firstCall.args[0].connectionInfo.connectionOptions
).to.deep.equal({
connectionString:
'mongodb://localhost:27018/?readPreference=primary&ssl=false',
});
});
it('should call to save the connection with a new lastUsed time', function () {
expect(saveConnectionSpy.callCount).to.equal(1);
expect(
saveConnectionSpy.firstCall.args[0].connectionInfo.lastUsed.getTime()
).to.equal(0);
});
it('should emit the connection configuration used to connect', function () {
expect(onConnectedSpy.firstCall.args[0].id).to.equal(savedConnectionId);
expect(
onConnectedSpy.firstCall.args[0].connectionOptions
).to.deep.equal({
connectionString:
'mongodb://localhost:27018/?readPreference=primary&ssl=false',
});
});
it('should emit the data service', function () {
expect(onConnectedSpy.firstCall.args[1].mockDataService).to.equal(
'yes'
);
});
});
describe('when a saved connection with appName is clicked on and connected to', function () {
beforeEach(async function () {
await loadSavedConnectionAndConnect(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
connections.find(({ id }) => id === savedConnectionWithAppNameId)!
);
});
it('should call the connect function without replacing appName', function () {
expect(mockConnectFn.callCount).to.equal(1);
expect(mockConnectFn.firstCall.args[0].connectionOptions).to.deep.equal(
{
connectionString:
'mongodb://localhost:27019/?appName=Some+App+Name',
oidc: {},
}
);
});
});
});
describe('connecting to a connection that is not succeeding', function () {
let mockConnectFn: sinon.SinonSpy;
let saveConnectionSpy: sinon.SinonSpy;
let savedConnectableId: string;
let savedUnconnectableId: string;
let connections: ConnectionInfo[];
beforeEach(async function () {
saveConnectionSpy = sinon.spy();
savedConnectableId = uuid();
savedUnconnectableId = uuid();
mockConnectFn = sinon.fake(
async ({
connectionOptions,
}: {
connectionOptions: ConnectionOptions;
}) => {
if (
connectionOptions.connectionString ===
'mongodb://localhost:27099/?connectTimeoutMS=5000&serverSelectionTimeoutMS=5000&appName=Test+App+Name'
) {
return new Promise((resolve) => {
// On first call we want this attempt to be cancelled before
// this promise resolves.
setTimeout(resolve, 20);
});
}
return Promise.resolve({
mockDataService: 'yes',
});
}
);
connections = [
{
id: savedConnectableId,
connectionOptions: {
connectionString:
'mongodb://localhost:27018/?readPreference=primary&ssl=false',
},
},
{
id: savedUnconnectableId,
connectionOptions: {
connectionString:
'mongodb://localhost:27099/?connectTimeoutMS=5000&serverSelectionTimeoutMS=5000',
},
},
];
const mockStorage = getMockConnectionStorage(connections);
sinon.replace(mockStorage, 'save', saveConnectionSpy);
render(
);
await waitFor(
() =>
expect(
screen.queryByTestId(
`saved-connection-button-${savedUnconnectableId}`
)
).to.exist
);
const savedConnectionButton = screen.getByTestId(
`saved-connection-button-${savedUnconnectableId}`
);
fireEvent.click(savedConnectionButton);
// Wait for the connection to load in the form.
await waitFor(() =>
expect(screen.queryByRole('textbox')?.textContent).to.equal(
'mongodb://localhost:27099/?connectTimeoutMS=5000&serverSelectionTimeoutMS=5000'
)
);
const connectButton = screen.getByText('Connect');
fireEvent.click(connectButton);
// Wait for the connecting... modal to be shown.
await waitFor(() => expect(screen.queryByText('Cancel')).to.be.visible);
});
describe('when the connection attempt is cancelled', function () {
beforeEach(async function () {
const cancelButton = screen.getByText('Cancel');
fireEvent.click(cancelButton);
// Wait for the connecting... modal to hide.
await waitFor(() => expect(screen.queryByText('Cancel')).to.not.exist);
});
it('should enable the connect button', function () {
const connectButton = screen.getByText('Connect');
expect(connectButton).to.not.match('disabled');
});
it('should not call to save the connection', function () {
expect(saveConnectionSpy.callCount).to.equal(0);
});
it('should not emit connected', function () {
expect(onConnectedSpy.called).to.equal(false);
});
it('should have the connections-wrapper test id', function () {
expect(screen.getByTestId('connections-wrapper')).to.be.visible;
});
it('should call the connect function with the connection options to connect', function () {
expect(mockConnectFn.callCount).to.equal(1);
expect(mockConnectFn.firstCall.args[0].connectionOptions).to.deep.equal(
{
connectionString:
'mongodb://localhost:27099/?connectTimeoutMS=5000&serverSelectionTimeoutMS=5000&appName=Test+App+Name',
oidc: {},
}
);
});
describe('connecting to a successful connection after cancelling a connect', function () {
beforeEach(async function () {
await loadSavedConnectionAndConnect(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
connections.find(({ id }) => id === savedConnectableId)!
);
});
it('should call onConnected once', function () {
expect(onConnectedSpy.callCount).to.equal(1);
});
it('should call to save the connection once', function () {
expect(saveConnectionSpy.callCount).to.equal(1);
});
it('should emit the connection configuration used to connect', function () {
expect(onConnectedSpy.firstCall.args[0].id).to.equal(
savedConnectableId
);
expect(
onConnectedSpy.firstCall.args[0].connectionOptions
).to.deep.equal({
connectionString:
'mongodb://localhost:27018/?readPreference=primary&ssl=false',
});
});
it('should call the connect function with the connection options to connect', function () {
expect(mockConnectFn.callCount).to.equal(2);
expect(
mockConnectFn.secondCall.args[0].connectionOptions
).to.deep.equal({
connectionString:
'mongodb://localhost:27018/?readPreference=primary&ssl=false&appName=Test+App+Name',
oidc: {},
});
});
it('should emit the data service', function () {
expect(onConnectedSpy.firstCall.args[1].mockDataService).to.equal(
'yes'
);
});
});
});
});
context('when user has any legacy connection', function () {
it('shows modal', async function () {
const mockStorage = getMockConnectionStorage([]);
sinon
.stub(mockStorage, 'getLegacyConnections')
.resolves([{ name: 'Connection1' }]);
render(
);
await waitFor(
() => expect(screen.getByTestId('legacy-connections-modal')).to.exist
);
const modal = screen.getByTestId('legacy-connections-modal');
expect(within(modal).getByText('Connection1')).to.exist;
});
it('does not show modal when user hides it', async function () {
const mockStorage = getMockConnectionStorage([]);
sinon
.stub(mockStorage, 'getLegacyConnections')
.resolves([{ name: 'Connection2' }]);
const { rerender } = render(
);
await waitFor(() => screen.getByTestId('legacy-connections-modal'));
const modal = screen.getByTestId('legacy-connections-modal');
const storageSpy = sinon.spy(Storage.prototype, 'setItem');
// Click the don't show again checkbox and close the modal
fireEvent.click(within(modal).getByText(/don't show this again/i));
fireEvent.click(within(modal).getByText(/close/i));
rerender(
);
// Saves data in storage
expect(storageSpy.firstCall.args).to.deep.equal([
'hide_legacy_connections_modal',
'true',
]);
expect(() => {
screen.getByTestId('legacy-connections-modal');
}).to.throw;
});
});
});