New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: _addQuery() #23665
feat: _addQuery() #23665
Changes from 30 commits
0cc8b88
0379058
fc1bdb2
e0ebba5
b9be83b
c4de5d9
518bd72
e33e29f
1876e83
789f71b
3857845
a52b361
2ffa3c5
661c305
0009d3c
443b636
caff894
ef57371
f9da4bb
5d6e930
98042e8
5ce6250
14aa153
1b4ebb8
cd263af
7a88624
dd05b4f
1ec423c
ec4a682
514233a
5a2db61
9263b69
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,12 @@ | ||
const { assertLogLength } = require('../../support/utils') | ||
const { _, $ } = Cypress | ||
const { _ } = Cypress | ||
|
||
describe('src/cy/commands/aliasing', () => { | ||
beforeEach(() => { | ||
cy.visit('/fixtures/dom.html') | ||
}) | ||
|
||
context('#as', () => { | ||
it('is special utility command', () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aliasing is no longer a 'special utility command', it exists in the command queue and runs the same as any other. |
||
cy.wrap('foo').as('f').then(() => { | ||
const cmd = cy.queue.find({ name: 'as' }) | ||
|
||
expect(cmd.get('type')).to.eq('utility') | ||
}) | ||
}) | ||
|
||
it('does not change the subject', () => { | ||
const body = cy.$$('body') | ||
|
||
|
@@ -34,11 +26,13 @@ describe('src/cy/commands/aliasing', () => { | |
cy.get('@body') | ||
}) | ||
|
||
it('stores the resulting subject as the alias', () => { | ||
const $body = cy.$$('body') | ||
|
||
it('stores the resulting subject chain as the alias', () => { | ||
cy.get('body').as('b').then(() => { | ||
expect(cy.state('aliases').b.subject.get(0)).to.eq($body.get(0)) | ||
const { subjectChain } = cy.state('aliases').b | ||
|
||
expect(subjectChain.length).to.eql(2) | ||
expect(subjectChain[0]).to.be.undefined | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is the first subject chain undefined? Shouldn't the only subject chain be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Subject chains always start with a "primitive" value, and then append any number of functions that then chain off this value. Consider these two cases:
In the first case, we make a subject chain like I start subject chains with 'undefined' so that they always have a consistent shape: a primitive value, followed by 0 or more functions that are applied to it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the second part - yes, yes we should assert that the second entry is |
||
expect(subjectChain[1].commandName).to.eq('get') | ||
}) | ||
}) | ||
|
||
|
@@ -50,6 +44,15 @@ describe('src/cy/commands/aliasing', () => { | |
}) | ||
}) | ||
|
||
it('retries previous commands invoked inside custom commands', () => { | ||
Cypress.Commands.add('get2', (selector) => cy.get(selector)) | ||
|
||
cy.get2('body').children('div').as('divs') | ||
cy.visit('/fixtures/dom.html') | ||
|
||
cy.get('@divs') | ||
}) | ||
|
||
it('retries primitives and assertions', () => { | ||
const obj = {} | ||
|
||
|
@@ -87,29 +90,13 @@ describe('src/cy/commands/aliasing', () => { | |
}) | ||
}) | ||
|
||
context('DOM subjects', () => { | ||
it('assigns the remote jquery instance', () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because the alias is now uses subject chains - which invoke the aliased commands directly - we've effectively made "using the correct jquery instance" the responsibility of the executing commands (which all already handle it properly). |
||
const obj = {} | ||
|
||
const jquery = () => { | ||
return obj | ||
} | ||
|
||
cy.state('jQuery', jquery) | ||
|
||
cy.get('input:first').as('input').then(function () { | ||
expect(this.input).to.eq(obj) | ||
}) | ||
}) | ||
|
||
it('retries previous commands invoked inside custom commands', () => { | ||
Cypress.Commands.add('get2', (selector) => cy.get(selector)) | ||
it('retries previous commands invoked inside custom commands', () => { | ||
Cypress.Commands.add('get2', (selector) => cy.get(selector)) | ||
|
||
cy.get2('body').children('div').as('divs') | ||
cy.visit('/fixtures/dom.html') | ||
cy.get2('body').children('div').as('divs') | ||
cy.visit('/fixtures/dom.html') | ||
|
||
cy.get('@divs') | ||
}) | ||
cy.get('@divs') | ||
}) | ||
|
||
context('#assign', () => { | ||
|
@@ -328,9 +315,8 @@ describe('src/cy/commands/aliasing', () => { | |
// sanity check without command overwrite | ||
cy.wrap('alias value').as('myAlias') | ||
.then(() => { | ||
expect(cy.getAlias('@myAlias'), 'alias exists').to.exist | ||
expect(cy.getAlias('@myAlias'), 'alias value') | ||
.to.have.property('subject', 'alias value') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This series of tests asserts on the internal state of Cypress - I've changed the shape of how aliases are stored, so the tests needed updating. This is not a "user-facing" test update. |
||
expect(cy.getAlias('@myAlias')).to.exist | ||
expect(cy.getAlias('@myAlias').subjectChain).to.eql(['alias value']) | ||
}) | ||
.then(() => { | ||
// cy.get returns the alias | ||
|
@@ -349,10 +335,9 @@ describe('src/cy/commands/aliasing', () => { | |
|
||
cy.wrap('alias value').as('myAlias') | ||
.then(() => { | ||
expect(wrapCalled, 'overwrite was called').to.be.true | ||
expect(cy.getAlias('@myAlias'), 'alias exists').to.exist | ||
expect(cy.getAlias('@myAlias'), 'alias value') | ||
.to.have.property('subject', 'alias value') | ||
expect(wrapCalled).to.be.true | ||
expect(cy.getAlias('@myAlias')).to.exist | ||
expect(cy.getAlias('@myAlias').subjectChain).to.eql(['alias value']) | ||
}) | ||
.then(() => { | ||
// verify cy.get works in arrow function | ||
|
@@ -382,9 +367,8 @@ describe('src/cy/commands/aliasing', () => { | |
.then(() => { | ||
expect(wrapCalled, 'overwrite was called').to.be.true | ||
expect(thenCalled, 'then was called').to.be.true | ||
expect(cy.getAlias('@myAlias'), 'alias exists').to.exist | ||
expect(cy.getAlias('@myAlias'), 'alias value') | ||
.to.have.property('subject', 'alias value') | ||
expect(cy.getAlias('@myAlias')).to.exist | ||
expect(cy.getAlias('@myAlias').subjectChain).to.eql(['alias value']) | ||
}) | ||
.then(() => { | ||
// verify cy.get works in arrow function | ||
|
@@ -400,9 +384,9 @@ describe('src/cy/commands/aliasing', () => { | |
// sanity test before the next one | ||
cy.wrap(1).as('myAlias') | ||
cy.wrap(2).then(function (subj) { | ||
expect(subj, 'subject').to.equal(2) | ||
expect(this, 'this is defined').to.not.be.undefined | ||
expect(this.myAlias, 'this has the alias as a property').to.eq(1) | ||
expect(subj).to.equal(2) | ||
expect(this).to.not.be.undefined | ||
expect(this.myAlias).to.eq(1) | ||
}) | ||
}) | ||
|
||
|
@@ -414,8 +398,8 @@ describe('src/cy/commands/aliasing', () => { | |
|
||
cy.wrap(1).as('myAlias') | ||
cy.wrap(2).then(function (subj) { | ||
expect(subj, 'subject').to.equal(2) | ||
expect(this, 'this is defined').to.not.be.undefined | ||
expect(subj).to.equal(2) | ||
expect(this).to.not.be.undefined | ||
expect(this.myAlias).to.eq(1) | ||
}) | ||
}) | ||
|
@@ -428,166 +412,61 @@ describe('src/cy/commands/aliasing', () => { | |
|
||
cy.wrap(1).as('myAlias') | ||
cy.wrap(2).then(function (subj) { | ||
expect(subj, 'subject').to.equal(2) | ||
expect(this, 'this is defined').to.not.be.undefined | ||
expect(subj).to.equal(2) | ||
expect(this).to.not.be.undefined | ||
expect(this.myAlias).to.eq(1) | ||
}) | ||
}) | ||
}) | ||
}) | ||
|
||
context('#replayCommandsFrom', () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aliases no longer run through the command queue, so this set of tests doesn't make much sense anymore. They instead use subject chains to accomplish the same goal. "A subject chain" is essentially the minimal set of information needed to "replay commands that led of to a current subject" - except now it's used everywhere in Cypress, so all commands can benefit from it, rather than as a special case in aliasing. |
||
describe('subject in document', () => { | ||
it('returns if subject is still in the document', () => { | ||
cy.get('#list').as('list').then(() => { | ||
const currentLength = cy.queue.length | ||
|
||
cy.get('@list').then(() => { | ||
// should only add the .get() and the .then() | ||
expect(cy.queue.length).to.eq(currentLength + 2) | ||
}) | ||
context('#replaying subjects', () => { | ||
it('returns if subject is still in the document', () => { | ||
cy.get('#list').as('list').then((firstList) => { | ||
cy.get('@list').then((secondList) => { | ||
expect(firstList).to.eql(secondList) | ||
}) | ||
}) | ||
}) | ||
|
||
describe('subject not in document', () => { | ||
it('inserts into the queue', () => { | ||
const existingNames = cy.queue.names() | ||
|
||
cy | ||
.get('#list li').eq(0).as('firstLi').then(($li) => { | ||
return $li.remove() | ||
}) | ||
.get('@firstLi').then(() => { | ||
expect(cy.queue.names()).to.deep.eq( | ||
existingNames.concat( | ||
['get', 'eq', 'as', 'then', 'get', 'get', 'eq', 'then'], | ||
), | ||
) | ||
}) | ||
}) | ||
|
||
it('replays from last root to current', () => { | ||
const first = cy.$$('#list li').eq(0) | ||
const second = cy.$$('#list li').eq(1) | ||
|
||
cy | ||
.get('#list li').eq(0).as('firstLi').then(($li) => { | ||
expect($li.get(0)).to.eq(first.get(0)) | ||
|
||
return $li.remove() | ||
}) | ||
.get('@firstLi').then(($li) => { | ||
expect($li.get(0)).to.eq(second.get(0)) | ||
}) | ||
}) | ||
|
||
it('replays up until first root command', () => { | ||
const existingNames = cy.queue.names() | ||
|
||
cy | ||
.get('body').noop({}) | ||
.get('#list li').eq(0).as('firstLi').then(($li) => { | ||
return $li.remove() | ||
}) | ||
.get('@firstLi').then(() => { | ||
expect(cy.queue.names()).to.deep.eq( | ||
existingNames.concat( | ||
['get', 'noop', 'get', 'eq', 'as', 'then', 'get', 'get', 'eq', 'then'], | ||
), | ||
) | ||
}) | ||
}) | ||
|
||
it('resets the chainerId allow subjects to be carried on', () => { | ||
cy.get('#dom').find('#button').as('button').then(($button) => { | ||
$button.remove() | ||
|
||
cy.$$('#dom').append($('<button />', { id: 'button' })) | ||
|
||
return null | ||
}) | ||
it('requeries when reading alias', () => { | ||
cy | ||
.get('#list li') | ||
.as('items').then((firstItems) => { | ||
cy.$$('#list').append('<li class="foobar">123456789</li>') | ||
|
||
// when cy is a separate chainer there *was* a bug | ||
// that cause the subject to null because of different | ||
// chainer id's | ||
cy.get('@button').then(($button) => { | ||
expect($button).to.have.id('button') | ||
cy.get('@items').then((secondItems) => { | ||
expect(firstItems).to.have.length(3) | ||
expect(secondItems).to.have.length(4) | ||
}) | ||
}) | ||
}) | ||
|
||
it('skips commands which did not change, and starts at the first valid subject or parent command', () => { | ||
const existingNames = cy.queue.names() | ||
|
||
cy.$$('#list li').click(function () { | ||
const ul = $(this).parent() | ||
const lis = ul.children().clone() | ||
|
||
// this simulates a re-render | ||
ul.children().remove() | ||
ul.append(lis) | ||
|
||
return lis.first().remove() | ||
}) | ||
it('requeries when subject is not in the DOM', () => { | ||
cy | ||
.get('#list li') | ||
.as('items').then((firstItems) => { | ||
firstItems.remove() | ||
setTimeout(() => { | ||
cy.$$('#list').append('<li class="foobar">123456789</li>') | ||
}, 50) | ||
|
||
cy | ||
.get('#list li') | ||
.then(($lis) => { | ||
return $lis | ||
}) | ||
.as('items') | ||
.first() | ||
.click() | ||
.as('firstItem') | ||
.then(() => { | ||
expect(cy.queue.names()).to.deep.eq( | ||
existingNames.concat( | ||
['get', 'then', 'as', 'first', 'click', 'as', 'then', 'get', 'should', 'then', 'get', 'should', 'then'], | ||
), | ||
) | ||
}) | ||
.get('@items') | ||
.should('have.length', 2) | ||
.then(() => { | ||
expect(cy.queue.names()).to.deep.eq( | ||
existingNames.concat( | ||
['get', 'then', 'as', 'first', 'click', 'as', 'then', 'get', 'get', 'should', 'then', 'get', 'should', 'then'], | ||
), | ||
) | ||
}) | ||
.get('@firstItem') | ||
.should('contain', 'li 1') | ||
.then(() => { | ||
expect(cy.queue.names()).to.deep.eq( | ||
existingNames.concat( | ||
['get', 'then', 'as', 'first', 'click', 'as', 'then', 'get', 'get', 'should', 'then', 'get', 'get', 'first', 'should', 'then'], | ||
), | ||
) | ||
cy.get('@items').then((secondItems) => { | ||
expect(secondItems).to.have.length(1) | ||
}) | ||
}) | ||
}) | ||
|
||
it('inserts assertions', (done) => { | ||
const existingNames = cy.queue.names() | ||
|
||
cy | ||
.get('#checkboxes input') | ||
.eq(0) | ||
.should('be.checked', 'cockatoo') | ||
.as('firstItem') | ||
.then(($input) => { | ||
return $input.remove() | ||
}) | ||
.get('@firstItem') | ||
.then(() => { | ||
expect(cy.queue.names()).to.deep.eq( | ||
existingNames.concat( | ||
['get', 'eq', 'should', 'as', 'then', 'get', 'get', 'eq', 'should', 'then'], | ||
), | ||
) | ||
|
||
done() | ||
}) | ||
}) | ||
it('only retries up to last command', () => { | ||
cy | ||
.get('#list li') | ||
.then((items) => items.length) | ||
.as('itemCount') | ||
.then(() => cy.$$('#list li').remove()) | ||
|
||
// Even though the list items have been removed from the DOM, 'then' can't be retried | ||
// so we just have the primitive value "3" as our subject. | ||
cy.get('@itemCount').should('eq', 3) | ||
}) | ||
}) | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already have tests around the user-facing behavior - this test asserting on the internal state that leads to this behavior is unnecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@BlueWinds Can you link that test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The rest of this file is asserting on the output of the alias (eg, that we can store and retrieve aliases). It's not a specific test, but the whole file. 🤷♀️