sequenceserver 3.0 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sequenceserver might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/bin/sequenceserver +2 -2
- data/lib/sequenceserver/api_errors.rb +1 -1
- data/lib/sequenceserver/blast/job.rb +20 -3
- data/lib/sequenceserver/blast/report.rb +74 -86
- data/lib/sequenceserver/blast/tasks.rb +38 -0
- data/lib/sequenceserver/config.rb +54 -20
- data/lib/sequenceserver/makeblastdb.rb +16 -2
- data/lib/sequenceserver/report.rb +0 -6
- data/lib/sequenceserver/routes.rb +32 -21
- data/lib/sequenceserver/version.rb +1 -1
- data/lib/sequenceserver.rb +1 -1
- data/public/css/app.css +121 -0
- data/public/css/app.min.css +1 -0
- data/public/css/sequenceserver.css +0 -148
- data/public/css/sequenceserver.min.css +3 -3
- data/public/js/circos.js +2 -2
- data/public/js/collapse_preferences.js +37 -0
- data/public/js/databases.js +65 -37
- data/public/js/databases_tree.js +2 -1
- data/public/js/dnd.js +37 -50
- data/public/js/form.js +78 -50
- data/public/js/grapher.js +23 -37
- data/public/js/hits_overview.js +2 -2
- data/public/js/kablammo.js +2 -2
- data/public/js/length_distribution.js +3 -3
- data/public/js/null_plugins/grapher/histogram.js +25 -0
- data/public/js/null_plugins/options.js +3 -0
- data/public/js/null_plugins/query_stats.js +11 -0
- data/public/js/null_plugins/report_plugins.js +6 -1
- data/public/js/null_plugins/search_header_plugin.js +4 -0
- data/public/js/options.js +161 -56
- data/public/js/query.js +85 -59
- data/public/js/report.js +1 -1
- data/public/js/search.js +2 -0
- data/public/js/search_button.js +67 -56
- data/public/js/sidebar.js +1 -1
- data/public/js/tests/database.spec.js +5 -5
- data/public/js/tests/{advanced_parameters.spec.js → form.spec.js} +35 -1
- data/public/js/tests/mock_data/databases.json +5 -5
- data/public/js/tests/mocks/circos.js +6 -0
- data/public/js/tests/report.spec.js +4 -3
- data/public/js/tests/search_query.spec.js +5 -6
- data/public/sequenceserver-report.min.js +45 -23
- data/public/sequenceserver-search.min.js +57 -13
- data/public/sequenceserver_logo.webp +0 -0
- data/views/blastn_options.erb +66 -66
- data/views/blastp_options.erb +59 -59
- data/views/blastx_options.erb +68 -68
- data/views/layout.erb +60 -3
- data/views/search.erb +33 -38
- data/views/search_layout.erb +152 -0
- data/views/tblastn_options.erb +57 -57
- data/views/tblastx_options.erb +64 -64
- metadata +31 -22
- data/lib/sequenceserver/makeblastdb-modified-with-cache.rb +0 -345
- data/public/SequenceServer_logo.png +0 -0
data/public/js/query.js
CHANGED
@@ -5,6 +5,7 @@ import HitsOverview from './hits_overview';
|
|
5
5
|
import LengthDistribution from './length_distribution'; // length distribution of hits
|
6
6
|
import Utils from './utils';
|
7
7
|
import { fastqToFasta } from './fastq_to_fasta';
|
8
|
+
import CollapsePreferences from './collapse_preferences';
|
8
9
|
|
9
10
|
/**
|
10
11
|
* Query component displays query defline, graphical overview, length
|
@@ -59,7 +60,7 @@ export class ReportQuery extends Component {
|
|
59
60
|
hitsListJSX() {
|
60
61
|
return <div className="section-content">
|
61
62
|
<HitsOverview key={'GO_' + this.props.query.number} query={this.props.query} program={this.props.program} collapsed={this.props.veryBig} />
|
62
|
-
<LengthDistribution key={'LD_' + this.props.query.id} query={this.props.query} algorithm={this.props.program}
|
63
|
+
<LengthDistribution key={'LD_' + this.props.query.id} query={this.props.query} algorithm={this.props.program} />
|
63
64
|
<HitsTable key={'HT_' + this.props.query.number} query={this.props.query} imported_xml={this.props.imported_xml} />
|
64
65
|
</div>;
|
65
66
|
}
|
@@ -107,8 +108,8 @@ export class SearchQueryWidget extends Component {
|
|
107
108
|
this.preProcessSequence = this.preProcessSequence.bind(this);
|
108
109
|
this.notify = this.notify.bind(this);
|
109
110
|
|
110
|
-
this.textareaRef = createRef()
|
111
|
-
this.controlsRef = createRef()
|
111
|
+
this.textareaRef = createRef();
|
112
|
+
this.controlsRef = createRef();
|
112
113
|
}
|
113
114
|
|
114
115
|
|
@@ -116,13 +117,14 @@ export class SearchQueryWidget extends Component {
|
|
116
117
|
|
117
118
|
componentDidMount() {
|
118
119
|
$('body').click(function () {
|
119
|
-
$('
|
120
|
+
$('[data-notifications] [data-role=notification].active').hide('drop', { direction: 'up' }).removeClass('active');
|
120
121
|
});
|
121
122
|
}
|
122
123
|
|
123
124
|
componentDidUpdate() {
|
124
125
|
this.hideShowButton();
|
125
126
|
this.preProcessSequence();
|
127
|
+
this.props.onSequenceChanged(this.residuesCount());
|
126
128
|
|
127
129
|
var type = this.type();
|
128
130
|
if (!type || type !== this._type) {
|
@@ -155,6 +157,19 @@ export class SearchQueryWidget extends Component {
|
|
155
157
|
}
|
156
158
|
}
|
157
159
|
|
160
|
+
residuesCount() {
|
161
|
+
const sequence = this.value();
|
162
|
+
const lines = sequence.split('\n');
|
163
|
+
const residuesCount = lines.reduce((count, line) => {
|
164
|
+
if (!line.startsWith('>')) {
|
165
|
+
return count + line.length;
|
166
|
+
}
|
167
|
+
return count;
|
168
|
+
}, 0);
|
169
|
+
|
170
|
+
return residuesCount;
|
171
|
+
}
|
172
|
+
|
158
173
|
/**
|
159
174
|
* Clears textarea. Returns `this`.
|
160
175
|
*
|
@@ -311,13 +326,13 @@ export class SearchQueryWidget extends Component {
|
|
311
326
|
notify(type) {
|
312
327
|
this.indicateNormal();
|
313
328
|
clearTimeout(this.notification_timeout);
|
314
|
-
// $('
|
329
|
+
// $('[data-notifications] [data-role=notification].active').hide().removeClass('active');
|
315
330
|
|
316
331
|
if (type) {
|
317
332
|
$('#' + type + '-sequence-notification').show('drop', { direction: 'up' }).addClass('active');
|
318
333
|
|
319
334
|
this.notification_timeout = setTimeout(function () {
|
320
|
-
$('
|
335
|
+
$('[data-notifications] [data-role=notification].active').hide('drop', { direction: 'up' }).removeClass('active');
|
321
336
|
}, 5000);
|
322
337
|
|
323
338
|
if (type === 'mixed') {
|
@@ -328,14 +343,15 @@ export class SearchQueryWidget extends Component {
|
|
328
343
|
|
329
344
|
render() {
|
330
345
|
return (
|
331
|
-
<div
|
332
|
-
className="col-md-12">
|
346
|
+
<div className="relative">
|
333
347
|
<div
|
334
348
|
className="sequence">
|
335
349
|
<textarea
|
336
350
|
id="sequence" ref={this.textareaRef}
|
337
|
-
className="
|
351
|
+
className="block w-full p-4 text-gray-900 border border-gray-300 rounded-l-lg rounded-tr-lg bg-gray-50 text-base text-monospace"
|
338
352
|
name="sequence" value={this.state.value}
|
353
|
+
rows="6"
|
354
|
+
required="required"
|
339
355
|
placeholder="Paste query sequence(s) or drag file
|
340
356
|
containing query sequence(s) in FASTA format here ..."
|
341
357
|
spellCheck="false" autoFocus
|
@@ -343,16 +359,16 @@ export class SearchQueryWidget extends Component {
|
|
343
359
|
</textarea>
|
344
360
|
</div>
|
345
361
|
<div
|
346
|
-
className="hidden"
|
347
|
-
style={{ position: 'absolute', top: '4px', right: '19px' }}
|
362
|
+
className="hidden absolute top-2 right-2"
|
348
363
|
ref={this.controlsRef}>
|
349
364
|
<button
|
350
365
|
type="button"
|
351
|
-
className="
|
366
|
+
className="border border-gray-300 rounded bg-white hover:bg-gray-200" id="btn-sequence-clear"
|
352
367
|
title="Clear query sequence(s)."
|
353
368
|
onClick={this.clear}>
|
354
369
|
<span id="sequence-file"></span>
|
355
|
-
<i className="fa fa-times"></i>
|
370
|
+
<i className="fa fa-times w-6 h-6 p-1"></i>
|
371
|
+
<span className="sr-only">Clear query sequence(s).</span>
|
356
372
|
</button>
|
357
373
|
</div>
|
358
374
|
</div>
|
@@ -368,8 +384,14 @@ export class SearchQueryWidget extends Component {
|
|
368
384
|
class HitsTable extends Component {
|
369
385
|
constructor(props) {
|
370
386
|
super(props);
|
387
|
+
this.name = 'Hit sequences producing significant alignments';
|
388
|
+
this.collapsePreferences = new CollapsePreferences(this);
|
389
|
+
this.state = {
|
390
|
+
collapsed: this.collapsePreferences.preferenceStoredAsCollapsed()
|
391
|
+
};
|
371
392
|
}
|
372
|
-
|
393
|
+
|
394
|
+
tableJSX() {
|
373
395
|
var hasName = _.every(this.props.query.hits, function (hit) {
|
374
396
|
return hit.sciname !== '';
|
375
397
|
});
|
@@ -385,54 +407,58 @@ class HitsTable extends Component {
|
|
385
407
|
// column.
|
386
408
|
if (this.props.imported_xml) seqwidth += 15;
|
387
409
|
|
410
|
+
return <table
|
411
|
+
className="table table-hover table-condensed tabular-view ">
|
412
|
+
<thead>
|
413
|
+
<tr>
|
414
|
+
<th className="text-left">#</th>
|
415
|
+
<th width={`${seqwidth}%`}>Similar sequences</th>
|
416
|
+
{hasName && <th width="15%" className="text-left">Species</th>}
|
417
|
+
{!this.props.imported_xml && <th width="15%" className="text-right">Query coverage (%)</th>}
|
418
|
+
<th width="10%" className="text-right">Total score</th>
|
419
|
+
<th width="10%" className="text-right">E value</th>
|
420
|
+
<th width="10%" className="text-right">Identity (%)</th>
|
421
|
+
</tr>
|
422
|
+
</thead>
|
423
|
+
<tbody>
|
424
|
+
{
|
425
|
+
_.map(this.props.query.hits, _.bind(function (hit) {
|
426
|
+
return (
|
427
|
+
<tr key={hit.number}>
|
428
|
+
<td className="text-left">{hit.number + '.'}</td>
|
429
|
+
<td className="nowrap-ellipsis"
|
430
|
+
title={`${hit.id} ${hit.title}`}
|
431
|
+
data-toggle="tooltip" data-placement="left">
|
432
|
+
<a href={'#Query_' + this.props.query.number + '_hit_' + hit.number}
|
433
|
+
className="btn-link">{hit.id} {hit.title}</a>
|
434
|
+
</td>
|
435
|
+
{hasName &&
|
436
|
+
<td className="nowrap-ellipsis" title={hit.sciname}
|
437
|
+
data-toggle="tooltip" data-placement="top">
|
438
|
+
{hit.sciname}
|
439
|
+
</td>
|
440
|
+
}
|
441
|
+
{!this.props.imported_xml && <td className="text-right">{hit.qcovs}</td>}
|
442
|
+
<td className="text-right">{hit.total_score}</td>
|
443
|
+
<td className="text-right">{Utils.inExponential(hit.hsps[0].evalue)}</td>
|
444
|
+
<td className="text-right">{Utils.inPercentage(hit.hsps[0].identity, hit.hsps[0].length)}</td>
|
445
|
+
</tr>
|
446
|
+
);
|
447
|
+
}, this))
|
448
|
+
}
|
449
|
+
</tbody>
|
450
|
+
</table>;
|
451
|
+
}
|
452
|
+
|
453
|
+
render() {
|
388
454
|
return (
|
389
455
|
<div className="table-hit-overview">
|
390
|
-
<h4 className="caption"
|
391
|
-
|
392
|
-
<span>
|
456
|
+
<h4 className="caption" onClick={() => this.collapsePreferences.toggleCollapse()}>
|
457
|
+
{this.collapsePreferences.renderCollapseIcon()}
|
458
|
+
<span> {this.name}</span>
|
393
459
|
</h4>
|
394
|
-
<div
|
395
|
-
|
396
|
-
className="table table-hover table-condensed tabular-view ">
|
397
|
-
<thead>
|
398
|
-
<tr>
|
399
|
-
<th className="text-left">#</th>
|
400
|
-
<th width={`${seqwidth}%`}>Similar sequences</th>
|
401
|
-
{hasName && <th width="15%" className="text-left">Species</th>}
|
402
|
-
{!this.props.imported_xml && <th width="15%" className="text-right">Query coverage (%)</th>}
|
403
|
-
<th width="10%" className="text-right">Total score</th>
|
404
|
-
<th width="10%" className="text-right">E value</th>
|
405
|
-
<th width="10%" className="text-right">Identity (%)</th>
|
406
|
-
</tr>
|
407
|
-
</thead>
|
408
|
-
<tbody>
|
409
|
-
{
|
410
|
-
_.map(this.props.query.hits, _.bind(function (hit) {
|
411
|
-
return (
|
412
|
-
<tr key={hit.number}>
|
413
|
-
<td className="text-left">{hit.number + '.'}</td>
|
414
|
-
<td className="nowrap-ellipsis"
|
415
|
-
title={`${hit.id} ${hit.title}`}
|
416
|
-
data-toggle="tooltip" data-placement="left">
|
417
|
-
<a href={'#Query_' + this.props.query.number + '_hit_' + hit.number}
|
418
|
-
className="btn-link">{hit.id} {hit.title}</a>
|
419
|
-
</td>
|
420
|
-
{hasName &&
|
421
|
-
<td className="nowrap-ellipsis" title={hit.sciname}
|
422
|
-
data-toggle="tooltip" data-placement="top">
|
423
|
-
{hit.sciname}
|
424
|
-
</td>
|
425
|
-
}
|
426
|
-
{!this.props.imported_xml && <td className="text-right">{hit.qcovs}</td>}
|
427
|
-
<td className="text-right">{hit.total_score}</td>
|
428
|
-
<td className="text-right">{Utils.inExponential(hit.hsps[0].evalue)}</td>
|
429
|
-
<td className="text-right">{Utils.inPercentage(hit.hsps[0].identity, hit.hsps[0].length)}</td>
|
430
|
-
</tr>
|
431
|
-
);
|
432
|
-
}, this))
|
433
|
-
}
|
434
|
-
</tbody>
|
435
|
-
</table>
|
460
|
+
<div id={'Query_' + this.props.query.number + 'HT_' + this.props.query.number}>
|
461
|
+
{!this.state.collapsed && this.tableJSX()}
|
436
462
|
</div>
|
437
463
|
</div>
|
438
464
|
);
|
data/public/js/report.js
CHANGED
@@ -300,6 +300,7 @@ class Report extends Component {
|
|
300
300
|
<div className="col-md-9">
|
301
301
|
{this.overviewJSX()}
|
302
302
|
{this.circosJSX()}
|
303
|
+
{this.plugins.generateStats()}
|
303
304
|
{this.state.results}
|
304
305
|
</div>
|
305
306
|
</div>
|
@@ -386,7 +387,6 @@ class Report extends Component {
|
|
386
387
|
<Circos
|
387
388
|
queries={this.state.queries}
|
388
389
|
program={this.state.program}
|
389
|
-
collapsed="true"
|
390
390
|
/>
|
391
391
|
) : (
|
392
392
|
<span></span>
|
data/public/js/search.js
CHANGED
@@ -3,6 +3,7 @@ import React, { Component } from "react";
|
|
3
3
|
import { createRoot } from "react-dom/client";
|
4
4
|
import { DnD } from "./dnd";
|
5
5
|
import { Form } from "./form";
|
6
|
+
import { SearchHeaderPlugin } from "search_header_plugin";
|
6
7
|
|
7
8
|
/**
|
8
9
|
* Clear sessionStorage on reload.
|
@@ -19,6 +20,7 @@ class Page extends Component {
|
|
19
20
|
render() {
|
20
21
|
return (
|
21
22
|
<div>
|
23
|
+
<SearchHeaderPlugin />
|
22
24
|
<DnD ref="dnd" />
|
23
25
|
<Form ref="form" />
|
24
26
|
</div>
|
data/public/js/search_button.js
CHANGED
@@ -12,6 +12,7 @@ export class SearchButton extends Component {
|
|
12
12
|
methods: [],
|
13
13
|
hasQuery: false,
|
14
14
|
hasDatabases: false,
|
15
|
+
dropdownVisible: false,
|
15
16
|
};
|
16
17
|
this.inputGroup = this.inputGroup.bind(this);
|
17
18
|
this.submitButton = this.submitButton.bind(this);
|
@@ -28,15 +29,17 @@ export class SearchButton extends Component {
|
|
28
29
|
}
|
29
30
|
|
30
31
|
shouldComponentUpdate(props, state) {
|
31
|
-
return !_.isEqual(state.methods, this.state.methods);
|
32
|
+
return !_.isEqual(state.methods, this.state.methods) || state.dropdownVisible !== this.state.dropdownVisible;
|
32
33
|
}
|
33
34
|
|
34
|
-
componentDidUpdate() {
|
35
|
-
if (this.state.methods
|
36
|
-
this.
|
37
|
-
|
38
|
-
|
39
|
-
|
35
|
+
componentDidUpdate(_prevProps, prevState) {
|
36
|
+
if (!_.isEqual(prevState.methods, this.state.methods)) {
|
37
|
+
if (this.state.methods.length > 0) {
|
38
|
+
this.inputGroup().wiggle();
|
39
|
+
this.props.onAlgoChanged(this.state.methods[0]);
|
40
|
+
} else {
|
41
|
+
this.props.onAlgoChanged('');
|
42
|
+
}
|
40
43
|
}
|
41
44
|
}
|
42
45
|
// Internal helpers. //
|
@@ -110,6 +113,7 @@ export class SearchButton extends Component {
|
|
110
113
|
methods.unshift(method);
|
111
114
|
this.setState({
|
112
115
|
methods: methods,
|
116
|
+
dropdownVisible: false,
|
113
117
|
});
|
114
118
|
}
|
115
119
|
|
@@ -131,64 +135,71 @@ export class SearchButton extends Component {
|
|
131
135
|
});
|
132
136
|
}
|
133
137
|
|
138
|
+
toggleDropdownVisibility = () => {
|
139
|
+
this.setState(prevState => ({
|
140
|
+
dropdownVisible: !prevState.dropdownVisible
|
141
|
+
}));
|
142
|
+
}
|
143
|
+
|
134
144
|
render() {
|
135
145
|
var methods = this.state.methods;
|
136
146
|
var method = methods[0];
|
137
147
|
var multi = methods.length > 1;
|
138
148
|
|
139
149
|
return (
|
140
|
-
<div
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
150
|
+
<div
|
151
|
+
// className={multi ? 'flex' : 'flex'}
|
152
|
+
className="my-4 md:my-2 flex justify-end w-full md:w-auto relative"
|
153
|
+
id="methods"
|
154
|
+
ref={this.inputGroupRef}
|
155
|
+
onMouseOver={this.showTooltip}
|
156
|
+
onMouseOut={this.hideTooltip}
|
157
|
+
>
|
158
|
+
<button
|
159
|
+
type="submit"
|
160
|
+
className="uppercase w-full md:w-auto flex text-xl justify-center py-2 px-16 border border-transparent rounded-md shadow-sm text-white bg-seqblue hover:bg-seqorange focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-seqorange"
|
161
|
+
id="method"
|
162
|
+
ref={this.submitButtonRef}
|
163
|
+
name="method"
|
164
|
+
value={method}
|
165
|
+
disabled={!method}
|
166
|
+
>
|
167
|
+
{this.decorate(method || 'blast')}
|
168
|
+
</button>
|
169
|
+
|
170
|
+
{multi && (
|
171
|
+
<div className="ui--multi-dropdown">
|
172
|
+
<button
|
173
|
+
className="text-xl bg-seqblue rounded-r-md text-white p-2 border border-seqblue hover:bg-seqorange focus:outline-none focus:ring-1 focus:ring-seqorange -ml-8"
|
174
|
+
type="button"
|
175
|
+
onClick={this.toggleDropdownVisibility}
|
149
176
|
>
|
150
|
-
<
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
return (
|
174
|
-
<li
|
175
|
-
key={method}
|
176
|
-
className="text-uppercase"
|
177
|
-
onClick={_.bind(function () {
|
178
|
-
this.changeAlgorithm(method);
|
179
|
-
}, this)}
|
180
|
-
>
|
181
|
-
{method}
|
182
|
-
</li>
|
183
|
-
);
|
184
|
-
}, this)
|
185
|
-
)}
|
186
|
-
</ul>
|
187
|
-
</div>
|
188
|
-
)}
|
177
|
+
<i className="fas fa-caret-down w-6 h-6 fill-current"></i>
|
178
|
+
<span className="sr-only">Other methods</span>
|
179
|
+
</button>
|
180
|
+
|
181
|
+
<div id="dropdown"
|
182
|
+
className={`z-10 my-2 uppercase bg-blue-300 divide-y divide-gray-100 rounded-lg shadow absolute left-0 bottom-12 w-full text-xl text-center ${this.state.dropdownVisible ? '' : 'hidden'}`}>
|
183
|
+
<ul className="text-gray-700" aria-labelledby="dropdownDefaultButton">
|
184
|
+
{_.map(
|
185
|
+
methods.slice(1),
|
186
|
+
_.bind(function (method) {
|
187
|
+
return (
|
188
|
+
<li
|
189
|
+
key={method}
|
190
|
+
onClick={_.bind(function () {
|
191
|
+
this.changeAlgorithm(method);
|
192
|
+
}, this)}
|
193
|
+
>
|
194
|
+
<a href="#" className="block px-4 py-2 hover:bg-blue-400 rounded-lg">{method}</a>
|
195
|
+
</li>
|
196
|
+
);
|
197
|
+
}, this)
|
198
|
+
)}
|
199
|
+
</ul>
|
189
200
|
</div>
|
190
201
|
</div>
|
191
|
-
|
202
|
+
)}
|
192
203
|
</div>
|
193
204
|
);
|
194
205
|
}
|
data/public/js/sidebar.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
/* eslint-disable no-unused-vars */
|
2
2
|
/* eslint-disable no-undef */
|
3
|
-
import { render, screen, fireEvent
|
3
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
4
4
|
import { Databases } from '../databases';
|
5
5
|
import data from './mock_data/databases.json';
|
6
6
|
|
@@ -21,13 +21,13 @@ describe('DATABASES COMPONENT', () => {
|
|
21
21
|
});
|
22
22
|
|
23
23
|
test('clicking select all on a database should select all its children', () => {
|
24
|
-
const { container } = render(<Databases databases={data.database} onDatabaseTypeChanged={() => { }} />);
|
25
|
-
|
24
|
+
const { container } = render(<Databases databases={data.database} onDatabaseTypeChanged={() => { }} onDatabaseSelectionChanged={() => { }} />);
|
25
|
+
|
26
26
|
// select all nucleotide databases
|
27
27
|
const nucleotideSelectAllBtn = screen.getByRole('heading', { name: /nucleotide databases/i }).parentElement.querySelector('button');
|
28
28
|
fireEvent.click(nucleotideSelectAllBtn);
|
29
29
|
const nucleotideCheckboxes = container.querySelector('.databases.nucleotide').querySelectorAll('input[type=checkbox]');
|
30
|
-
|
30
|
+
|
31
31
|
// all nucleotide databases should be checked
|
32
32
|
nucleotideCheckboxes.forEach((checkbox) => {
|
33
33
|
expect(checkbox).toBeChecked();
|
@@ -45,7 +45,7 @@ describe('DATABASES COMPONENT', () => {
|
|
45
45
|
|
46
46
|
test('checking any item of a database type should disable other database type', () => {
|
47
47
|
const mockFunction = jest.fn(() => { });
|
48
|
-
const { container } = render(<Databases databases={data.database} onDatabaseTypeChanged={mockFunction} />);
|
48
|
+
const { container } = render(<Databases databases={data.database} onDatabaseTypeChanged={mockFunction} onDatabaseSelectionChanged={mockFunction}/>);
|
49
49
|
|
50
50
|
//select a proteinn database
|
51
51
|
fireEvent.click(screen.getByRole('checkbox', { name: /2020-11 Swiss-Prot insecta/i }));
|
@@ -11,6 +11,7 @@ import '@testing-library/react/dont-cleanup-after-each';
|
|
11
11
|
export const setMockJSONResult = (result) => {
|
12
12
|
global.$.getJSON = (_, cb) => cb(result);
|
13
13
|
};
|
14
|
+
|
14
15
|
describe('ADVANCED PARAMETERS', () => {
|
15
16
|
const getInputElement = () => screen.getByRole('textbox', { name: '' });
|
16
17
|
test('should not render the link to advanced parameters modal if blast algorithm is unknown', () => {
|
@@ -24,7 +25,7 @@ describe('ADVANCED PARAMETERS', () => {
|
|
24
25
|
setMockJSONResult(data);
|
25
26
|
const {container } =render(<Form onSequenceTypeChanged={() => { }
|
26
27
|
} />);
|
27
|
-
|
28
|
+
|
28
29
|
const inputEl = getInputElement();
|
29
30
|
// populate search and select dbs to determine blast algorithm
|
30
31
|
fireEvent.change(inputEl, { target: { value: AMINO_ACID_SEQUENCE } });
|
@@ -34,3 +35,36 @@ describe('ADVANCED PARAMETERS', () => {
|
|
34
35
|
expect(modalButton).not.toBeNull();
|
35
36
|
});
|
36
37
|
});
|
38
|
+
|
39
|
+
describe('query stats', () => {
|
40
|
+
const getInputElement = () => screen.getByRole('textbox', { name: '' });
|
41
|
+
|
42
|
+
test('should render the query stats modal when clicked', () => {
|
43
|
+
const logSpy = jest.spyOn(global.console, 'log');
|
44
|
+
|
45
|
+
setMockJSONResult(data);
|
46
|
+
render(<Form onSequenceTypeChanged={() => { }} />);
|
47
|
+
|
48
|
+
expect(logSpy).toHaveBeenCalledTimes(1);
|
49
|
+
|
50
|
+
const inputEl = getInputElement();
|
51
|
+
// populate search and select dbs to determine blast algorithm
|
52
|
+
fireEvent.change(inputEl, { target: { value: AMINO_ACID_SEQUENCE } });
|
53
|
+
|
54
|
+
expect(logSpy).toHaveBeenCalledTimes(2);
|
55
|
+
|
56
|
+
const proteinSelectAllBtn = screen.getByRole('heading', { name: /protein databases/i }).parentElement.querySelector('button');
|
57
|
+
fireEvent.click(proteinSelectAllBtn);
|
58
|
+
|
59
|
+
expect(logSpy).toHaveBeenCalledTimes(4);
|
60
|
+
expect(logSpy).toHaveBeenCalledWith(
|
61
|
+
'Query stats:',
|
62
|
+
{
|
63
|
+
residuesInQuerySequence: 385,
|
64
|
+
numberOfDatabasesSelected: 4,
|
65
|
+
residuesInSelectedDbs: 4343318,
|
66
|
+
currentBlastMethod: 'blastp'
|
67
|
+
}
|
68
|
+
);
|
69
|
+
});
|
70
|
+
});
|
@@ -82,10 +82,10 @@
|
|
82
82
|
}
|
83
83
|
],
|
84
84
|
"options": {
|
85
|
-
"blastn": { "default": ["-task blastn", "-evalue 1e-5"] },
|
86
|
-
"blastp": { "default": ["-evalue 1e-5"] },
|
87
|
-
"blastx": { "default": ["-evalue 1e-5"] },
|
88
|
-
"tblastx": { "default": ["-evalue 1e-5"] },
|
89
|
-
"tblastn": { "default": ["-evalue 1e-5"] }
|
85
|
+
"blastn": { "default": { "attributes": ["-task blastn", "-evalue 1e-5"] }},
|
86
|
+
"blastp": { "default": { "attributes": ["-evalue 1e-5"] }},
|
87
|
+
"blastx": { "default": { "attributes": ["-evalue 1e-5"] }},
|
88
|
+
"tblastx": { "default": { "attributes": ["-evalue 1e-5"] }},
|
89
|
+
"tblastn": { "default": { "attributes": ["-evalue 1e-5"] }}
|
90
90
|
}
|
91
91
|
}
|
@@ -44,6 +44,7 @@ describe('REPORT PAGE', () => {
|
|
44
44
|
render(<Report showErrorModal={showErrorModal} />);
|
45
45
|
expect(showErrorModal).toHaveBeenCalledTimes(1);
|
46
46
|
});
|
47
|
+
|
47
48
|
it('it should render the report page correctly if there\'s a response provided', () => {
|
48
49
|
setMockJSONResult({ status: 200, responseJSON: shortResponseJSON });
|
49
50
|
const { container } = render(<Report getCharacterWidth={jest.fn()} />);
|
@@ -119,7 +120,7 @@ describe('REPORT PAGE', () => {
|
|
119
120
|
it('link for downloading alignment of specific number of selected hits should be disabled on initial load', () => {
|
120
121
|
const alignment_download_link = container.querySelector('.download-alignment-of-selected');
|
121
122
|
expect(alignment_download_link.classList.contains('disabled')).toBeTruthy();
|
122
|
-
|
123
|
+
|
123
124
|
});
|
124
125
|
it('should generate a blob url and filename for downloading alignment of specific number of selected hits', () => {
|
125
126
|
const alignment_download_link = container.querySelector('.download-alignment-of-selected');
|
@@ -132,7 +133,7 @@ describe('REPORT PAGE', () => {
|
|
132
133
|
expect(alignment_download_link.download).toEqual(file_name);
|
133
134
|
});
|
134
135
|
});
|
135
|
-
|
136
|
+
|
136
137
|
describe('FASTA DOWNLOAD', () => {
|
137
138
|
let fasta_download_link;
|
138
139
|
beforeEach(() => {
|
@@ -141,7 +142,7 @@ describe('REPORT PAGE', () => {
|
|
141
142
|
it('link for downloading fasta of selected number of hits should be disabled on initial load', () => {
|
142
143
|
expect(fasta_download_link.classList.contains('disabled')).toBeTruthy();
|
143
144
|
});
|
144
|
-
|
145
|
+
|
145
146
|
it('link for downloading fasta of specific number of selected hits should be active after selection', () => {
|
146
147
|
const checkboxes = container.querySelectorAll('.hit-links input[type="checkbox"]');
|
147
148
|
// SELECT 5 CHECKBOXES
|
@@ -1,7 +1,6 @@
|
|
1
1
|
/* eslint-disable no-unused-vars */
|
2
2
|
/* eslint-disable no-undef */
|
3
3
|
import { render, screen, fireEvent } from '@testing-library/react';
|
4
|
-
import { SearchQueryWidget } from '../query';
|
5
4
|
import { Form } from '../form';
|
6
5
|
import { AMINO_ACID_SEQUENCE, NUCLEOTIDE_SEQUENCE, FASTQ_SEQUENCE, FASTA_OF_FASTQ_SEQUENCE } from './mock_data/sequences';
|
7
6
|
import '@testing-library/jest-dom/extend-expect';
|
@@ -18,7 +17,7 @@ describe('SEARCH COMPONENT', () => {
|
|
18
17
|
});
|
19
18
|
|
20
19
|
test('should render the search component textarea', () => {
|
21
|
-
expect(inputEl).
|
20
|
+
expect(inputEl).toBeInTheDocument();
|
22
21
|
});
|
23
22
|
|
24
23
|
test('clear button should only become visible if textarea is not empty', () => {
|
@@ -33,7 +32,7 @@ describe('SEARCH COMPONENT', () => {
|
|
33
32
|
test('should correctly detect the amino-acid sequence type and show notification', () => {
|
34
33
|
// populate search
|
35
34
|
fireEvent.change(inputEl, { target: { value: AMINO_ACID_SEQUENCE } });
|
36
|
-
const activeNotification = container.querySelector('
|
35
|
+
const activeNotification = container.querySelector('[data-role=notification].active');
|
37
36
|
expect(activeNotification.id).toBe('protein-sequence-notification');
|
38
37
|
const alertWrapper = activeNotification.children[0];
|
39
38
|
expect(alertWrapper).toHaveTextContent('Detected: amino-acid sequence(s).');
|
@@ -42,7 +41,7 @@ describe('SEARCH COMPONENT', () => {
|
|
42
41
|
test('should correctly detect the nucleotide sequence type and show notification', () => {
|
43
42
|
// populate search
|
44
43
|
fireEvent.change(inputEl, { target: { value: NUCLEOTIDE_SEQUENCE } });
|
45
|
-
const activeNotification = container.querySelector('
|
44
|
+
const activeNotification = container.querySelector('[data-role=notification].active');
|
46
45
|
const alertWrapper = activeNotification.children[0];
|
47
46
|
expect(activeNotification.id).toBe('nucleotide-sequence-notification');
|
48
47
|
expect(alertWrapper).toHaveTextContent('Detected: nucleotide sequence(s).');
|
@@ -50,7 +49,7 @@ describe('SEARCH COMPONENT', () => {
|
|
50
49
|
|
51
50
|
test('should correctly detect the mixed sequences and show error notification', () => {
|
52
51
|
fireEvent.change(inputEl, { target: { value: `${NUCLEOTIDE_SEQUENCE}${AMINO_ACID_SEQUENCE}` } });
|
53
|
-
const activeNotification = container.querySelector('
|
52
|
+
const activeNotification = container.querySelector('[data-role=notification].active');
|
54
53
|
expect(activeNotification.id).toBe('mixed-sequence-notification');
|
55
54
|
const alertWrapper = activeNotification.children[0];
|
56
55
|
expect(alertWrapper).toHaveTextContent('Error: mixed nucleotide and amino-acid sequences detected.');
|
@@ -58,7 +57,7 @@ describe('SEARCH COMPONENT', () => {
|
|
58
57
|
|
59
58
|
test('should correctly detect FASTQ and convert it to FASTA', () => {
|
60
59
|
fireEvent.change(inputEl, { target: { value: FASTQ_SEQUENCE } });
|
61
|
-
const activeNotification = container.querySelector('
|
60
|
+
const activeNotification = container.querySelector('[data-role=notification].active');
|
62
61
|
const alertWrapper = activeNotification.children[0];
|
63
62
|
expect(activeNotification.id).toBe('fastq-sequence-notification');
|
64
63
|
expect(alertWrapper).toHaveTextContent('Detected FASTQ and automatically converted to FASTA.');
|