Skip to content

Commit

Permalink
Fix attribute search with spaces, add more tests (GMOD#293)
Browse files Browse the repository at this point in the history
* More tests and quote query for search

* Fix sporadic error while testing

Usually the assembly selector box is empty, but sometimes it comes with
a preset assembly which caused the command to fail. Fix by accounting
for the possibility that the box is not empty.
  • Loading branch information
dariober authored Oct 25, 2023
1 parent cfbaa39 commit 2f3e34e
Show file tree
Hide file tree
Showing 10 changed files with 316 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ export class FeaturesService {
.find({ assembly: assemblyIds })
.exec()
return this.featureModel
.find({ $text: { $search: term }, refSeq: refSeqs })
.find({ $text: { $search: `"${term}"` }, refSeq: refSeqs })
.populate('refSeq')
.exec()
}
Expand Down
15 changes: 14 additions & 1 deletion packages/jbrowse-plugin-apollo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,21 @@ accordingly in `commands.ts`
`package.json`. Typically (again outside the dev container/vscode):

```
yarn --cwd packages/jbrowse-plugin-apollo run cypress open
yarn --cwd packages/jbrowse-plugin-apollo run cypress open --config baseUrl=http://localhost:3000
```

- For end-to-end testing, click "E2E Testing" `->` Chrome `->`
`Start E2E Testing`. Click on one of the available test scripts.

To run tests locally in headless mode:

```
yarn --cwd packages/jbrowse-plugin-apollo run cypress run \
--browser chrome \
--config '{"baseUrl": "http://localhost:3000",
"screenshotOnRunFailure": true,
"video": true,
"videoCompression": false,
"retries": {"runMode": 0}}' \
--spec cypress/e2e/editFeature.cy.ts
```
26 changes: 21 additions & 5 deletions packages/jbrowse-plugin-apollo/cypress/e2e/addAssembly.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ describe('Add Assembly', () => {
})

it('Can add assembly from fasta', () => {
cy.contains('Apollo').click()
cy.contains('button[data-testid="dropDownMenuButton"]', 'Apollo').click()
cy.contains('Add Assembly').click()
cy.get('input[type="TextField"]').type('volvox_deleteme')
cy.get('input[type="TextField"]').type('volvox.fa')
cy.get('input[value="text/x-fasta"]').check()
cy.get('input[type="file"]').selectFile('test_data/volvox.fa')

Expand All @@ -18,9 +18,9 @@ describe('Add Assembly', () => {
})

it('Can add assembly from gff3 with fasta', () => {
cy.contains('Apollo').click()
cy.contains('button[data-testid="dropDownMenuButton"]', 'Apollo').click()
cy.contains('Add Assembly').click()
cy.get('input[type="TextField"]').type('volvox_deleteme')
cy.get('input[type="TextField"]').type('volvox.fasta.gff3')
cy.get('input[value="text/x-gff3"]').check()
cy.get('input[type="file"]').selectFile('test_data/volvox.fasta.gff3')

Expand All @@ -30,8 +30,24 @@ describe('Add Assembly', () => {
cy.wait('@changes').its('response.statusCode').should('match', /2../)
})

it('Can import and add features', () => {
cy.addAssemblyFromGff('volvox.fasta', 'test_data/volvox.fasta.gff3')
cy.importFeatures('test_data/onegene.fasta.gff3', 'volvox.fasta', false)
cy.selectAssemblyToView('volvox.fasta')
cy.searchFeatures('gx1', 1)
cy.searchFeatures('EDEN', 1)
})

it('Can import and replace features', () => {
cy.addAssemblyFromGff('volvox.fasta', 'test_data/volvox.fasta.gff3')
cy.importFeatures('test_data/onegene.fasta.gff3', 'volvox.fasta', true)
cy.selectAssemblyToView('volvox.fasta')
cy.searchFeatures('gx1', 1)
cy.searchFeatures('EDEN', 0)
})

it('FIXME: Can add assembly from 2bit', () => {
cy.contains('Apollo').click()
cy.contains('button[data-testid="dropDownMenuButton"]', 'Apollo').click()
cy.contains('Add Assembly').click()
cy.get('input[type="TextField"]').type('volvox_deleteme')
cy.get('input[value="text/x-fasta"]').check()
Expand Down
104 changes: 97 additions & 7 deletions packages/jbrowse-plugin-apollo/cypress/e2e/editFeature.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,22 +61,112 @@ describe('Different ways of editing features', () => {
})
})

it('Can add gene ontology attribute', () => {
cy.addAssemblyFromGff('onegene.fasta.gff3', 'test_data/onegene.fasta.gff3')
cy.selectAssemblyToView('onegene.fasta.gff3')
cy.searchFeatures('gx1', 1)
cy.contains('td', 'CDS1').rightclick()
cy.contains('Edit attributes').click()
cy.contains('Feature attributes')
.parent()
.within(() => {
cy.contains('button', 'Add new').click()
cy.get('input[value="Gene Ontology"]').click()
cy.contains('button', /^Add$/).click()
cy.contains('Gene Ontology')
.parent()
.parent()
.parent()
.within(() => {
cy.get('input').type('quiescence')
})
})
// This seems to take ~6 minutes in headless mode!
cy.contains('li', 'GO:0044838', { timeout: 600_000 }).click()
cy.contains('button', 'Submit changes').click()
cy.contains('td', 'Gene Ontology=GO:0044838')
})

it('Can delete feature', () => {
cy.addAssemblyFromGff('onegene.fasta.gff3', 'test_data/onegene.fasta.gff3')
cy.selectAssemblyToView('onegene.fasta.gff3')
cy.searchFeatures('gx1', 1)
cy.contains('td', '=CDS1')
cy.contains('td', '=tx1').rightclick()
cy.contains('Delete feature').click()
cy.contains('Are you sure you want to delete the selected feature?')
.parent()
.parent()
.within(() => {
cy.contains('button', /^yes$/, { matchCase: false }).click()
})
cy.contains('td', '=gx1')
cy.contains('td', '=tx1').should('not.exist')
cy.contains('td', '=CDS1').should('not.exist')
})

it('Suggest only valid SO terms from dropdown', () => {
cy.addAssemblyFromGff('onegene.fasta.gff3', 'test_data/onegene.fasta.gff3')
cy.selectAssemblyToView('onegene.fasta.gff3')
cy.searchFeatures('gx1')
cy.searchFeatures('gx1', 1)
// In headless mode it seems to take a long time for menus to be populated
cy.get('input[type="text"][value="exon"]', { timeout: 60_000 })
.eq(0)
.click({ timeout: 60_000, force: true })
cy.contains('li', /^CDS$/, { timeout: 60_000, matchCase: false }).should(
'exist',
)
cy.get('input[type="text"][value="CDS"]', { timeout: 60_000 }).click({
timeout: 60_000,
force: true,
})
cy.contains('li', /^start_codon$/, {
timeout: 60_000,
matchCase: false,
}).should('exist')
cy.contains('li', /^gene$/, { timeout: 60_000, matchCase: false }).should(
'not.exist',
)
})

it('Can add child feature via table editor', () => {
cy.addAssemblyFromGff('onegene.fasta.gff3', 'test_data/onegene.fasta.gff3')
cy.selectAssemblyToView('onegene.fasta.gff3')
cy.searchFeatures('gx1', 1)
// In headless mode it seems to take a long time for menus to be populated
cy.get('input[type="text"][value="CDS"]', { timeout: 60_000 }).rightclick({
timeout: 60_000,
force: true,
})
cy.contains('Add child feature').click()
cy.contains('Add new child feature')
.parent()
.within(() => {
cy.get('form').within(() => {
cy.contains('Start')
.parent()
.within(() => {
cy.get('input').type('{selectall}{backspace}1')
})
cy.contains('End')
.parent()
.within(() => {
cy.get('input').type('{selectall}{backspace}3')
})
cy.contains('Type')
.parent()
.within(() => {
cy.get('input').click({ timeout: 60_000 })
})
})
})
cy.contains('li', /^start_codon$/, {
timeout: 60_000,
matchCase: false,
}).click()
cy.get('button').contains('Submit').click()
cy.reload() // Ideally, you shouldn't need to reload to see the change?
cy.get('tbody', { timeout: 60_000 }).within(() => {
cy.get('input[value="start_codon"]').should('have.length', 1)
cy.get('input[value="1"]').should('have.length', 4)
cy.get('input[value="3"]').should('have.length', 1)
})
})

it.skip('Can select region on rubber-band and zoom into it', () => {
const assemblyName = 'space.gff3'
cy.addAssemblyFromGff(assemblyName, `test_data/${assemblyName}`)
Expand Down
88 changes: 70 additions & 18 deletions packages/jbrowse-plugin-apollo/cypress/e2e/searchFeatures.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,55 @@ describe('Search features', () => {
cy.loginAsGuest()
})

it('One hit, no children', () => {
it('FIXME: Use of quotes', () => {
cy.addAssemblyFromGff('space.gff3', 'test_data/space.gff3')
cy.selectAssemblyToView('space.gff3')
cy.searchFeatures('"agt A"', 4) // Should return 2 matches
})

it('FIXME: Inconsistent substring matching', () => {
cy.addAssemblyFromGff('space.gff3', 'test_data/space.gff3')
cy.selectAssemblyToView('space.gff3')

cy.searchFeatures('transmem', 0)
cy.searchFeatures('transmembrane', 1)
cy.currentLocationEquals('ctgA', 9520, 9900, 10)
cy.searchFeatures('7-transmembrane', 1)
cy.searchFeatures('someKeyWord', 1)
cy.searchFeatures('mRNA', 1)
cy.searchFeatures('UTRs', 1)
cy.searchFeatures('UTR', 1) // Why one match? Should be zero
cy.searchFeatures('with', 0) // Why zero matches?
cy.searchFeatures('both', 0) // Why zero matches?
cy.searchFeatures('and', 0) // Why zero matches?
})

it('One hit with no children', () => {
cy.addAssemblyFromGff('volvox.fasta.gff3', 'test_data/volvox.fasta.gff3')
cy.selectAssemblyToView('volvox.fasta.gff3')
cy.searchFeatures('Match6', 1)
cy.currentLocationEquals('ctgA', 8000, 9000, 10)
})

it('Match is not case sensitive', () => {
cy.addAssemblyFromGff('volvox.fasta.gff3', 'test_data/volvox.fasta.gff3')
cy.selectAssemblyToView('volvox.fasta.gff3')
cy.searchFeatures('Match6')
cy.searchFeatures('match6', 1)
cy.currentLocationEquals('ctgA', 8000, 9000, 10)
})

it('One matching Parent and multiple matching children', () => {
it('Decode URL escapes', () => {
cy.addAssemblyFromGff('volvox.fasta.gff3', 'test_data/volvox.fasta.gff3')
cy.selectAssemblyToView('volvox.fasta.gff3')
cy.searchFeatures('EDEN')
cy.searchFeatures('Some%2CNote', 0)
cy.searchFeatures('Some,Note', 1)
cy.currentLocationEquals('ctgA', 1000, 2000, 10)
})

it('One matching parent and multiple matching children', () => {
cy.addAssemblyFromGff('volvox.fasta.gff3', 'test_data/volvox.fasta.gff3')
cy.selectAssemblyToView('volvox.fasta.gff3')
cy.searchFeatures('EDEN', 1)
cy.currentLocationEquals('ctgA', 1050, 9000, 10)
})

Expand All @@ -23,29 +61,43 @@ describe('Search features', () => {
cy.addAssemblyFromGff('volvox2.fasta.gff3', 'test_data/volvox2.fasta.gff3')

cy.selectAssemblyToView('volvox2.fasta.gff3')
cy.searchFeatures('SpamGene')
cy.searchFeatures('SpamGene', 1)
cy.currentLocationEquals('ctgA', 100, 200, 10)

cy.visit('/?config=http://localhost:9000/jbrowse_config.json')
cy.selectAssemblyToView('volvox.fasta.gff3')
cy.searchFeatures('SpamGene')
cy.contains('Error: Unknown reference sequence "SpamGene"')
cy.searchFeatures('SpamGene', 0)
})

it('Can use quotes to handle spaces', () => {
cy.addAssemblyFromGff('space.gff3', 'test_data/space.gff3')
cy.selectAssemblyToView('space.gff3')
cy.searchFeatures('"agt A"')
cy.currentLocationEquals('ctgA', 7500, 8000, 10)
it('Select from multiple hits', () => {
cy.addAssemblyFromGff('volvox.fasta.gff3', 'test_data/volvox.fasta.gff3')
cy.selectAssemblyToView('volvox.fasta.gff3')
cy.searchFeatures('hga', 3)
cy.contains('td', 'ctgA:1000..2000')
.parent()
.within(() => {
cy.contains('button', /^Go$/, { matchCase: false }).click()
cy.wait('@search hga')
})
cy.currentLocationEquals('ctgA', 1000, 2000, 10)

cy.searchFeatures('hgb', 2)
})

it('FIXME: Can handle regex and space in attribute values', () => {
it('Can handle regex and space in attribute values', () => {
cy.addAssemblyFromGff('space.gff3', 'test_data/space.gff3')
cy.selectAssemblyToView('space.gff3')
cy.get('input[placeholder="Search for location"]').type('Ma*.1{enter}')
cy.contains('Search results')
// It should instead either:
// * Return only one hit for Match1, or
// * Return 'Error: Unknown reference sequence'
cy.searchFeatures('Ma.*1', 0)

cy.searchFeatures('agt 2', 1)
cy.currentLocationEquals('ctgA', 1150, 7200, 10)

cy.searchFeatures('spam"foo"eggs', 1)
cy.currentLocationEquals('ctgA', 1150, 7200, 10)

cy.searchFeatures('agt B', 1)
cy.currentLocationEquals('ctgA', 8000, 9000, 10)

cy.searchFeatures('agt 1', 2)
})
})
Loading

0 comments on commit 2f3e34e

Please sign in to comment.