From 444905f6d3560cd6fd144472a4797643f915cd7d Mon Sep 17 00:00:00 2001 From: Olga Bulat Date: Fri, 27 Mar 2020 01:15:24 +0300 Subject: [PATCH] Add tests for Stepper component Signed-off-by: Olga Bulat --- src/components/DropdownStep.vue | 2 +- src/components/Stepper.vue | 22 +- tests/e2e/page-objects/chooser.js | 88 +++++++ tests/e2e/page-objects/homepage.js | 52 ---- tests/e2e/specs/HelpSection.js | 82 ------- tests/e2e/specs/Stepper.js | 131 ++++++++++ tests/unit/components/Stepper.spec.js | 224 ++++++++++++++++++ .../__snapshots__/Stepper.spec.js.snap | 161 +++++++++++++ 8 files changed, 612 insertions(+), 150 deletions(-) create mode 100644 tests/e2e/page-objects/chooser.js delete mode 100644 tests/e2e/page-objects/homepage.js delete mode 100644 tests/e2e/specs/HelpSection.js create mode 100644 tests/e2e/specs/Stepper.js create mode 100644 tests/unit/components/Stepper.spec.js create mode 100644 tests/unit/components/__snapshots__/Stepper.spec.js.snap diff --git a/src/components/DropdownStep.vue b/src/components/DropdownStep.vue index 3b1c9ef27..e6164b8df 100644 --- a/src/components/DropdownStep.vue +++ b/src/components/DropdownStep.vue @@ -37,7 +37,7 @@ export default { }, methods: { updateSelected() { - this.$emit('input', 'DD', this.$props.stepId, true) + this.$emit('change', 'DD', this.$props.stepId, true) } } } diff --git a/src/components/Stepper.vue b/src/components/Stepper.vue index cce98b7dc..e16fcdc6f 100644 --- a/src/components/Stepper.vue +++ b/src/components/Stepper.vue @@ -3,7 +3,7 @@
this.currentStepId) { - for (let i = this.currentStepId; i < clickedStepId; i++) { - this.$set(this.steps, i, { ...this.steps[i], status: 'previous' }) - } - this.$set(this.steps, clickedStepId, { ...this.steps[clickedStepId], status: 'current' }) - } else { - for (let i = this.currentStepId; i > clickedStepId; i--) { - this.$set(this.steps, i, { ...this.steps[i], status: 'inactive' }) - } - this.$set(this.steps, clickedStepId, { ...this.steps[clickedStepId], status: 'current' }) + // only steps before the current one are clickable + if (clickedStepId >= this.currentStepId) return + for (let i = this.currentStepId; i > clickedStepId; i--) { + this.$set(this.steps, i, { ...this.steps[i], status: 'inactive' }) } + this.$set(this.steps, clickedStepId, { ...this.steps[clickedStepId], status: 'current' }) this.currentStepId = clickedStepId }, setStepsVisible(stepsToSetVisible) { diff --git a/tests/e2e/page-objects/chooser.js b/tests/e2e/page-objects/chooser.js new file mode 100644 index 000000000..12d32d31c --- /dev/null +++ b/tests/e2e/page-objects/chooser.js @@ -0,0 +1,88 @@ +/** + * Page object for license chooser + */ + +const stepperCommands = { + clickYes: function() { + this.click('.step-actions .field:first-of-type .check') + return this + }, + clickNo: function() { + this.click('.step-actions .field:last-of-type .check') + return this + }, + clickNext: function() { + this.click('.pagination-next') + return this + }, + clickPrevious: function() { + this.click('.pagination-previous') + return this + }, + chooseNo: function() { + this.clickNo() + .clickNext() + return this + }, + chooseYes: function() { + this.clickYes() + .clickNext() + return this + }, + clickWaiver: function() { + this.click('.checkbox:first-of-type input[type=checkbox]') + .click('.checkbox:last-of-type input[type=checkbox]') + .click('.pagination-next') + return this + }, + selectFromDropdown: function(licenseName) { + this + .waitForElementVisible('.license-dropdown') + .click('.license-dropdown') + .click(`.license-dropdown option[value="${licenseName}"]`) + .click('.pagination-next') + return this + }, + assertStepName: function(stepName) { + this.expect.element('@currentStep').to.have.property('classList').contain(stepName) + return this + } +} +const chooserCommands = { + assertSelectedLicenseDisplayed: function(licenseName) { + this + .assert.containsText('.license-name', licenseName) + .assert.containsText('p.license-text a', licenseName) + return this + } +} +module.exports = { + url: '/', + commands: [chooserCommands], + + elements: { + appContainer: '#app', + stepper: '.stepper-container', + selectedLicenseCard: '.selected-license-card', + licenseUseCard: '.license-use-card' + }, + + sections: { + stepper: { + selector: '.stepper-container', + elements: { + currentStep: { + selector: '.step-container.current' + } + }, + commands: [stepperCommands] + }, + selectedLicenseCard: { + selector: '.selected-license-card' + }, + licenseUseCard: { + selector: '.license-use-card' + } + } + +} diff --git a/tests/e2e/page-objects/homepage.js b/tests/e2e/page-objects/homepage.js deleted file mode 100644 index 427ce832d..000000000 --- a/tests/e2e/page-objects/homepage.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * A Nightwatch page object. The page object name is the filename. - * - * Example usage: - * browser.page.homepage.navigate() - * - * For more information on working with page objects see: - * https://nightwatchjs.org/guide/working-with-page-objects/ - * - */ - -module.exports = { - url: '/', - commands: [], - - // A page object can have elements - elements: { - appContainer: '#app' - }, - - // Or a page objects can also have sections - sections: { - app: { - selector: '#app', - - elements: { - logo: 'img' - }, - - // - a page object section can also have sub-sections - // - elements or sub-sections located here are retrieved using the "app" section as the base - sections: { - headline: { - selector: 'h1' - }, - - welcome: { - // the equivalent css selector for the "welcome" sub-section would be: - // '#app div.hello' - selector: 'div.hello', - - elements: { - cliPluginLinks: { - selector: 'ul', - index: 0 - } - } - } - } - } - } -} diff --git a/tests/e2e/specs/HelpSection.js b/tests/e2e/specs/HelpSection.js deleted file mode 100644 index 7f04de5ee..000000000 --- a/tests/e2e/specs/HelpSection.js +++ /dev/null @@ -1,82 +0,0 @@ -module.exports = { - - 'Common license pop up redirects to https://creativecommons.org/licenses/': function(browser) { - browser - .init() - .assert.elementPresent('#cc_license_btn') - .click('#cc_license_btn') - .assert.elementPresent('#license_link') - .click('#license_link') - .assert.urlEquals('https://creativecommons.org/licenses/') - }, - - 'How do licenses work pop up redirects to https://creativecommons.org/licenses/': function(browser) { - browser - .init() - .assert.elementPresent('#license_work_btn') - .click('#license_work_btn') - .assert.elementPresent('#license_link_2') - .click('#license_link_2') - .assert.urlEquals('https://creativecommons.org/licenses/') - }, - - 'How do licenses work pop up redirects to https://wiki.creativecommons.org/wiki/CC_REL': function(browser) { - browser - .init() - .assert.elementPresent('#license_work_btn') - .click('#license_work_btn') - .assert.elementPresent('#cc_rights_link') - .click('#cc_rights_link') - .assert.urlEquals('https://wiki.creativecommons.org/wiki/CC_REL') - }, - - 'Public domain pop up redirects to https://creativecommons.org/publicdomain/': function(browser) { - browser - .init() - .assert.elementPresent('#public_domain_btn') - .click('#public_domain_btn') - .assert.elementPresent('#domain_link') - .click('#domain_link') - .assert.urlEquals('https://creativecommons.org/publicdomain/') - }, - - 'License consideration pop up redirects to https://wiki.creativecommons.org/wiki/Considerations_for_licensors_and_licensees#Considerations_for_licensors': function(browser) { - browser - .init() - .assert.elementPresent('#consideration_btn') - .click('#consideration_btn') - .assert.elementPresent('#licensors_link') - .click('#licensors_link') - .assert.urlEquals('https://wiki.creativecommons.org/wiki/Considerations_for_licensors_and_licensees#Considerations_for_licensors') - }, - - 'License consideration pop up redirects to https://wiki.creativecommons.org/wiki/Considerations_for_licensors_and_licensees#Considerations_for_licensees': function(browser) { - browser - .init() - .assert.elementPresent('#consideration_btn') - .click('#consideration_btn') - .assert.elementPresent('#licensees_link') - .click('#licensees_link') - .assert.urlEquals('https://wiki.creativecommons.org/wiki/Considerations_for_licensors_and_licensees#Considerations_for_licensees') - }, - - 'License consideration pop up redirects to https://wiki.creativecommons.org/wiki/Considerations_for_licensors_and_licensees': function(browser) { - browser - .init() - .assert.elementPresent('#consideration_btn') - .click('#consideration_btn') - .assert.elementPresent('#wiki_link') - .click('#wiki_link') - .assert.urlEquals('https://wiki.creativecommons.org/wiki/Considerations_for_licensors_and_licensees') - }, - - 'License types pop up redirects to https://creativecommons.org/share-your-work/licensing-types-examples/': function(browser) { - browser - .init() - .assert.elementPresent('#license_description_btn') - .click('#license_description_btn') - .assert.elementPresent('#licensing_examples_link') - .click('#licensing_examples_link') - .assert.urlEquals('https://creativecommons.org/share-your-work/licensing-examples/') - } -} diff --git a/tests/e2e/specs/Stepper.js b/tests/e2e/specs/Stepper.js new file mode 100644 index 000000000..fe0fdca26 --- /dev/null +++ b/tests/e2e/specs/Stepper.js @@ -0,0 +1,131 @@ +module.exports = { + 'Step one: open License Chooser page': function(browser) { + const chooser = browser.page.chooser().navigate() + chooser.expect.section('@stepper').to.be.visible + chooser.expect.section('@selectedLicenseCard').to.not.be.present + chooser.expect.section('@licenseUseCard').to.not.be.present + }, + 'Step two: selecting "I need help" opens license attribute steps and selected license card': function(browser) { + const chooser = browser.page.chooser().navigate() + const stepper = chooser.section.stepper + stepper.expect.element('@currentStep').to.be.present + stepper.chooseNo().clickNo() + chooser.expect.section('@selectedLicenseCard').to.be.visible + }, + 'Step three: going through license attribute steps opens license use card and Attribution Details Step': function(browser) { + const chooser = browser.page.chooser().navigate() + const stepper = chooser.section.stepper + stepper + .chooseNo() + .chooseYes() + .chooseYes() + .chooseYes() + .chooseYes() + chooser.expect.section('@licenseUseCard').to.be.visible + }, + 'Can choose CC0 1.0 license': function(browser) { + const chooser = browser.page.chooser().navigate() + const stepper = chooser.section.stepper + stepper + .chooseNo() + .chooseNo() + .clickWaiver() + chooser.assertSelectedLicenseDisplayed('CC0 1.0') + }, + 'Can choose CC BY license': function(browser) { + const chooser = browser.page.chooser().navigate() + const stepper = chooser.section.stepper + stepper + .chooseNo() + .chooseYes() + .chooseYes() + .chooseYes() + .chooseYes() + chooser.assertSelectedLicenseDisplayed('CC BY 4.0') + }, + 'Can choose CC BY-NC license': function(browser) { + const chooser = browser.page.chooser().navigate() + const stepper = chooser.section.stepper + stepper + .chooseNo() + .chooseYes() + .chooseNo() + .chooseYes() + .chooseYes() + chooser.assertSelectedLicenseDisplayed('CC BY-NC 4.0') + }, + 'Can choose CC BY-ND license': function(browser) { + const chooser = browser.page.chooser().navigate() + const stepper = chooser.section.stepper + stepper + .chooseNo() + .chooseYes() + .chooseYes() + .chooseNo() + + chooser.assertSelectedLicenseDisplayed('CC BY-ND 4.0') + }, + 'Can choose CC BY-NC-ND license': function(browser) { + const chooser = browser.page.chooser().navigate() + const stepper = chooser.section.stepper + stepper + .chooseNo() + .chooseYes() + .chooseNo() + .chooseNo() + chooser.assertSelectedLicenseDisplayed('CC BY-NC-ND 4.0') + }, + 'Can choose CC BY-SA license': function(browser) { + const chooser = browser.page.chooser().navigate() + const stepper = chooser.section.stepper + stepper + .chooseNo() + .chooseYes() + .chooseYes() + .chooseYes() + .chooseNo() + chooser.assertSelectedLicenseDisplayed('CC BY-SA 4.0') + }, + 'Can choose CC BY-NC-SA license': function(browser) { + const chooser = browser.page.chooser().navigate() + const stepper = chooser.section.stepper + stepper + .chooseNo() + .chooseYes() + .chooseNo() + .chooseYes() + .chooseNo() + chooser.assertSelectedLicenseDisplayed('CC BY-NC-SA 4.0') + }, + 'Can select a license from dropdown': function(browser) { + const chooser = browser.page.chooser().navigate() + const stepper = chooser.section.stepper + stepper + .chooseYes() + .selectFromDropdown('CC BY-NC-ND 4.0') + chooser.assertSelectedLicenseDisplayed('CC BY-NC-ND 4.0') + }, + 'Can go back by clicking on Previous button': function(browser) { + const chooser = browser.page.chooser().navigate() + const stepper = chooser.section.stepper + stepper + .chooseNo() + .chooseYes() + .chooseNo() + .chooseYes() + .chooseNo() + chooser.assertSelectedLicenseDisplayed('CC BY-NC-SA 4.0') + stepper.clickPrevious() + chooser.assert.elementNotPresent('@licenseUseCard') + stepper.assertStepName('SA') + .clickPrevious() + .assertStepName('ND') + .clickPrevious() + .assertStepName('NC') + .clickPrevious() + .assertStepName('BY') + .clickPrevious() + .assertStepName('FS') + chooser.assert.elementPresent('@selectedLicenseCard') + } +} diff --git a/tests/unit/components/Stepper.spec.js b/tests/unit/components/Stepper.spec.js new file mode 100644 index 000000000..8986217f4 --- /dev/null +++ b/tests/unit/components/Stepper.spec.js @@ -0,0 +1,224 @@ +import Vuex from 'vuex' +import { shallowMount, createLocalVue, config } from '@vue/test-utils' +import Stepper from '@/components/Stepper' +import Buefy from 'buefy' +import VueI18n from 'vue-i18n' +import Vue from 'vue' +import store from '@/store' + +function getNextButton(wrapper) { + return wrapper.find('.pagination-next') +} +function clickNext(wrapper) { + getNextButton(wrapper).trigger('click') +} +function getStepId(wrapper, stepName) { + return wrapper.vm.steps.filter((step) => { return step.name === stepName })[0].id +} +function setStepSelected(wrapper, stepName, isSelected) { + const stepId = getStepId(wrapper, stepName) + wrapper.vm.changeStepSelected(stepName, stepId, isSelected) +} +function stepHeadingText(steps, index) { + return steps.at(index).find('h5').text() +} +function advanceStep(wrapper, actions) { + for (const { stepName, isSelected, license } of actions) { + const stepId = getStepId(wrapper, stepName) + if (stepName === 'DD') { + wrapper.vm.$store.commit('updateAttributesFromShort', license) + } + wrapper.vm.changeStepSelected(stepName, stepId, isSelected) + wrapper.vm.handleNext(stepName) + } +} + +let wrapper +let localVue + +function setUp() { + localVue = createLocalVue() + localVue.use(Vuex) + localVue.use(Buefy) + Vue.use(VueI18n) + const messages = require('@/locales/en.json') + const i18n = new VueI18n({ + locale: 'en', + fallbackLocale: 'en', + messages: messages + }) + + config.mocks.i18n = i18n + + config.mocks.$t = key => { + // key is a string (eg. 'stepper.ND.question') + // this line converts it into an object reference + // eg. messages['stepper.ND.question'] -> messages.stepper.ND.question + return key.split('.').reduce((messages, k) => messages[k], i18n.messages) + } + wrapper = shallowMount(Stepper, { + store, + localVue + }) + wrapper.vm.$on('input', (newVal) => { + wrapper.setProps({ value: newVal }) + }) +} + +beforeEach(() => setUp()) + +describe('Stepper.vue', () => { + describe('renders correctly', () => { + it('is called', () => { + expect(wrapper.name()).toBe('Stepper') + expect(wrapper.isVueInstance()).toBeTruthy() + }) + it('has expected UI initially', () => { + expect(wrapper).toMatchSnapshot() + }) + it('has expected UI on ND step', () => { + advanceStep(wrapper, [ + { stepName: 'FS', isSelected: false }, + { stepName: 'BY', isSelected: true }, + { stepName: 'NC', isSelected: false } + ]) + expect(wrapper).toMatchSnapshot() + }) + it('has expected UI on CW step after DD', () => { + advanceStep(wrapper, [ + { stepName: 'FS', isSelected: true }, + { stepName: 'DD', isSelected: true, license: 'CC0 1.0' } + ]) + expect(wrapper).toMatchSnapshot() + }) + }) + + describe('Step headings', () => { + it('inactive step headings are not clickable', () => { + const stepHeaders = wrapper.findAll('.step-header') + const steps = wrapper.findAll('.step-container') + stepHeaders.at(0).trigger('click') + expect(steps.at(0).classes('current')).toBe(true) + stepHeaders.at(1).trigger('click') + expect(steps.at(1).classes('inactive')).toBe(true) + }) + it('clicking on disabled previous step does not change current step', () => { + advanceStep(wrapper, + [ + { stepName: 'FS', isSelected: false }, + { stepName: 'BY', isSelected: false } + ]) + expect(wrapper.find('.current').classes()).toContain('CW') + wrapper.findAll('.step-header').at(2).trigger('click') + expect(wrapper.find('.current').classes()).toContain('CW') + }) + }) + describe('Next button', () => { + it('becomes clickable only after selection is made', () => { + const nextButton = wrapper.find('.pagination-next') + expect(nextButton.classes('disabled')).toBe(true) + setStepSelected(wrapper, 'FS', false) + expect(nextButton.classes('disabled')).toBe(false) + }) + it('clicking on Next button advances the step', () => { + setStepSelected(wrapper, 'FS', true) + clickNext(wrapper) + const steps = wrapper.findAll('.step-container') + expect(steps.at(0).classes('previous')).toBe(true) + expect(steps.at(1).classes('current')).toBe(true) + }) + }) + describe('Previous button', () => { + it('AttributionDetails Step opens correct step when Previous button is clicked', () => { + // For BY- licenses, it should open SA step + advanceStep(wrapper, [ + { stepName: 'FS', isSelected: false }, + { stepName: 'BY', isSelected: true }, + { stepName: 'NC', isSelected: false }, + { stepName: 'ND', isSelected: false }, + { stepName: 'SA', isSelected: false }]) + wrapper.find('.pagination-previous').trigger('click') + expect(wrapper.find('.current').classes()).toContain('SA') + // for CC0 license, it should open CW step + wrapper.findAll('.step-header').at(1).trigger('click') + setStepSelected(wrapper, 'BY', false) + clickNext(wrapper) + setStepSelected(wrapper, 'CW', true) + clickNext(wrapper) + wrapper.find('.pagination-previous').trigger('click') + expect(wrapper.find('.current').classes()).toContain('CW') + // if using dropdown with a BY- license, it should open DD step + wrapper.findAll('.step-header').at(0).trigger('click') + advanceStep(wrapper, [{ stepName: 'FS', isSelected: true }]) + advanceStep(wrapper, [{ stepName: 'DD', isSelected: true, license: 'CC BY-NC-ND 4.0' }]) + wrapper.find('.pagination-previous').trigger('click') + expect(wrapper.find('.current').classes()).toContain('DD') + console.log(wrapper.emitted()) + }) + }) + describe('FirstStep interactions', () => { + it('choosing Yes sets 3 steps visible: FS, Dropdown and AttributionDetails, opens DD', () => { + advanceStep(wrapper, [ + { stepName: 'FS', isSelected: true }]) + const steps = wrapper.findAll('.step-container') + expect(steps.length).toEqual(3) + expect(steps.at(0).classes('previous')).toBe(true) + expect(wrapper.find('.current').classes('DD')).toBe(true) + }) + it('choosing No sets 6 steps visible: FS, BY, NC, ND, SA and AttributionDetails, opens BY', () => { + setStepSelected(wrapper, 'FS', false) + clickNext(wrapper) + const steps = wrapper.findAll('.step-container') + expect(steps.length).toEqual(6) + expect(wrapper.vm.currentStepId).toEqual(1) + expect(stepHeadingText(steps, 1)).toEqual(wrapper.vm.$t('stepper.BY.question')) + }) + }) + describe('Step interactions', () => { + it('BY: selecting No updates enabled steps; disables NC, ND and SA; opens CopyrightWaiverStep', () => { + advanceStep(wrapper, [{ stepName: 'FS', isSelected: false }]) + setStepSelected(wrapper, 'BY', false) + const nextButton = getNextButton(wrapper) + expect(nextButton.classes('disabled')).toBe(false) + const steps = wrapper.findAll('.step-container') + const disabledSteps = wrapper.findAll('.disabled') + expect(disabledSteps.length).toEqual(3) + expect(steps.length).toEqual(7) + clickNext(wrapper) + expect(wrapper.find('.current').classes()).toContain('CW') + }) + it('ND: selecting ND license updates enabled steps; disables SA step; opens AttributionDetailsStep', () => { + advanceStep(wrapper, [ + { stepName: 'FS', isSelected: false }, + { stepName: 'BY', isSelected: true }, + { stepName: 'NC', isSelected: false }]) + setStepSelected(wrapper, 'ND', true) + const steps = wrapper.findAll('.step-container') + const disabledSteps = wrapper.findAll('.disabled') + expect(disabledSteps.length).toEqual(1) + expect(steps.length).toEqual(6) + clickNext(wrapper) + expect(wrapper.find('.current').classes()).toContain('AD') + }) + }) + describe('DropdownStep interactions', () => { + beforeEach(() => { + // setUp() + advanceStep(wrapper, [ + { stepName: 'FS', isSelected: true }]) + }) + it('selecting CC0 makes 4 steps visible and opens Copyright Waiver step', async() => { + const shortName = 'CC0 1.0' + advanceStep(wrapper, [{ stepName: 'DD', isSelected: true, license: shortName }]) + const steps = wrapper.findAll('.step-container') + expect(steps.length).toEqual(4) + expect(wrapper.find('.current').classes()).toContain('CW') + }) + it('selecting a BY license and clicking Next makes 3 steps visible and opens AttributionDetails step', async() => { + advanceStep(wrapper, [{ stepName: 'DD', isSelected: true, license: 'CC BY 4.0' }]) + const steps = wrapper.findAll('.step-container') + expect(steps.length).toEqual(3) + expect(wrapper.find('.current').classes()).toContain('AD') + }) + }) +}) diff --git a/tests/unit/components/__snapshots__/Stepper.spec.js.snap b/tests/unit/components/__snapshots__/Stepper.spec.js.snap new file mode 100644 index 000000000..009f8326a --- /dev/null +++ b/tests/unit/components/__snapshots__/Stepper.spec.js.snap @@ -0,0 +1,161 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Stepper.vue renders correctly has expected UI initially 1`] = ` +
+
+
+
+ Do you know which license you need? +
+
+ + +
+
+
+
+ Attribution +
+
+ + +
+
+
+
+ Commercial Use +
+
+ + +
+
+
+
+ Derivative Works +
+
+ + +
+
+
+
+ Sharing Requirements +
+
+ + +
+
+
+
+ Attribution Details +
+
+ + +
+
+`; + +exports[`Stepper.vue renders correctly has expected UI on CW step after DD 1`] = ` +
+ + +
+
+
+ Waive Your Copyright +
+
+ + +
+
+
+
+ Attribution Details +
+
+ + +
+
+`; + +exports[`Stepper.vue renders correctly has expected UI on ND step 1`] = ` +
+ + + +
+
+
+ Do you want to allow others to remix, adapt, or build upon your work? +
+
+ + +
+
+
+
+ Sharing Requirements +
+
+ + +
+
+
+
+ Attribution Details +
+
+ + +
+
+`;