Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React from 'react';
import { cleanup, render, screen } from '@testing-library/react';
import { expect } from 'chai';
import sinon from 'sinon';

import AdvancedOptionsTabs from './advanced-options-tabs';

const testUrl = 'mongodb+srv://0ranges:p!neapp1es@localhost/?ssl=true';

describe('AdvancedOptionsTabs Component', function () {
let updateConnectionFormFieldSpy: sinon.SinonSpy;

beforeEach(function () {
updateConnectionFormFieldSpy = sinon.spy();
});

afterEach(cleanup);

it('should render all of the tabs', function () {
render(
<AdvancedOptionsTabs
connectionOptions={{
connectionString: testUrl,
}}
errors={[]}
updateConnectionFormField={updateConnectionFormFieldSpy}
/>
);

[
'General',
'Authentication',
'TLS/SSL',
'Proxy/SSH Tunnel',
'Advanced',
].forEach((tabName) => {
expect(screen.getByText(tabName)).to.be.visible;
});
});

it('should have the tab with an error have the error', function () {
render(
<AdvancedOptionsTabs
connectionOptions={{
connectionString: testUrl,
}}
errors={[
{
fieldTab: 'advanced',
fieldName: 'connectionString',
message: 'oranges',
},
]}
updateConnectionFormField={updateConnectionFormFieldSpy}
/>
);

['General', 'Authentication', 'TLS/SSL', 'Proxy/SSH Tunnel'].forEach(
(tabName) => {
const tab = screen.getAllByTestId(`${tabName}-tab`)[0];
expect(tab.getAttribute('data-has-error')).to.equal('false');
}
);
expect(
screen.getAllByTestId('Advanced-tab')[0].getAttribute('data-has-error')
).to.equal('true');
});

it('should have an aria-label for the tab that shows the error count', function () {
render(
<AdvancedOptionsTabs
connectionOptions={{
connectionString: testUrl,
}}
errors={[
{
fieldTab: 'tls',
fieldName: 'tls',
message: 'oranges',
},
{
fieldTab: 'tls',
fieldName: 'tlsCertificateKeyFile',
message: 'peaches',
},
]}
updateConnectionFormField={updateConnectionFormFieldSpy}
/>
);

['General', 'Authentication', 'Proxy/SSH Tunnel', 'Advanced'].forEach(
(tabName) => {
const tab = screen.getAllByTestId(`${tabName}-tab`)[0];
expect(tab.getAttribute('aria-label')).to.equal(tabName);
}
);
expect(
screen.getAllByTestId('TLS/SSL-tab')[0].getAttribute('aria-label')
).to.equal('TLS/SSL (2 errors)');
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import React, { useState, useMemo } from 'react';
import { Tabs, Tab, spacing, css } from '@mongodb-js/compass-components';
import {
Tabs,
Tab,
spacing,
css,
cx,
uiColors,
} from '@mongodb-js/compass-components';
import ConnectionStringUrl from 'mongodb-connection-string-url';
import { ConnectionOptions } from 'mongodb-data-service';

Expand All @@ -9,14 +16,34 @@ import ProxyAndSshTunnelTab from './ssh-tunnel-tab/proxy-and-ssh-tunnel-tab';
import TLSTab from './tls-ssl-tab/tls-ssl-tab';
import AdvancedTab from './advanced-tab/advanced-tab';
import { UpdateConnectionFormField } from '../../hooks/use-connect-form';
import { ConnectionFormError } from '../../utils/validation';
import {
ConnectionFormError,
TabId,
errorsByFieldTab,
} from '../../utils/validation';
import { defaultConnectionString } from '../../constants/default-connection';

const tabsStyles = css({
marginTop: spacing[1],
});

const tabWithErrorIndicatorStyles = css({
position: 'relative',
'&::after': {
position: 'absolute',
top: -spacing[2],
right: -spacing[2],
content: '""',
width: spacing[2],
height: spacing[2],
borderRadius: '50%',
backgroundColor: uiColors.red.base,
},
});

interface TabObject {
name: string;
id: TabId;
component: React.FunctionComponent<{
errors: ConnectionFormError[];
connectionStringUrl: ConnectionStringUrl;
Expand All @@ -37,11 +64,15 @@ function AdvancedOptionsTabs({
const [activeTab, setActiveTab] = useState(0);

const tabs: TabObject[] = [
{ name: 'General', component: GeneralTab },
{ name: 'Authentication', component: AuthenticationTab },
{ name: 'TLS/SSL', component: TLSTab },
{ name: 'Proxy/SSH Tunnel', component: ProxyAndSshTunnelTab },
{ name: 'Advanced', component: AdvancedTab },
{ name: 'General', id: 'general', component: GeneralTab },
{
name: 'Authentication',
id: 'authentication',
component: AuthenticationTab,
},
{ name: 'TLS/SSL', id: 'tls', component: TLSTab },
{ name: 'Proxy/SSH Tunnel', id: 'proxy', component: ProxyAndSshTunnelTab },
{ name: 'Advanced', id: 'advanced', component: AdvancedTab },
];

const connectionStringUrl = useMemo(() => {
Expand All @@ -63,8 +94,31 @@ function AdvancedOptionsTabs({
{tabs.map((tabObject: TabObject, idx: number) => {
const TabComponent = tabObject.component;

const tabErrors = errorsByFieldTab(errors, tabObject.id);
const showTabErrorIndicator = tabErrors.length > 0;

return (
<Tab key={idx} name={tabObject.name} aria-label={tabObject.name}>
<Tab
key={idx}
name={
<div
className={cx({
[tabWithErrorIndicatorStyles]: showTabErrorIndicator,
})}
>
{tabObject.name}
</div>
}
aria-label={`${tabObject.name}${
tabErrors.length > 0
? ` (${tabErrors.length} error${
tabErrors.length > 1 ? 's' : ''
})`
: ''
}`}
data-testid={`${tabObject.name}-tab`}
data-has-error={showTabErrorIndicator}
>
<TabComponent
errors={errors}
connectionStringUrl={connectionStringUrl}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ describe('HostInput', function () {
errors={[
{
fieldName: 'hosts',
fieldTab: 'general',
fieldIndex: 1,
message: 'Eeeee!!!',
},
Expand Down
2 changes: 1 addition & 1 deletion packages/connect-form/src/components/connect-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ function ConnectForm({
</Banner>
)}
<AdvancedConnectionOptions
errors={errors}
errors={connectionStringInvalidError ? [] : errors}
disabled={!!connectionStringInvalidError}
updateConnectionFormField={updateConnectionFormField}
connectionOptions={connectionOptions}
Expand Down
4 changes: 4 additions & 0 deletions packages/connect-form/src/hooks/use-connect-form.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ describe('use-connect-form hook', function () {
{
fieldName: 'hosts',
fieldIndex: 0,
fieldTab: 'general',
message:
'Host cannot be empty. The host is the address hostname, IP address, or UNIX domain socket where the mongodb instance is running.',
},
Expand Down Expand Up @@ -368,6 +369,7 @@ describe('use-connect-form hook', function () {
expect(updateResult.errors).to.deep.equal([
{
fieldName: 'hosts',
fieldTab: 'general',
fieldIndex: 1,
message: "Invalid character in host: '@'",
},
Expand Down Expand Up @@ -410,6 +412,7 @@ describe('use-connect-form hook', function () {
{
fieldName: 'hosts',
fieldIndex: 0,
fieldTab: 'general',
message: "Invalid character in host: '/'",
},
]);
Expand Down Expand Up @@ -451,6 +454,7 @@ describe('use-connect-form hook', function () {
{
fieldName: 'hosts',
fieldIndex: 0,
fieldTab: 'general',
message: "Invalid character in host: ':'",
},
]);
Expand Down
3 changes: 3 additions & 0 deletions packages/connect-form/src/hooks/use-connect-form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ function parseConnectionString(
? [
{
fieldName: 'connectionString',
fieldTab: 'general',
message: parsingError.message,
},
]
Expand Down Expand Up @@ -236,6 +237,7 @@ function handleUpdateHost({
errors: [
{
fieldName: 'hosts',
fieldTab: 'general',
fieldIndex,
message: (err as Error).message,
},
Expand Down Expand Up @@ -387,6 +389,7 @@ export function handleConnectionFormFieldUpdate(
errors: [
{
fieldName: 'isSrv',
fieldTab: 'general',
message: `Error updating connection schema: ${
(err as Error).message
}`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ describe('Authentication Handler', function () {
expect(res.errors).to.deep.equal([
{
fieldName: 'username',
fieldTab: 'authentication',
message:
'Username cannot be empty: "URI contained empty userinfo section"',
},
Expand Down Expand Up @@ -187,11 +188,13 @@ describe('Authentication Handler', function () {
expect(res.errors).to.deep.equal([
{
fieldName: 'username',
fieldTab: 'authentication',
message:
'Username cannot be empty: "URI contained empty userinfo section"',
},
{
fieldName: 'password',
fieldTab: 'authentication',
message: 'Please enter a username first',
},
]);
Expand Down
4 changes: 4 additions & 0 deletions packages/connect-form/src/utils/authentication-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export function handleUpdateUsername({
errors: [
{
fieldName: 'username',
fieldTab: 'authentication',
message: action.username
? parsingError.message
: `Username cannot be empty: "${parsingError.message}"`,
Expand Down Expand Up @@ -153,16 +154,19 @@ export function handleUpdatePassword({
? [
{
fieldName: 'password',
fieldTab: 'authentication',
message: parsingError.message,
},
]
: [
{
fieldName: 'username',
fieldTab: 'authentication',
message: `Username cannot be empty: "${parsingError.message}"`,
},
{
fieldName: 'password',
fieldTab: 'authentication',
message: 'Please enter a username first',
},
],
Expand Down
Loading