Skip to content

Commit 5d8502b

Browse files
committed
add tests
1 parent 3c135eb commit 5d8502b

File tree

6 files changed

+322
-212
lines changed

6 files changed

+322
-212
lines changed
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
import React from 'react';
2+
import { expect } from 'chai';
3+
import { screen, userEvent } from '@mongodb-js/testing-library-compass';
4+
import {
5+
CreateShardKeyForm,
6+
type CreateShardKeyFormProps,
7+
} from './create-shard-key-form';
8+
import { renderWithStore } from '../../tests/create-store';
9+
import sinon from 'sinon';
10+
11+
function renderWithProps(props?: Partial<CreateShardKeyFormProps>) {
12+
return renderWithStore(
13+
<CreateShardKeyForm
14+
isSubmittingForSharding={false}
15+
isCancellingSharding={false}
16+
namespace="airbnb.test"
17+
onCreateShardKey={() => {}}
18+
{...props}
19+
/>
20+
);
21+
}
22+
23+
function setShardingKeyFieldValue(value: string) {
24+
const input = screen.getByLabelText('Second shard key field');
25+
expect(input).to.exist;
26+
userEvent.type(input, value);
27+
expect(input).to.have.value(value);
28+
userEvent.keyboard('{Escape}');
29+
30+
// For some reason, when running tests in electron mode, the value of
31+
// the input field is not being updated. This is a workaround to ensure
32+
// the value is being updated before clicking the submit button.
33+
userEvent.click(screen.getByText(value), undefined, {
34+
skipPointerEventsCheck: true,
35+
});
36+
}
37+
38+
describe('CreateShardKeyForm', function () {
39+
let onCreateShardKeySpy: sinon.SinonSpy;
40+
context('default', function () {
41+
beforeEach(async function () {
42+
onCreateShardKeySpy = sinon.spy();
43+
await renderWithProps({ onCreateShardKey: onCreateShardKeySpy });
44+
});
45+
46+
it('renders location form field as disabled', function () {
47+
expect(screen.getByLabelText('First shard key field')).to.have.attribute(
48+
'aria-disabled',
49+
'true'
50+
);
51+
});
52+
53+
it('does not allow user to submit when no second shard key is selected', function () {
54+
expect(screen.getByTestId('shard-collection-button')).to.have.attribute(
55+
'aria-disabled',
56+
'true'
57+
);
58+
59+
userEvent.click(screen.getByTestId('shard-collection-button'));
60+
expect(onCreateShardKeySpy.called).to.be.false;
61+
});
62+
63+
it('allows user to input second shard key and submit it', function () {
64+
setShardingKeyFieldValue('name');
65+
66+
userEvent.click(screen.getByTestId('shard-collection-button'));
67+
68+
expect(onCreateShardKeySpy.calledOnce).to.be.true;
69+
expect(onCreateShardKeySpy.firstCall.args[0]).to.deep.equal({
70+
customShardKey: 'name',
71+
isShardKeyUnique: false,
72+
isCustomShardKeyHashed: false,
73+
presplitHashedZones: false,
74+
numInitialChunks: null,
75+
});
76+
});
77+
78+
it('renders advanced options and radio buttons for: default, unique-index and hashed index', function () {
79+
const accordian = screen.getByText('Advanced Shard Key Configuration');
80+
expect(accordian).to.exist;
81+
82+
userEvent.click(accordian);
83+
84+
const defaultRadio = screen.getByLabelText('Default');
85+
const uniqueIndexRadio = screen.getByLabelText(
86+
'Use unique index as the shard key'
87+
);
88+
const hashedIndexRadio = screen.getByLabelText(
89+
'Use hashed index as the shard key'
90+
);
91+
92+
expect(defaultRadio).to.exist;
93+
expect(uniqueIndexRadio).to.exist;
94+
expect(hashedIndexRadio).to.exist;
95+
});
96+
97+
it('allows user to select unique index as shard key', function () {
98+
const accordian = screen.getByText('Advanced Shard Key Configuration');
99+
userEvent.click(accordian);
100+
101+
const uniqueIndexRadio = screen.getByLabelText(
102+
'Use unique index as the shard key'
103+
);
104+
userEvent.click(uniqueIndexRadio);
105+
106+
expect(uniqueIndexRadio).to.have.attribute('aria-checked', 'true');
107+
108+
setShardingKeyFieldValue('name');
109+
110+
userEvent.click(screen.getByTestId('shard-collection-button'));
111+
112+
expect(onCreateShardKeySpy.calledOnce).to.be.true;
113+
expect(onCreateShardKeySpy.firstCall.args[0]).to.deep.equal({
114+
customShardKey: 'name',
115+
isShardKeyUnique: true,
116+
isCustomShardKeyHashed: false,
117+
presplitHashedZones: false,
118+
numInitialChunks: null,
119+
});
120+
});
121+
122+
it('allows user to select hashed index as shard key with split-chunks option', function () {
123+
const accordian = screen.getByText('Advanced Shard Key Configuration');
124+
userEvent.click(accordian);
125+
126+
const hashedIndexRadio = screen.getByLabelText(
127+
'Use hashed index as the shard key'
128+
);
129+
userEvent.click(hashedIndexRadio);
130+
131+
expect(hashedIndexRadio).to.have.attribute('aria-checked', 'true');
132+
133+
setShardingKeyFieldValue('name');
134+
135+
// Check pre-split data
136+
userEvent.click(screen.getByTestId('presplit-data-checkbox'), undefined, {
137+
skipPointerEventsCheck: true,
138+
});
139+
140+
userEvent.click(screen.getByTestId('shard-collection-button'));
141+
142+
expect(onCreateShardKeySpy.calledOnce).to.be.true;
143+
expect(onCreateShardKeySpy.firstCall.args[0]).to.deep.equal({
144+
customShardKey: 'name',
145+
isShardKeyUnique: false,
146+
isCustomShardKeyHashed: true,
147+
presplitHashedZones: true,
148+
numInitialChunks: null,
149+
});
150+
});
151+
152+
it('allows user to select hashed index as shard key with all its options', function () {
153+
const accordian = screen.getByText('Advanced Shard Key Configuration');
154+
userEvent.click(accordian);
155+
156+
const hashedIndexRadio = screen.getByLabelText(
157+
'Use hashed index as the shard key'
158+
);
159+
userEvent.click(hashedIndexRadio);
160+
161+
expect(hashedIndexRadio).to.have.attribute('aria-checked', 'true');
162+
163+
setShardingKeyFieldValue('name');
164+
165+
// Check pre-split data
166+
userEvent.click(screen.getByTestId('presplit-data-checkbox'), undefined, {
167+
skipPointerEventsCheck: true,
168+
});
169+
170+
// Enter number of chunks
171+
userEvent.type(screen.getByTestId('chunks-per-shard-input'), '10');
172+
173+
userEvent.click(screen.getByTestId('shard-collection-button'));
174+
175+
expect(onCreateShardKeySpy.calledOnce).to.be.true;
176+
expect(onCreateShardKeySpy.firstCall.args[0]).to.deep.equal({
177+
customShardKey: 'name',
178+
isShardKeyUnique: false,
179+
isCustomShardKeyHashed: true,
180+
presplitHashedZones: true,
181+
numInitialChunks: 10,
182+
});
183+
});
184+
});
185+
186+
it('cannot be submitted when already submitting', async function () {
187+
onCreateShardKeySpy = sinon.spy();
188+
await renderWithProps({
189+
onCreateShardKey: onCreateShardKeySpy,
190+
isSubmittingForSharding: true,
191+
});
192+
setShardingKeyFieldValue('name');
193+
194+
userEvent.click(screen.getByTestId('shard-collection-button'));
195+
196+
expect(onCreateShardKeySpy.calledOnce).to.be.false;
197+
});
198+
199+
it('cannot be submitted when cancelling', async function () {
200+
onCreateShardKeySpy = sinon.spy();
201+
await renderWithProps({
202+
onCreateShardKey: onCreateShardKeySpy,
203+
isCancellingSharding: true,
204+
});
205+
setShardingKeyFieldValue('name');
206+
207+
userEvent.click(screen.getByTestId('shard-collection-button'));
208+
209+
expect(onCreateShardKeySpy.calledOnce).to.be.false;
210+
});
211+
});

packages/compass-global-writes/src/components/create-shard-key-form.tsx

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,14 @@ import {
1717
Subtitle,
1818
TextInput,
1919
} from '@mongodb-js/compass-components';
20-
import type { CreateShardKeyData } from '../store/reducer';
20+
import {
21+
createShardKey,
22+
type RootState,
23+
ShardingStatuses,
24+
type CreateShardKeyData,
25+
} from '../store/reducer';
2126
import { useAutocompleteFields } from '@mongodb-js/compass-field-store';
27+
import { connect } from 'react-redux';
2228

2329
const contentStyles = css({
2430
display: 'flex',
@@ -108,12 +114,14 @@ function CreateShardKeyDescription() {
108114
export type CreateShardKeyFormProps = {
109115
namespace: string;
110116
isSubmittingForSharding: boolean;
117+
isCancellingSharding: boolean;
111118
onCreateShardKey: (data: CreateShardKeyData) => void;
112119
};
113120

114-
function CreateShardKeyForm({
121+
export function CreateShardKeyForm({
115122
namespace,
116123
isSubmittingForSharding,
124+
isCancellingSharding,
117125
onCreateShardKey,
118126
}: CreateShardKeyFormProps) {
119127
const [isAdvancedOptionsOpen, setIsAdvancedOptionsOpen] = useState(false);
@@ -297,7 +305,9 @@ function CreateShardKeyForm({
297305
<Button
298306
data-testid="shard-collection-button"
299307
onClick={onSubmit}
300-
disabled={!secondShardKey || isSubmittingForSharding}
308+
disabled={
309+
!secondShardKey || isSubmittingForSharding || isCancellingSharding
310+
}
301311
variant="primary"
302312
isLoading={isSubmittingForSharding}
303313
>
@@ -309,4 +319,21 @@ function CreateShardKeyForm({
309319
);
310320
}
311321

312-
export default CreateShardKeyForm;
322+
export default connect(
323+
(state: RootState) => {
324+
return {
325+
namespace: state.namespace,
326+
isSubmittingForSharding: [
327+
ShardingStatuses.SUBMITTING_FOR_SHARDING,
328+
ShardingStatuses.SUBMITTING_FOR_SHARDING_ERROR,
329+
].includes(state.status),
330+
isCancellingSharding: [
331+
ShardingStatuses.CANCELLING_SHARDING,
332+
ShardingStatuses.CANCELLING_SHARDING_ERROR,
333+
].includes(state.status),
334+
};
335+
},
336+
{
337+
onCreateShardKey: createShardKey,
338+
}
339+
)(CreateShardKeyForm);
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import React from 'react';
2+
import { expect } from 'chai';
3+
import { screen, userEvent } from '@mongodb-js/testing-library-compass';
4+
import { ShardingError } from './sharding-error';
5+
import { renderWithStore } from '../../../tests/create-store';
6+
import Sinon from 'sinon';
7+
8+
const shardingError = 'This is an error';
9+
function renderWithProps(
10+
props?: Partial<React.ComponentProps<typeof ShardingError>>
11+
) {
12+
return renderWithStore(
13+
<ShardingError
14+
isSubmittingForSharding={false}
15+
isCancellingSharding={false}
16+
shardingError={shardingError}
17+
onCancelSharding={() => {}}
18+
{...props}
19+
/>
20+
);
21+
}
22+
23+
describe('ShardingError', function () {
24+
it('renders the error', async function () {
25+
await renderWithProps();
26+
expect(screen.getByText(/There was an error sharding your collection/)).to
27+
.be.visible;
28+
expect(screen.getByText(shardingError)).to.be.visible;
29+
});
30+
31+
it('includes a button to cancel sharding', async function () {
32+
const onCancelSharding = Sinon.spy();
33+
await renderWithProps({ onCancelSharding });
34+
const btn = screen.getByRole('button', { name: 'Cancel Request' });
35+
expect(btn).to.be.visible;
36+
37+
userEvent.click(btn);
38+
expect(onCancelSharding).to.have.been.called;
39+
});
40+
41+
it('the cancel sharding button is disabled when cancelling is in progress', async function () {
42+
const onCancelSharding = Sinon.spy();
43+
await renderWithProps({ onCancelSharding, isCancellingSharding: true });
44+
const btn = screen.getByTestId('cancel-sharding-btn');
45+
46+
userEvent.click(btn);
47+
expect(onCancelSharding).not.to.have.been.called;
48+
});
49+
50+
it('the cancel sharding button is disabled also when sharding is in progress', async function () {
51+
const onCancelSharding = Sinon.spy();
52+
await renderWithProps({ onCancelSharding, isSubmittingForSharding: true });
53+
const btn = screen.getByTestId('cancel-sharding-btn');
54+
55+
userEvent.click(btn);
56+
expect(onCancelSharding).not.to.have.been.called;
57+
});
58+
59+
it('includes the createShardKeyForm', async function () {
60+
await renderWithProps();
61+
expect(screen.getByRole('button', { name: 'Shard Collection' })).to.be
62+
.visible;
63+
});
64+
});

0 commit comments

Comments
 (0)