From 9562053f173b24c5a541db0e036634183679f041 Mon Sep 17 00:00:00 2001 From: Hung Tran Date: Sat, 19 Oct 2019 16:33:42 +0700 Subject: [PATCH 1/4] Added cy.queryDocuments to query documents from firestore --- cypress/support/commands.ts | 20 ++++++++++++++++++++ cypress/support/firestore.ts | 15 +++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 6afc9e2c79..2e2b4ba37c 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -52,6 +52,13 @@ declare global { value: string, ): Promise + queryDocuments( + collectionName: string, + fieldPath: string, + opStr: any, + value: string, + ): Chainable + step(message: string) uploadFiles(filePath: string | string[]) @@ -104,6 +111,19 @@ const attachCustomCommands = (Cypress, fb: typeof firebase) => { return fb.auth().signOut() }) + Cypress.Commands.add( + 'queryDocuments', + (collectionName: string, fieldPath: string, opStr: any, value: string) => { + Cypress.log({ + displayName: 'queryDocuments', + consoleProps: () => { + return { collectionName, fieldPath, opStr, value } + }, + }) + return firestore.queryDocuments(collectionName, fieldPath, opStr, value) + }, + ) + Cypress.Commands.add( 'deleteDocuments', (collectionName: string, fieldPath: string, opStr: any, value: string) => { diff --git a/cypress/support/firestore.ts b/cypress/support/firestore.ts index 88f8f97a22..c6f776601b 100644 --- a/cypress/support/firestore.ts +++ b/cypress/support/firestore.ts @@ -10,6 +10,21 @@ export class Firestore { constructor(firestore: firebase.firestore.Firestore) { this.db = firestore } + queryDocuments = (collectionName: string, fieldPath: string, opStr: any, value: string): Promise | Promise => { + return this.db.collection(collectionName).where(fieldPath, opStr, value).get() + .then(snapshot => { + const result: any[] = [] + if (snapshot.empty) { + return result + } + snapshot.forEach(doc => result.push(doc.data())) + if (result.length === 1) { + return result[0] + } + return result + } + ) + }; deleteDocuments = (collectionName: string, fieldPath: string, opStr: any, value: string) => { const query = this.db.collection(collectionName).where(fieldPath, opStr, value).limit(Firestore.MAX_BATCH_SIZE); From fbdc3e7809abc6cfcdac391ba9f75bae1821b777 Mon Sep 17 00:00:00 2001 From: Hung Tran Date: Sat, 19 Oct 2019 16:34:39 +0700 Subject: [PATCH 2/4] Added a custom chai assertion to verify a How-to --- cypress/support/custom-assertions.ts | 39 ++++++++++++++++++++++++++++ cypress/support/index.ts | 2 +- package.json | 1 + yarn.lock | 5 ++++ 4 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 cypress/support/custom-assertions.ts diff --git a/cypress/support/custom-assertions.ts b/cypress/support/custom-assertions.ts new file mode 100644 index 0000000000..4c6cbfa90e --- /dev/null +++ b/cypress/support/custom-assertions.ts @@ -0,0 +1,39 @@ +import { IHowto, IHowtoStep } from '../../src/models/howto.models' +import chaiSubset from 'chai-subset' + +declare global { + namespace Chai { + // tslint:disable-next-line:interface-name + interface Assertion { + containSubset(expect: any): any; + eqHowtoStep(expect: any, index: number) + } + } +} + +const eqHowto =(chaiObj, utils) => { + function shallowCompare(this: any, expected: any) { + const subject: IHowto = this._obj + const { _createdBy, _deleted, caption, description, difficulty_level, slug, time, title , tags} = expected + expect(subject, 'Basic info').to.containSubset({ _createdBy, _deleted, caption, description, difficulty_level, slug, time, title , tags}) + expect(subject.cover_image, 'Cover images').to.containSubset(expected.cover_image) + + expected.steps.forEach((step, index) => { + expect(subject.steps[index], `Have step ${index}`).to.eqHowtoStep(step, index) + }) + } + chaiObj.Assertion.addMethod('eqHowto', shallowCompare) +} +const eqHowtoStep = (chaiObj, utils) => { + function shallowCompare(this: any, expected: any, index: number) { + const subject: IHowtoStep = this._obj + const {_animationKey, caption, text, title} = expected + expect(subject, `Step ${index} with info`).to.containSubset({_animationKey, caption, text, title}) + expect(subject.images, `Step ${index} with images`).to.containSubset(expected.images) + } + + chaiObj.Assertion.addMethod('eqHowtoStep', shallowCompare) +} +chai.use(eqHowto); +chai.use(eqHowtoStep); +chai.use(chaiSubset) \ No newline at end of file diff --git a/cypress/support/index.ts b/cypress/support/index.ts index ccb2b9ed56..d612e132ca 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -16,6 +16,6 @@ import './commands' // Import commands.js using ES2015 syntax: import './commands' - +import './custom-assertions' // Alternatively you can use CommonJS syntax: // require('./commands') diff --git a/package.json b/package.json index 0fc4fd6828..3142c82c16 100644 --- a/package.json +++ b/package.json @@ -161,6 +161,7 @@ "@types/storybook__addon-info": "^4.1.0", "@types/storybook__addon-knobs": "^4.0.1", "@types/storybook__react": "^4.0.1", + "chai-subset": "^1.6.0", "cross-env": "^6.0.3", "cypress": "^3.4.1", "cypress-file-upload": "^3.3.4", diff --git a/yarn.lock b/yarn.lock index 128a738c5e..6ff7f41649 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5544,6 +5544,11 @@ center-align@^0.1.1: align-text "^0.1.3" lazy-cache "^1.0.3" +chai-subset@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/chai-subset/-/chai-subset-1.6.0.tgz#a5d0ca14e329a79596ed70058b6646bd6988cfe9" + integrity sha1-pdDKFOMpp5WW7XAFi2ZGvWmIz+k= + chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" From 4a2c3321849788b6436afa6f2cc27d50c630a5e1 Mon Sep 17 00:00:00 2001 From: Hung Tran Date: Sat, 19 Oct 2019 16:35:26 +0700 Subject: [PATCH 3/4] Verify if a how-to is created correctly with the new custom assertion --- cypress/integration/howto/write.spec.ts | 91 ++++++++++++++++++++----- 1 file changed, 73 insertions(+), 18 deletions(-) diff --git a/cypress/integration/howto/write.spec.ts b/cypress/integration/howto/write.spec.ts index 6e1cf40bfe..c4c0b09303 100644 --- a/cypress/integration/howto/write.spec.ts +++ b/cypress/integration/howto/write.spec.ts @@ -1,12 +1,14 @@ describe('[How To]', () => { + type Duration = '<1 week'| '1-2 weeks' | '3-4 weeks' + type Difficulty = 'Easy' | 'Medium' | 'Hard' | 'Very Hard' - const selectTimeDuration = (duration: '<1 week'| '1-2 weeks' | '3-4 weeks') => { + const selectTimeDuration = (duration: Duration) => { cy.get('[data-cy=time-select]').click() cy.get('.data-cy__menu') .contains(duration) .click() } - const selectDifficultLevel = (difficultLevel: 'Easy' | 'Medium' | 'Hard' | 'Very Hard') => { + const selectDifficultLevel = (difficultLevel: Difficulty) => { cy.get('[data-cy=difficulty-select]').click() cy.get('.data-cy__menu') .contains(difficultLevel) @@ -20,7 +22,7 @@ describe('[How To]', () => { .click() } - const fillStep = (stepNumber: number) => { + const fillStep = (stepNumber: number, title: string, description: string, caption: string, images: string[]) => { const stepIndex = stepNumber - 1 cy.step(`Filling step ${stepNumber}`) cy.get(`[data-cy=step_${stepIndex}]:visible`) @@ -35,10 +37,7 @@ describe('[How To]', () => { cy.wrap($deleteButton).click() }) } - cy.get(':file').uploadFiles([ - 'images/howto-step-pic1.jpg', - 'images/howto-step-pic2.jpg', - ]) + cy.get(':file').uploadFiles(images) }) } @@ -50,8 +49,58 @@ describe('[How To]', () => { } describe('[Create a how-to]', () => { + const expected = { + '_createdBy': 'howto_creator', + '_deleted': false, + 'caption': 'Intro caption goes here ...', + 'description': 'After creating, the how-to will be deleted', + 'difficulty_level': 'Medium', + 'time': '1-2 weeks', + 'title': 'Create a how-to test', + 'slug': 'create-a-howto-test', + 'files': [], + 'tags': { + 'jUtS7pVbv7DXoQyV13RR': true + }, + 'cover_image': { + 'contentType': 'image/jpeg', + 'name': 'howto-intro.jpg', + 'size': 19897, + 'type': 'image/jpeg', + }, + 'steps': [ + { + '_animationKey': 'unique1', + 'caption': 'What a step caption', + 'images': [ + { + 'contentType': 'image/jpeg', + 'name': 'howto-step-pic1.jpg', + 'size': 19410, + 'type': 'image/jpeg', + }, + { + 'contentType': 'image/jpeg', + 'name': 'howto-step-pic2.jpg', + 'size': 20009, + 'type': 'image/jpeg', + } + ], + 'text': 'Description for step 1', + 'title': 'Step 1 is easy' + }, + { + '_animationKey': 'unique2', + 'caption': 'What a step caption', + 'images': [], + 'text': 'Description for step 2', + 'title': 'Step 2 is easy' + } + ] + } + it('[By Authenticated]', () => { - cy.deleteDocuments('v2_howtos', 'title', '==', 'Create a how-to test') + cy.deleteDocuments('v2_howtos', 'title', '==', expected.title) cy.login('howto_creator@test.com', 'test1234') cy.step('Access the create-how-to page with its url') cy.visit('/how-to/create') @@ -69,20 +118,22 @@ describe('[How To]', () => { .clear() .type('Create a how-to test') selectTag('howto_testing') - selectTimeDuration('1-2 weeks') - selectDifficultLevel('Medium') + selectTimeDuration(expected.time as Duration) + selectDifficultLevel(expected.difficulty_level as Difficulty) - cy.get('[data-cy=intro-description]').type( - 'After creating, the how-to will be deleted', - ) - cy.get('[data-cy=intro-caption]').type('Intro caption goes here ...') + cy.get('[data-cy=intro-description]').type(expected.description) + cy.get('[data-cy=intro-caption]').type(expected.caption) cy.step('Upload a cover for the intro') cy.get('[data-cy=intro-cover]') .find(':file') .uploadFiles('images/howto-intro.jpg') - fillStep(1) - fillStep(2) + expected.steps.forEach((step, index) => { + fillStep(index + 1, step.title, step.text, step.caption, [ + 'images/howto-step-pic1.jpg', + 'images/howto-step-pic2.jpg', + ]) + }) deleteStep(3) cy.get('[data-cy=header]').click({ force: true}) @@ -93,6 +144,9 @@ describe('[How To]', () => { .click() .url() .should('include', `/how-to/create-a-howto-test`) + + cy.step('Howto was created correctly') + cy.queryDocuments('v2_howtos', 'title', '==', expected.title).should('eqHowto', expected) }) it('[By Anonymous]', () => { @@ -145,8 +199,9 @@ describe('[How To]', () => { deleteStep(5) deleteStep(4) deleteStep(2) - fillStep(1) - fillStep(2) + // TODO + // fillStep(1) + // fillStep(2) cy.get('[data-cy=submit]').click() From b44f3fded781a07224df16d97fd6ef346db578ec Mon Sep 17 00:00:00 2001 From: Hung Tran Date: Sat, 19 Oct 2019 16:48:31 +0700 Subject: [PATCH 4/4] Verify if a how-to is updated correctly --- cypress/integration/howto/write.spec.ts | 78 ++++++++++++++++++++++--- cypress/support/custom-assertions.ts | 8 +-- 2 files changed, 74 insertions(+), 12 deletions(-) diff --git a/cypress/integration/howto/write.spec.ts b/cypress/integration/howto/write.spec.ts index c4c0b09303..42fd512a22 100644 --- a/cypress/integration/howto/write.spec.ts +++ b/cypress/integration/howto/write.spec.ts @@ -159,6 +159,63 @@ describe('[How To]', () => { describe('[Edit a how-to]', () => { const editHowtoUrl = '/how-to/set-up-devsite-to-help-coding/edit' + const expected = { + '_createdBy': 'howto_editor', + '_deleted': false, + 'caption': 'Caption edited!', + 'description': 'After editing, all changes are reverted', + 'difficulty_level': 'Hard', + 'files': [], + 'slug': 'this-is-an-edit-test', + 'tags': { 'jUtS7pVbv7DXoQyV13RR': true }, + 'time': '3-4 weeks', + 'title': 'This is an edit test', + 'cover_image': { + 'contentType': 'image/jpeg', + 'name': 'howto-intro.jpg', + 'size': 19897, + 'type': 'image/jpeg', + }, + 'steps': [{ + '_animationKey': 'unique1', + 'caption': 'What a step caption', + 'images': [{ + 'contentType': 'image/jpeg', + 'name': 'howto-step-pic1.jpg', + 'size': 19410, + 'type': 'image/jpeg', + }, { + 'contentType': 'image/jpeg', + 'name': 'howto-step-pic2.jpg', + 'size': 20009, + 'type': 'image/jpeg', + }], + 'text': 'Description for step 1', + 'title': 'Step 1 is easy', + }, { + '_animationKey': 'unique3', + 'caption': 'What a step caption', + 'images': [{ + 'contentType': 'image/jpeg', + 'name': '3.1.jpg', + 'size': 141803, + 'type': 'image/jpeg', + }, { + 'contentType': 'image/jpeg', + 'name': '3.2.jpg', + 'size': 211619, + 'type': 'image/jpeg', + }, { + 'contentType': 'image/jpeg', + 'name': '3.4.jpg', + 'size': 71309, + 'type': 'image/jpeg', + }], + 'text': 'Description for step 2', + 'title': 'Step 2 is easy', + }], + } + it('[By Anonymous]', () => { cy.step('Redirect to Home Page after visiting an url') cy.logout() @@ -184,12 +241,12 @@ describe('[How To]', () => { cy.get('[data-cy=edit]').click() cy.step('Update the intro') - cy.get('[data-cy=intro-title]').clear().type('This is an edit test') + cy.get('[data-cy=intro-title]').clear().type(expected.title) selectTag('howto_testing') - selectTimeDuration('3-4 weeks') - selectDifficultLevel('Hard') - cy.get('[data-cy=intro-description]').clear().type('After editing, all changes are reverted') - cy.get('[data-cy=intro-caption]').clear().type('Caption edited!') + selectTimeDuration(expected.time as Duration) + selectDifficultLevel(expected.difficulty_level as Difficulty) + cy.get('[data-cy=intro-description]').clear().type(expected.description) + cy.get('[data-cy=intro-caption]').clear().type(expected.caption) cy.step('Update a new cover for the intro') cy.get('[data-cy=intro-cover]').find('button[data-cy=delete]').click() @@ -199,9 +256,13 @@ describe('[How To]', () => { deleteStep(5) deleteStep(4) deleteStep(2) - // TODO - // fillStep(1) - // fillStep(2) + + expected.steps.forEach((step, index) => { + fillStep(index + 1, step.title, step.text, step.caption, [ + 'images/howto-step-pic1.jpg', + 'images/howto-step-pic2.jpg', + ]) + }) cy.get('[data-cy=submit]').click() @@ -210,6 +271,7 @@ describe('[How To]', () => { cy.get('[data-cy=view-howto]').click() .url().should('include', '/how-to/this-is-an-edit-test') cy.get('[data-cy=how-to-basis]').contains('This is an edit test') + cy.queryDocuments('v2_howtos', 'title', '==', 'This is an edit test').should('eqHowto', expected) }) }) }) diff --git a/cypress/support/custom-assertions.ts b/cypress/support/custom-assertions.ts index 4c6cbfa90e..41ced6e47b 100644 --- a/cypress/support/custom-assertions.ts +++ b/cypress/support/custom-assertions.ts @@ -12,7 +12,7 @@ declare global { } const eqHowto =(chaiObj, utils) => { - function shallowCompare(this: any, expected: any) { + function compare(this: any, expected: any) { const subject: IHowto = this._obj const { _createdBy, _deleted, caption, description, difficulty_level, slug, time, title , tags} = expected expect(subject, 'Basic info').to.containSubset({ _createdBy, _deleted, caption, description, difficulty_level, slug, time, title , tags}) @@ -22,17 +22,17 @@ const eqHowto =(chaiObj, utils) => { expect(subject.steps[index], `Have step ${index}`).to.eqHowtoStep(step, index) }) } - chaiObj.Assertion.addMethod('eqHowto', shallowCompare) + chaiObj.Assertion.addMethod('eqHowto', compare) } const eqHowtoStep = (chaiObj, utils) => { - function shallowCompare(this: any, expected: any, index: number) { + function compare(this: any, expected: any, index: number) { const subject: IHowtoStep = this._obj const {_animationKey, caption, text, title} = expected expect(subject, `Step ${index} with info`).to.containSubset({_animationKey, caption, text, title}) expect(subject.images, `Step ${index} with images`).to.containSubset(expected.images) } - chaiObj.Assertion.addMethod('eqHowtoStep', shallowCompare) + chaiObj.Assertion.addMethod('eqHowtoStep', compare) } chai.use(eqHowto); chai.use(eqHowtoStep);