@opencloning/ui 1.4.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @opencloning/ui
2
2
 
3
+ ## 1.4.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#616](https://github.com/manulera/OpenCloning_frontend/pull/616) [`1419d5c`](https://github.com/manulera/OpenCloning_frontend/commit/1419d5cb80f738b737076e4d3b958c31eeb0f7b4) Thanks [@manulera](https://github.com/manulera)! - Allow to "select all" when importing static sequences from server
8
+
9
+ - Updated dependencies []:
10
+ - @opencloning/store@1.4.1
11
+ - @opencloning/utils@1.4.1
12
+
3
13
  ## 1.4.0
4
14
 
5
15
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencloning/ui",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
6
  "scripts": {
@@ -25,8 +25,8 @@
25
25
  "@emotion/styled": "^11.14.0",
26
26
  "@mui/icons-material": "^5.15.17",
27
27
  "@mui/material": "^5.15.17",
28
- "@opencloning/store": "1.4.0",
29
- "@opencloning/utils": "1.4.0",
28
+ "@opencloning/store": "1.4.1",
29
+ "@opencloning/utils": "1.4.1",
30
30
  "@teselagen/bio-parsers": "^0.4.34",
31
31
  "@teselagen/ove": "^0.8.34",
32
32
  "@teselagen/range-utils": "^0.3.20",
@@ -274,6 +274,115 @@ describe('<ServerStaticFileSelect />', () => {
274
274
  expect(file.size).to.equal(28);
275
275
  });
276
276
  });
277
+ it('allows selecting all sequences with the Select all option when multiple', () => {
278
+ const httpGet = cy.stub(localFilesHttpClient, 'get').callsFake((url) => {
279
+ if (url.endsWith('/index.json')) {
280
+ return Promise.resolve({
281
+ data: dummyIndex,
282
+ });
283
+ }
284
+ if (url.endsWith('/example.fa')) {
285
+ return Promise.resolve({ data: 'ATGC' });
286
+ }
287
+ if (url.endsWith('/example2.gb')) {
288
+ return Promise.resolve({ data: 'ATGCA' });
289
+ }
290
+ if (url.endsWith('/example3.fa')) {
291
+ return Promise.resolve({ data: 'ATGCG' });
292
+ }
293
+ throw new Error(`Unexpected URL: ${url}`);
294
+ });
295
+ cy.wrap(httpGet).as('httpGet');
296
+
297
+ const onFileSelected = cy.spy().as('onFileSelected');
298
+ cy.mount(
299
+ <ConfigProvider config={config}>
300
+ <ServerStaticFileSelect
301
+ onFileSelected={onFileSelected}
302
+ multiple
303
+ />
304
+ </ConfigProvider>,
305
+ );
306
+
307
+ cy.get('@httpGet').should('have.been.calledWithMatch', 'index.json');
308
+
309
+ // Use the Select all option to select all sequences
310
+ cy.get('#option-select').click();
311
+ cy.contains('Select all').click();
312
+ cy.get('body').click(0, 0);
313
+
314
+ // Submit form
315
+ cy.contains('button', 'Submit').click();
316
+
317
+ cy.get('@httpGet').should('have.been.calledWithMatch', 'example.fa');
318
+ cy.get('@httpGet').should('have.been.calledWithMatch', 'example2.gb');
319
+ cy.get('@httpGet').should('have.been.calledWithMatch', 'example3.fa');
320
+
321
+ cy.get('@onFileSelected').should('have.been.calledOnce');
322
+ cy.get('@onFileSelected').should((spy) => {
323
+ const [files] = spy.lastCall.args;
324
+ expect(files).to.have.length(3);
325
+ expect(files[0].name).to.equal('example.fa');
326
+ expect(files[0].type).to.equal('text/plain');
327
+ expect(files[1].name).to.equal('example2.gb');
328
+ expect(files[1].type).to.equal('text/plain');
329
+ expect(files[2].name).to.equal('example3.fa');
330
+ expect(files[2].type).to.equal('text/plain');
331
+ });
332
+ });
333
+ it('clicking select all only selects the sequences that were filtered by category', () => {
334
+ const httpGet = cy.stub(localFilesHttpClient, 'get').callsFake((url) => {
335
+ if (url.endsWith('/index.json')) {
336
+ return Promise.resolve({
337
+ data: dummyIndex,
338
+ });
339
+ }
340
+ if (url.endsWith('/example.fa')) {
341
+ return Promise.resolve({ data: 'ATGC' });
342
+ }
343
+ if (url.endsWith('/example3.fa')) {
344
+ return Promise.resolve({ data: 'ATGCG' });
345
+ }
346
+ });
347
+ cy.wrap(httpGet).as('httpGet');
348
+ const onFileSelected = cy.spy().as('onFileSelected');
349
+ cy.mount(
350
+ <ConfigProvider config={config}>
351
+ <ServerStaticFileSelect
352
+ onFileSelected={onFileSelected}
353
+ multiple
354
+ />
355
+ </ConfigProvider>,
356
+ );
357
+ cy.get('@httpGet').should('have.been.calledWithMatch', 'index.json');
358
+
359
+ // Select category
360
+ cy.get('#category-select').click();
361
+ cy.contains('Test category').click();
362
+
363
+ // Select all
364
+ cy.get('#option-select').click();
365
+ cy.contains('Select all').click();
366
+
367
+ // Only one sequence should be shown
368
+ cy.get('#option-select').click();
369
+ cy.contains('Example sequence 1').should('exist');
370
+ cy.contains('Example sequence 2').should('not.exist');
371
+ cy.contains('Example sequence 3').should('exist');
372
+
373
+ // Click outside to close select element
374
+ cy.get('body').click(0, 0);
375
+ cy.contains('button', 'Submit').click();
376
+
377
+ cy.get('@httpGet').should('have.been.calledThrice');
378
+ cy.get('@onFileSelected').should('have.been.calledOnce');
379
+ cy.get('@onFileSelected').should((spy) => {
380
+ const [files] = spy.lastCall.args;
381
+ expect(files).to.have.length(2);
382
+ expect(files[0].name).to.equal('example.fa');
383
+ expect(files[1].name).to.equal('example3.fa');
384
+ });
385
+ })
277
386
  it('works with multiple', () => {
278
387
  const httpGet = cy.stub(localFilesHttpClient, 'get').callsFake((url) => {
279
388
  if (url.endsWith('/index.json')) {
@@ -16,11 +16,12 @@ function ServerStaticFileSelect({ onFileSelected, multiple = false, type = 'sequ
16
16
  if (type !== 'sequence') {
17
17
  return index.syntaxes;
18
18
  }
19
+ const prePendArray = multiple ? ['__all__'] : [];
19
20
  if (selectedCategory === '') {
20
- return index.sequences;
21
+ return [...prePendArray, ...index.sequences];
21
22
  }
22
- return index.sequences.filter((sequence) => sequence.categories?.includes(selectedCategory));
23
- }, [type, index, selectedCategory]);
23
+ return [...prePendArray, ...index.sequences.filter((sequence) => sequence.categories?.includes(selectedCategory))];
24
+ }, [type, index, selectedCategory, multiple]);
24
25
 
25
26
  const categoryOptions = React.useMemo(() => {
26
27
  if (!index?.categories) return [];
@@ -72,6 +73,19 @@ function ServerStaticFileSelect({ onFileSelected, multiple = false, type = 'sequ
72
73
  const buttonDisabled = multiple ? selectedOptions.length === 0 : !selectedOptions;
73
74
 
74
75
  const label = type === 'sequence' ? 'Sequence' : 'Syntax';
76
+
77
+ const onOptionsChange = React.useCallback((event, value) => {
78
+ console.log('onOptionsChange', value);
79
+ if (multiple && type === 'sequence') {
80
+ if (value.includes('__all__')) {
81
+ const allSequences = options.filter((option) => option !== '__all__');
82
+ setSelectedOptions(allSequences);
83
+ return;
84
+ }
85
+ }
86
+ setSelectedOptions(value);
87
+ }, [multiple, type, options]);
88
+
75
89
  return (
76
90
  <RequestStatusWrapper requestStatus={indexRequestStatus} retry={indexRetry}>
77
91
  {error && <Alert severity="error">{error}</Alert>}
@@ -98,8 +112,8 @@ function ServerStaticFileSelect({ onFileSelected, multiple = false, type = 'sequ
98
112
  multiple={multiple}
99
113
  options={options}
100
114
  value={selectedOptions}
101
- onChange={(event, value) => setSelectedOptions(value)}
102
- getOptionLabel={(option) => option?.name || option?.path || ''}
115
+ onChange={onOptionsChange}
116
+ getOptionLabel={(option) => (option === '__all__' ? 'Select all' : option?.name || option?.path || '')}
103
117
  disableCloseOnSelect={multiple}
104
118
  renderInput={(params) => (
105
119
  <TextField
package/src/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Version placeholder - replaced at publish time via prepack script
2
- export const version = "1.4.0";
2
+ export const version = "1.4.1";