tao-test/app/taoQtiTest/views/js/e2e/runner/tools/tools.spec.js

721 lines
30 KiB
JavaScript

/**
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; under version 2
* of the License (non-upgradable).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2019 (original work) Open Assessment Technologies SA ;
*/
import '../../_helpers/commands/setupCommands';
import '../../_helpers/commands/pointerCommands';
import '../../_helpers/commands/cleanupCommands';
import '../../_helpers/routes/backOfficeRoutes';
import '../../_helpers/routes/runnerRoutes';
import base64Test from './fixtures/base64TestTakerToolsTestPackage';
/**
* Re-login as guest and enter the test
* Intended for the before hook of each tool's describe() block
*/
Cypress.Commands.add('resetAndEnterTest', function() {
cy.setupServer();
cy.addRunnerRoutes();
cy.guestLogin();
cy.startTest('e2e Tools test');
});
describe('Tools', () => {
/**
* Setup to have a proper delivery:
* - Start server
* - Add necessary routes
* - Admin login
* - Import test package
* - Publish imported test as a delivery
* - Set guest access on delivery and save
* - Logout
*/
before(() => {
if (Cypress.env('bypassBackOffice') !== "true") {
cy.setupServer();
cy.addBackOfficeRoutes();
cy.login('admin');
cy.importTestPackage(base64Test, 'e2e Tools test');
cy.publishTest('e2e Tools test');
cy.setDeliveryForGuests('e2e Tools test');
cy.logout();
}
});
/**
* Destroy everything we created during setup, leaving the environment clean for next time.
*/
after(() => {
if (Cypress.env('bypassBackOffice') !== "true") {
cy.setupServer();
cy.addBackOfficeRoutes();
cy.guestLogout();
cy.login('admin');
cy.deleteItem('e2e Tools test');
cy.deleteTest('e2e Tools test');
cy.deleteDelivery('Delivery of e2e Tools test');
}
});
/**
* Tools tests
*/
describe('Test-Taker Tools', () => {
describe('Comments tool', () => {
before(() => {
cy.resetAndEnterTest();
});
beforeEach(() => {
// server & routes needed here for comment submission test
cy.setupServer();
cy.addRunnerRoutes();
cy.get('.tools-box-list [data-control=comment] a').as('toolBtn');
cy.get('[data-control=qti-comment]').as('popup');
cy.get('[data-control=qti-comment-text]').as('textarea');
cy.get('[data-control=qti-comment-send]').as('submitBtn');
cy.get('[data-control=qti-comment-cancel]').as('cancelBtn');
});
it('loads', function() {
cy.get('@toolBtn').should('have.length', 1).and('be.visible');
});
it('opens/closes', () => {
// click tool => textarea visible
cy.get('@toolBtn').click();
cy.get('@popup').should('be.visible');
// click tool => textarea closes
cy.get('@toolBtn').click();
cy.get('@popup').should('not.be.visible');
// click tool => textarea visible
cy.get('@toolBtn').click();
// cancel => textarea closes
cy.get('@cancelBtn').click();
cy.get('@popup').should('not.be.visible');
});
it('submits', () => {
// open it
cy.get('@toolBtn').click();
cy.get('@popup').should('be.visible');
cy.get('@textarea').should('have.attr', 'placeholder', 'Your comment…');
// empty => cannot submit
cy.get('@submitBtn').click();
cy.get('@popup').should('be.visible');
// type text => can submit & close
cy.get('@textarea').type('Blah blah blah');
cy.get('@submitBtn').click();
cy.get('@popup').should('not.be.visible');
// xhr
cy.wait('@comment').then((xhr) => {
assert.ok(xhr.response.body.success, 'comment response success true');
});
});
});
describe('Calculator tool', () => {
before(() => {
cy.resetAndEnterTest();
});
beforeEach(() => {
cy.get('.tools-box-list [data-control=calculator] a').as('toolBtn');
cy.get('.test-runner-scope .widget-calculator').as('calcContainer');
});
it('loads', function() {
cy.get('@toolBtn').should('have.length', 1).and('be.visible');
cy.get('@calcContainer').should('have.length', 1).and('be.empty').and('not.be.visible');
});
it('opens/closes', function() {
// click tool => calc renders
cy.get('@toolBtn').click();
cy.get('@calcContainer').find('.dynamic-component-container').as('calc');
cy.get('@calc').should('be.visible');
cy.get('@calc').find('a[title="Close"]').as('closer');
// click tool => hide
cy.get('@toolBtn').click();
cy.get('@calc').should('not.be.visible');
// click tool => calc visible
cy.get('@toolBtn').click();
cy.get('@calc').should('be.visible');
// click close => hide
cy.get('@closer').click();
cy.get('@calc').should('not.be.visible');
});
it('calculates', function() {
// click tool => calc renders
cy.get('@toolBtn').click();
cy.get('@calcContainer').find('.dynamic-component-container').as('calc');
cy.get('@calc').should('be.visible');
cy.get('@calc').find('.calcDisplay').as('display');
// 2 + 2 => 4
cy.get('@calc').find('[data-key="2"]').click();
cy.get('@calc').find('[data-key="+"]').click();
cy.get('@calc').find('[data-key="2"]').click();
cy.get('@calc').find('[data-key="="]').click();
cy.get('@display').should('have.value', '4');
// clear
cy.get('@calc').find('[data-key="C"]').click();
cy.get('@display').should('have.value', '0');
// close it
cy.get('@toolBtn').click();
});
it.skip('is dynamic (drag/resize)', function() {
// open it
cy.get('@toolBtn').click();
cy.get('@calcContainer').find('.dynamic-component-container').as('calc');
cy.get('@calc').within(() => {
// draggable
cy.get('.dynamic-component-title-bar').dragToPoint({x: 400, y: 250}, 'left');
// using approximate position values because pointer can't get right into corner of title bar
cy.root().invoke('data', 'x').should('be.gt', 385);
cy.root().invoke('data', 'y').should('be.gt', 75);
// resizable (to its minimum size)
cy.get('.dynamic-component-resize-wrapper').dragToPoint({x: 0, y: 0});
cy.root().invoke('width').should('equal', 148);
cy.root().invoke('height').should('equal', 218);
});
// close it
cy.get('@toolBtn').click();
});
});
describe('Zoom tool', () => {
before(() => {
cy.resetAndEnterTest();
});
beforeEach(() => {
cy.get('.tools-box-list').within(() => {
cy.get('[data-control=zoomOut] a').as('zoomOut');
cy.get('[data-control=zoomIn] a').as('zoomIn');
});
cy.get('.test-runner-scope .qti-item').as('item');
});
it('loads', function() {
cy.get('@zoomOut').should('have.length', 1).and('be.visible');
cy.get('@zoomIn').should('have.length', 1).and('be.visible');
});
it('zooms in/out', function() {
// zoom out
cy.get('@zoomOut').click();
cy.get('@item')
.should('have.class', 'transform-scale')
.and('have.attr', 'style').and('contain', 'scaleX(0.9)').and('contain', 'scaleY(0.9)');
cy.get('@zoomOut').click();
cy.get('@item')
.should('have.class', 'transform-scale')
.and('have.attr', 'style').and('contain', 'scaleX(0.8)').and('contain', 'scaleY(0.8)');
// reset
cy.get('@zoomIn').click();
cy.get('@zoomIn').click();
// zoom in
cy.get('@zoomIn').click();
cy.get('@item')
.should('have.class', 'transform-scale')
.and('have.attr', 'style').and('contain', 'scaleX(1.1)').and('contain', 'scaleY(1.1)');
cy.get('@zoomIn').click();
cy.get('@item')
.should('have.class', 'transform-scale')
.and('have.attr', 'style').and('contain', 'scaleX(1.2)').and('contain', 'scaleY(1.2)');
// beyond the max! (2.0)
for (let i = 0; i < 10; i++) {
cy.get('@zoomIn').click();
}
cy.get('@item')
.should('have.class', 'transform-scale')
.and('have.attr', 'style').and('contain', 'scaleX(2)').and('contain', 'scaleY(2)');
// reset
for (let i = 0; i < 10; i++) {
cy.get('@zoomOut').click();
}
cy.get('@item')
.should('not.have.class', 'transform-scale')
.should('have.css', 'transform', 'none');
});
});
describe('Highlighter tool', () => {
before(() => {
cy.resetAndEnterTest();
});
beforeEach(() => {
cy.get('.tools-box-list').within(() => {
cy.get('[data-control=highlight-trigger] a').as('trigger');
cy.get('[data-control=highlight-clear] a').as('clear');
});
});
it('loads', function() {
cy.get('@trigger').should('have.length', 1).and('be.visible');
cy.get('@clear').should('have.length', 1).and('be.visible');
});
it('highlights (tool first)', function() {
cy.get('.test-runner-scope .qti-item').within(() => {
// tool first mode
cy.get('@trigger').click();
cy.get('h1').selectText();
cy.get('h1').find('span.txt-user-highlight')
.contains('Tools')
.and('has.css', 'background-color', 'rgb(255, 255, 0)');
// clear
cy.get('@clear').click();
cy.get('.qti-itemBody').should('not.contain', 'span.txt-user-highlight');
});
});
it('highlights (selection first)', function() {
cy.get('.test-runner-scope .qti-item').within(() => {
// selection first mode
cy.get('.qti-prompt').selectText();
cy.get('@trigger').click();
cy.get('.qti-prompt').find('span.txt-user-highlight')
.contains('Here is the test for Answer Elimination and Answer Masking')
.and('has.css', 'background-color', 'rgb(255, 255, 0)');
// clear
cy.get('@clear').click();
cy.get('.qti-itemBody').should('not.contain', 'span.txt-user-highlight');
});
});
it('turns off', function() {
// turn on, off
cy.get('@trigger').click();
cy.get('@trigger').click();
cy.get('.test-runner-scope .qti-item h1').selectText()
.should('not.contain', 'span.txt-user-highlight');
});
});
describe('Line reader tool', () => {
before(() => {
cy.resetAndEnterTest();
});
beforeEach(() => {
cy.get('.tools-box-list [data-control=line-reader] a').as('toolBtn');
});
it('loads', function() {
cy.get('@toolBtn').should('have.length', 1).and('be.visible');
});
it('opens/closes', function() {
cy.get('.test-runner-scope').within(() => {
// open/close toolBtn
cy.get('@toolBtn').click();
cy.get('.line-reader-mask').as('maskParts').should('have.length', 8).and('be.visible');
cy.get('.line-reader-overlay').as('overlay').should('be.visible');
cy.get('.line-reader-overlay .icon').as('outerDrag').should('be.visible');
cy.get('.line-reader-inner-drag').as('innerDrag').should('be.visible');
cy.get('.line-reader-closer').as('closer').should('be.visible');
cy.get('@maskParts').should('be.visible');
cy.get('@toolBtn').click();
cy.get('@maskParts').should('not.be.visible');
// open + closer
cy.get('@toolBtn').click();
cy.get('@maskParts').should('be.visible');
cy.get('@closer').click();
cy.get('@maskParts').should('not.be.visible');
});
});
it.skip('is dynamic (drag/resize)', function() {
// open it
cy.get('@toolBtn').click();
cy.get('.test-runner-scope').within(() => {
cy.get('.line-reader-overlay').as('overlay');
cy.get('.line-reader-inner-drag').as('innerDrag');
// draggable
cy.get('@overlay')
.dragToPoint({x: 500, y: 200})
.invoke('position').then(pos => {
cy.wrap(pos).its('left').should('equal', 0);
cy.wrap(pos).its('top').should('be.gt', 75);
});
// movable slot
cy.get('@innerDrag')
.dragToPoint({x: 600, y: 400})
.invoke('position').then(pos => {
cy.wrap(pos).its('left').should('be.gt', 40);
cy.wrap(pos).its('top').should('be.gt', 200);
});
// resizable (taller)
cy.get('.line-reader-mask.se .resize-control').dragToPoint({x: 1000, y: 600});
cy.get('@overlay').invoke('width').should('be.gt', 800);
// inner window height only knowable by sum:
cy.get('.line-reader-mask.ne').then($top => {
cy.get('.line-reader-mask.e').then($middle => {
cy.get('.line-reader-mask.se').then($bottom => {
expect($top.height() + $middle.height() + $bottom.height()).to.be.greaterThan(400);
});
});
});
// resizable inner (tall and thin)
cy.get('.line-reader-mask.e .resize-control').dragToPoint({x: 750, y: 500});
cy.get('.line-reader-mask.n').invoke('width').should('be.lt', 800);
cy.get('.line-reader-mask.e').invoke('height').should('be.gt', 200);
});
// close it
cy.get('@toolBtn').click();
});
});
describe('Answer masking tool', function() {
before(() => {
cy.resetAndEnterTest();
});
beforeEach(() => {
cy.get('.tools-box-list [data-control=answer-masking] a').as('toolBtn');
});
after(() => {
// close tool after final test
cy.get('@toolBtn').click({ force: true });
});
it('loads', function() {
cy.get('@toolBtn').should('have.length', 1).and('be.visible');
cy.get('.qti-choice').as('choices').should('have.length', 4);
});
it('turns on/off', function() {
// click tool => masks visible
cy.get('@toolBtn').click();
cy.get('.qti-choice.masked').should('have.length', 4);
cy.get('.qti-choice.masked .answer-mask.masked').should('have.length', 4);
// click tool => masks hidden
cy.get('@toolBtn').click();
cy.get('.qti-choice.masked').should('have.length', 0);
cy.get('.qti-choice.masked .answer-mask.masked').should('have.length', 0);
});
it('controls single choice mask', function() {
// click tool => masks visible
cy.get('@toolBtn').click();
// unmask first choice
cy.get('.qti-choice.masked:eq(0) .answer-mask-toggle').as('toggle1');
cy.get('@toggle1').click();
cy.get('.qti-choice.masked').should('have.length', 3);
cy.get('.qti-choice.masked .answer-mask.masked').should('have.length', 3);
cy.get('.qti-choice:eq(0) .answer-mask').invoke('width').should('be.lt', 40);
// remask first choice
cy.get('@toggle1').click();
cy.get('.qti-choice.masked').should('have.length', 4);
cy.get('.qti-choice.masked .answer-mask.masked').should('have.length', 4);
cy.get('.qti-choice:eq(0) .answer-mask').invoke('width').should('be.gt', 40);
// see if choice is really covered (not clickable)
// must be the last line in the test, further code will not be reached
cy.get('.qti-choice:eq(0) .pseudo-label-box').isNotActionable();
});
});
describe('Answer elimination tool', function() {
before(() => {
cy.resetAndEnterTest();
});
beforeEach(() => {
cy.get('.tools-box-list [data-control=eliminator] a').as('toolBtn');
});
after(() => {
// close tool after final test
cy.get('@toolBtn').click({ force: true });
});
it('loads', function() {
cy.get('@toolBtn').should('have.length', 1).and('be.visible');
});
it('turns on/off', function() {
cy.get('.qti-itemBody').within(() => {
// turn on
cy.get('@toolBtn').click();
cy.get('.qti-choice [data-eliminable="container"]').should('have.length', 4).and('be.visible');
cy.get('.qti-choice [data-eliminable="trigger"]').should('have.length', 4).and('be.visible');
// turn off
cy.get('@toolBtn').click();
cy.get('.qti-choice [data-eliminable="container"]').should('have.length', 4).and('not.be.visible');
cy.get('.qti-choice [data-eliminable="trigger"]').should('have.length', 4).and('not.be.visible');
});
});
it('eliminates single choice', function() {
cy.get('.qti-itemBody').within(() => {
cy.get('.qti-choice:eq(0) [data-eliminable="trigger"]').as('firstTrigger');
cy.get('.qti-choice:eq(0) .label-box').as('firstLabel');
// turn on
cy.get('@toolBtn').click();
// eliminate first choice
cy.get('@firstTrigger').click();
cy.get('.qti-choice:eq(0)').should('have.class', 'eliminated');
// un-eliminate first choice
cy.get('@firstTrigger').click();
cy.get('.qti-choice:eq(0)').should('not.have.class', 'eliminated');
cy.get('@firstLabel').click();
// eliminate first choice again
cy.get('@firstTrigger').click();
cy.get('.qti-choice:eq(0)').should('have.class', 'eliminated');
// see if choice is really covered (not clickable)
// must be the last line in the test, further code will not be reached
cy.get('@firstLabel').isNotActionable()
.then(() => {
cy.log('yay');
cy.get('@firstTrigger').click();
});
});
});
});
describe('Area mask tool', function() {
before(() => {
cy.resetAndEnterTest();
});
beforeEach(() => {
cy.get('.tools-box-list [data-control=area-masking] a').as('toolBtn');
});
it('loads', function() {
cy.get('@toolBtn').should('have.length', 1).and('be.visible');
});
it('launches/destroys', function() {
// click tool => areaMask renders
cy.get('@toolBtn').click();
cy.get('.test-runner-scope .mask-container').as('areaMaskContainer');
cy.get('@areaMaskContainer').find('.mask').as('areaMask');
cy.get('@areaMask').should('be.visible');
cy.get('@areaMask').find('.inner').as('inner');
cy.get('@areaMask').find('.controls .close').as('closer');
cy.get('@areaMask').find('.controls .view').as('viewer');
// click close => destroy
cy.get('@closer').click();
cy.get('@areaMaskContainer').should('not.exist');
});
it('unhides content', function() {
// click tool => areaMask renders
cy.get('@toolBtn').click();
cy.get('.test-runner-scope .mask-container').as('areaMaskContainer');
cy.get('@areaMaskContainer').find('.mask').as('areaMask');
cy.get('@areaMask').find('.inner').as('inner');
cy.get('@areaMask').find('.controls .close').as('closer');
cy.get('@areaMask').find('.controls .view').as('viewer');
// look through
cy.get('@viewer').click();
cy.get('@areaMaskContainer').should('have.class', 'previewing');
cy.get('@inner').should('have.css', 'opacity', '0.15');
// un-look through
// the component uses a default delay of 3000ms before restoring the mask
cy.wait(3000);
cy.get('@areaMaskContainer').should('not.have.class', 'previewing');
cy.get('@inner').should('have.css', 'opacity', '1');
// click close => destroy
cy.get('@closer').click();
});
it.skip('is dynamic (drag/resize)', function() {
// open it
cy.get('@toolBtn').click();
cy.get('.test-runner-scope .mask-container').within(() => {
// draggable
cy.get('.dynamic-component-title-bar').dragToPoint({x: 400, y: 250}, 'left');
// using approximate position values because pointer can't get right into corner of title bar
cy.root().invoke('data', 'x').should('be.gt', '385');
cy.root().invoke('data', 'y').should('be.gt', '75');
// resizable
cy.get('.dynamic-component-resize-wrapper').dragToPoint({x: 700, y: 450});
cy.root().invoke('width').should('be.gt', '250');
cy.root().invoke('height').should('be.gt', '100');
});
// close it
cy.get('@toolBtn').click();
});
it('can have multiple instances', function() {
// add multiple instances (max 5)
for (let i = 0; i < 6; i++) {
cy.get('@toolBtn').click();
}
cy.get('.test-runner-scope .mask-container').as('areaMaskContainer');
cy.get('@areaMaskContainer').find('.mask').as('areaMask').should('have.length', 5);
cy.get('@areaMask').find('.controls .close').as('closer');
// clean up
cy.get('@closer').should('have.length', 5);
for (let i = 0; i < 5; i++) {
cy.get('@closer').first().click({ force: true });
}
cy.get('@areaMaskContainer').should('not.exist');
});
});
//Note: the magnifier is tested last, because it duplicates the DOM and can break other tests
describe('Magnifier tool', function() {
before(() => {
cy.resetAndEnterTest();
});
beforeEach(() => {
// Even the toolBtn will be duplicated, when the magnifier is opened!
cy.get('.tools-box-list [data-control=magnify] a').first().as('toolBtn');
});
it('loads', function() {
cy.get('@toolBtn').should('have.length', 1).and('be.visible');
});
it('opens/closes', function() {
// click tool => magnifier renders
cy.get('@toolBtn').click();
cy.get('.runner > .magnifier-container').as('magnifierContainer');
cy.get('@magnifierContainer').find('.magnifier').first().as('magnifier');
cy.get('@magnifier').should('be.visible');
cy.get('@magnifier').find('[data-control="closeMagnifier"]').first().as('closer');
// click tool => hide
cy.get('@toolBtn').click();
cy.get('@magnifier').should('not.be.visible');
// click tool => magnifier visible
cy.get('@toolBtn').click();
cy.get('@magnifier').should('be.visible');
// click close => hide
cy.get('@closer').click();
cy.get('@magnifier').should('not.be.visible');
});
it('zooms in/out', function() {
// click tool => magnifier renders
cy.get('@toolBtn').click();
cy.get('.runner > .magnifier-container').as('magnifierContainer');
cy.get('@magnifierContainer').find('.magnifier').first().as('magnifier');
cy.get('@magnifier').find('.inner').first().as('inner');
cy.get('@magnifier').find(':not(.inner) .control[data-control="zoomIn"]').as('magZoomIn');
cy.get('@magnifier').find(':not(.inner) .control[data-control="zoomOut"]').as('magZoomOut');
// contains inner item
cy.get('@inner').find('.qti-itemBody').should('exist').and('be.visible');
// initial transform scale applied (2x)
cy.get('@inner').should('have.attr', 'style').and('contain', 'scale(2)');
// zoom out (min zoom 2x)
cy.get('@magZoomOut').click();
cy.get('@inner').should('have.attr', 'style').and('contain', 'scale(2)');
// zoom in (scales in 0.5 incrs)
cy.get('@magZoomIn').click();
cy.get('@inner').should('have.attr', 'style').and('contain', 'scale(2.5)');
cy.get('@magZoomIn').click();
cy.get('@inner').should('have.attr', 'style').and('contain', 'scale(3)');
// zoom out
cy.get('@magZoomOut').click();
cy.get('@inner').should('have.attr', 'style').and('contain', 'scale(2.5)');
// zoom in (max zoom 8x)
for (let i = 0; i < 12; i++) {
cy.get('@magZoomIn').click();
}
// now 8.5x
cy.get('@inner').should('have.attr', 'style').and('contain', 'scale(8)');
// close it
cy.get('@toolBtn').click();
});
it.skip('is dynamic (drag/resize)', function() {
// open it
cy.get('@toolBtn').click();
cy.get('.runner > .magnifier-container').within(() => {
// draggable
cy.root().children('.dynamic-component-title-bar').dragToPoint({x: 400, y: 250}, 'left');
// using approximate position values because pointer can't get right into corner of title bar
cy.root().invoke('data', 'x').should('be.gt', '385');
cy.root().invoke('data', 'y').should('be.gt', '75');
// resizable
cy.root().children().children('.dynamic-component-resize-wrapper').dragToPoint({x: 700, y: 450});
cy.root().invoke('width').should('be.gt', '250');
cy.root().invoke('height').should('be.gt', '100');
});
// close it
cy.get('@toolBtn').click();
});
});
});
});