diff --git a/accessibilityTests/helper.js b/accessibilityTests/helper.js index 2cd4e86739..d04bb52072 100644 --- a/accessibilityTests/helper.js +++ b/accessibilityTests/helper.js @@ -80,220 +80,5 @@ export const initSurvey = ClientFunction( } ); -export const registerCustomToolboxComponent = ClientFunction( - (framework, json, events, isDesignMode, props) => { - if (framework === "knockout") { - window["ko"].components.register("svc-custom-action", { - viewModel: { - createViewModel: (params) => { - return params.item; - }, - }, - template: - '', - }); - } else if (framework === "react") { - class CustomActionButton extends window["React"].Component { - click = () => { - this.props.item.action(); - }; - render() { - return ( - // eslint-disable-next-line react/react-in-jsx-scope - - {" "} - {this.props.item.title} - - ); - } - } - - window["Survey"].ReactElementFactory.Instance.registerElement( - "svc-custom-action", - (props) => { - return window["React"].createElement(CustomActionButton, props); - } - ); - } else if (framework === "vue") { - window["Vue"].component("svc-custom-action", { - props: { - item: {}, - }, - template: - '{{ item.title }}', - }); - } - } -); - -export const registerCustomItemComponent = ClientFunction( - (framework, json, events, isDesignMode, props) => { - if (framework === "knockout") { - window["ko"].components.register("new-item", { - viewModel: { - createViewModel: function (params, componentInfo) { - const item = params.item; - item.iconName = "icon-defaultfile"; - item.hint = item.title + " - Description"; - return item; - }, - }, - template: - '
', - }); - } else if (framework === "react") { - class ItemTemplateComponent extends window["React"].Component { - render() { - const item = this.props.item; - var Survey = window["Survey"]; - item.iconName = "icon-defaultfile"; - item.hint = item.title + " - Description"; - - /* eslint-disable */ - return ( -
- {" "} - - {" "} - {" "} - {" "} - {item.title}{" "} -
- ); - /* eslint-enable */ - } - } - window["Survey"].ReactElementFactory.Instance.registerElement( - "new-item", - (props) => { - return window["React"].createElement(ItemTemplateComponent, props); - } - ); - } else if (framework === "vue") { - window["Vue"].component("new-item", { - props: { - item: {}, - }, - created: function () { - const item = this.item; - item.iconName = "icon-defaultfile"; - item.hint = item.title + " - Description"; - }, - template: - '
{{ item.title }}
', - }); - } - } -); - -export const getSurveyResult = ClientFunction(() => { - var result = window["SurveyResult"]; - if (typeof result === "undefined") { - return result; - } - //clean result object from the vuejs stuff - return JSON.parse(JSON.stringify(result)); -}); - -export const getData = ClientFunction(() => { - return window["survey"].data; -}); - -export const setData = ClientFunction((newData) => { - window["survey"].data = newData; - window["survey"].render(); -}); - -export const setOptions = ClientFunction((questionName, modValue) => { - const mergeOptions = function (obj1, obj2) { - for (const attrname in obj2) { - obj1[attrname] = obj2[attrname]; - } - }; - const q = window["survey"].getQuestionByName(questionName || "car"); - mergeOptions(q, modValue); - // window["survey"].render(); -}); - -export const joinElementInnerText = ClientFunction((tagName, index) => { - const el = document.getElementsByTagName(tagName)[index]; - const spans = el.querySelectorAll("span"); - let res = ""; - for (let i = 0; i < spans.length; i++) { - const sp = spans[i]; - if (!sp.innerHTML || sp.innerHTML == " ") continue; - const childs = sp.getElementsByTagName("span"); - if (childs.length > 0) continue; - if (!!res) res += " "; - res += sp.innerHTML; - } - return res; -}); - -export const getQuestionValue = ClientFunction(() => { - return window["survey"].getAllQuestions()[0].value; -}); - -export const getQuestionJson = ClientFunction(() => { - return JSON.stringify(window["survey"].getAllQuestions()[0].toJSON()); -}); - -export const getPanelJson = ClientFunction(() => { - return JSON.stringify(window["survey"].getAllPanels()[0].toJSON()); -}); - -export function getDynamicPanelRemoveButton(questionTitle, buttonText) { - return Selector("span") - .withText(`${questionTitle}`) - .parent("[aria-labelledby]") - .find("span") - .withText(buttonText); -} - -export async function checkSurveyWithEmptyQuestion(t) { - const requiredMessage = - Selector(".sv-string-viewer").withText("Response required."); - - await t - .expect(requiredMessage.exists) - .notOk() - .click(completeButton) - .expect(requiredMessage.visible) - .ok(); - - let surveyResult = await getSurveyResult(); - await t.expect(typeof surveyResult).eql("undefined"); -} - -export function getListItemByText(text) { - return Selector(".sv-popup__content .sv-list .sv-list__item") - .withText(text) - .filterVisible(); -} -export var completeButton = Selector(".sv_complete_btn"); - -export const explicitErrorHandler = ClientFunction(() => { - window.addEventListener("error", e => { - if (e.message === "ResizeObserver loop completed with undelivered notifications." || - e.message === "ResizeObserver loop limit exceeded") { - e.stopImmediatePropagation(); - } - }); -}); -export function filterIsInViewport(node) { - const rect = node.getBoundingClientRect(); - - return ( - rect.top >= 0 && - rect.left >= 0 && - rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && - rect.right <= (window.innerWidth || document.documentElement.clientWidth) - ); -} +// https://www.deque.com/axe/core-documentation/api-documentation/#overview +export const axeTags = ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "best-practice", "section508", "wcag412"]; diff --git a/accessibilityTests/matrices.ts b/accessibilityTests/matrices.ts new file mode 100644 index 0000000000..961570a636 --- /dev/null +++ b/accessibilityTests/matrices.ts @@ -0,0 +1,284 @@ +import { frameworks, url, initSurvey, axeTags } from "./helper"; +import { fixture, test } from "testcafe"; +import { axeCheck, createReport } from "axe-testcafe"; +const title = "matrices"; + +const json = { + "elements": [ + { + type: "matrix", + name: "Quality", + title: "Matrix", + columns: [ + { + value: 1, + text: "Strongly Disagree" + }, + { + value: 2, + text: "Disagree" + }, + { + value: 3, + text: "Neutral" + }, + { + value: 4, + text: "Agree" + }, + { + value: 5, + text: "Strongly Agree" + } + ], + rows: [ + { + value: "affordable", + text: "Product is affordable" + }, + { + value: "does what it claims", + text: "Product does what it claims" + }, + { + value: "better than others", + text: "Product is better than other products on the market" + }, + { + value: "easy to use", + text: "Product is easy to use" + } + ] + }, + { + type: "matrix Dropdown", + name: "frameworksRate", + title: "Matrixdropdown", + choices: ["Excelent", "Good", "Average", "Fair", "Poor"], + columns: [ + { + name: "using", + title: "Do you use it?", + choices: ["Yes", "No"], + cellType: "radiogroup" + }, + { + name: "experience", + title: "How long do you use it?", + choices: [ + { + value: 5, + text: "3-5 years" + }, + { + value: 2, + text: "1-2 years" + }, + { + value: 1, + text: "less than a year" + } + ] + }, + { + name: "strength", + title: "What is main strength?", + choices: ["Easy", "Compact", "Fast", "Powerfull"], + cellType: "checkbox" + }, + { + name: "knowledge", + title: "Please describe your experience", + cellType: "text" + }, + { + name: "rate", + title: "Please rate the framework itself" + } + ], + rows: [ + { + value: "angularv1", + text: "angularjs v1.x" + }, + { + value: "angularv2", + text: "angularjs v2" + }, + { + value: "knockoutjs" + }, + { + value: "reactjs" + } + ] + }, + { + type: "matrixdynamic", + name: "teachersRate", + title: "Matrix Dynamic", + addRowText: "Add Subject", + horizontalScroll: true, + columnMinWidth: "130px", + columnColCount: 1, + cellType: "radiogroup", + choices: [ + { + value: 1, + text: "Yes" + }, + { + value: 0, + text: "Sometimes" + }, + { + value: -1, + text: "No" + } + ], + columns: [ + { + name: "subject", + cellType: "dropdown", + title: "Select a subject", + isRequired: true, + minWidth: "300px", + choices: [ + "English: American Literature", + "English: British and World Literature", + "Math: Consumer Math", + "Math: Practical Math", + "Math: Developmental Algebra", + "Math: Continuing Algebra", + "Math: Pre-Algebra", + "Math: Algebra", + "Math: Geometry", + "Math: Integrated Mathematics", + "Science: Physical Science", + "Science: Earth Science", + "Science: Biology", + "Science: Chemistry", + "History: World History", + "History: Modern World Studies", + "History: U.S. History", + "History: Modern U.S. History", + "Social Sciences: U.S. Government and Politics", + "Social Sciences: U.S. and Global Economics", + "World Languages: Spanish", + "World Languages: French", + "World Languages: German", + "World Languages: Latin", + "World Languages: Chinese", + "World Languages: Japanese" + ] + }, + { + name: "explains", + title: "Clearly explains the objectives" + }, + { + name: "interesting", + title: "Makes class interesting" + }, + { + "name": "Column 2", + "cellType": "boolean" + }, + { + name: "effective", + title: "Uses class time effectively" + }, + { + name: "knowledge", + title: "Knows the subject matter" + }, + { + name: "recognition", + title: "Recognizes and acknowledges effort" + }, + { + name: "inform", + title: "Keeps me informed of my progress" + }, + { + name: "opinion", + title: "Encourages and accepts different opinions" + }, + { + name: "respect", + title: "Has the respect of the student" + }, + { + name: "cooperation", + title: "Encourages cooperation and participation" + }, + { + name: "parents", + title: "Communicates with my parents" + }, + { + name: "selfthinking", + title: "Encourages me to think for myself" + }, + { + name: "frusturation", + cellType: "comment", + title: "Is there anything about this class that frustrates you?", + minWidth: "250px" + }, + { + name: "likeTheBest", + cellType: "comment", + title: "What do you like best about this class and/or teacher?", + minWidth: "250px" + }, + { + name: "improvements", + cellType: "comment", + title: + "What do you wish this teacher would do differently that would improve this class?", + minWidth: "250px" + } + ], + rowCount: 2 + }, + ] +}; + +frameworks.forEach((framework) => { + fixture`${framework} a11y:${title}`.page`${url}${framework}`.beforeEach( + async (t) => { + await initSurvey(framework, json); + } + ); + + test("axe check", async (t) => { + const axeContext = { include: [[".sv_p_root"]] }; + const axeOptions = { + runOnly: { + type: "tag", + values: axeTags + }, + rules: { + //https://github.com/dequelabs/axe-core/blob/develop/doc/rule-descriptions.md + "color-contrast": { + enabled: false + }, + "document-title": { + enabled: false + }, + "landmark-one-main": { + enabled: false + }, + "page-has-heading-one": { + enabled: false + }, + "region": { + enabled: false + } + } + }; + const { error, violations } = await axeCheck(t, axeContext, axeOptions); + await t.expect(violations.length === 0).ok(createReport(violations)); + }); +}); \ No newline at end of file diff --git a/accessibilityTests/questions.ts b/accessibilityTests/questions.ts new file mode 100644 index 0000000000..1c84811e0d --- /dev/null +++ b/accessibilityTests/questions.ts @@ -0,0 +1,70 @@ +import { frameworks, url, initSurvey, axeTags } from "./helper"; +import { fixture, test } from "testcafe"; +import { axeCheck, createReport } from "axe-testcafe"; +const title = "others"; + +const json = { + "elements": [ + { + type: "boolean", + name: "bool", + title: "Boolean", + label: "Are you 21 or older?", + }, + { + type: "image", + name: "banner", + imageHeight: "300px", + imageWidth: "450px", + imageLink: + "https://surveyjs.io/Content/Images/examples/image-picker/lion.jpg" + }, + { + type: "file", + title: "File", + name: "image", + storeDataAsText: false, + showPreview: true, + imageWidth: 150, + maxSize: 102400 + }, + ] +}; + +frameworks.forEach((framework) => { + fixture`${framework} a11y:${title}`.page`${url}${framework}`.beforeEach( + async (t) => { + await initSurvey(framework, json); + } + ); + + test("axe check", async (t) => { + const axeContext = { include: [[".sv_p_root"]] }; + const axeOptions = { + runOnly: { + type: "tag", + values: axeTags + }, + rules: { + //https://github.com/dequelabs/axe-core/blob/develop/doc/rule-descriptions.md + "color-contrast": { + enabled: false + }, + "document-title": { + enabled: false + }, + "landmark-one-main": { + enabled: false + }, + "page-has-heading-one": { + enabled: false + }, + "region": { + enabled: false + } + } + }; + const { error, violations } = await axeCheck(t, axeContext, axeOptions); + await t.expect(violations.length === 0).ok(createReport(violations)); + }); +}); \ No newline at end of file diff --git a/accessibilityTests/ranks.ts b/accessibilityTests/ranks.ts new file mode 100644 index 0000000000..68ce2c6d86 --- /dev/null +++ b/accessibilityTests/ranks.ts @@ -0,0 +1,68 @@ +import { frameworks, url, initSurvey, axeTags } from "./helper"; +import { fixture, test } from "testcafe"; +import { axeCheck, createReport } from "axe-testcafe"; +const title = "ranks"; + +const json = { + "elements": [ + { + type: "rating", + name: "satisfaction", + title: "Rating", + minRateDescription: "Not Satisfied", + maxRateDescription: "Completely satisfied" + }, + { + type: "ranking", + name: "smartphone-features", + title: "Please rank the following smartphone features in order of importance:", + choices: [ + "Battery life", + "Screen size", + "Storage space", + "Camera quality", + "Durability", + "Processor power", + "Price", + ], + }, + ] +}; + +frameworks.forEach((framework) => { + fixture`${framework} a11y:${title}`.page`${url}${framework}`.beforeEach( + async (t) => { + await initSurvey(framework, json); + } + ); + + test("axe check", async (t) => { + const axeContext = { include: [[".sv_p_root"]] }; + const axeOptions = { + runOnly: { + type: "tag", + values: axeTags + }, + rules: { + //https://github.com/dequelabs/axe-core/blob/develop/doc/rule-descriptions.md + "color-contrast": { + enabled: false + }, + "document-title": { + enabled: false + }, + "landmark-one-main": { + enabled: false + }, + "page-has-heading-one": { + enabled: false + }, + "region": { + enabled: false + } + } + }; + const { error, violations } = await axeCheck(t, axeContext, axeOptions); + await t.expect(violations.length === 0).ok(createReport(violations)); + }); +}); \ No newline at end of file diff --git a/accessibilityTests/selectbase.ts b/accessibilityTests/selectbase.ts new file mode 100644 index 0000000000..1411dd4025 --- /dev/null +++ b/accessibilityTests/selectbase.ts @@ -0,0 +1,175 @@ +import { frameworks, url, initSurvey, axeTags } from "./helper"; +import { fixture, test } from "testcafe"; +import { axeCheck, createReport } from "axe-testcafe"; +const title = "selectbase"; + +const json = { + "elements": [ + { + type: "dropdown", + name: "cars", + title: "Dropdown", + isRequired: true, + showNoneItem: true, + colCount: 4, + choices: [ + "Ford", + "Vauxhall", + "Volkswagen", + "Nissan", + "Audi", + "Mercedes-Benz", + "BMW", + "Peugeot", + "Toyota", + "Citroen" + ] + }, + { + type: "tagbox", + name: "cars2", + title: "Tagbox", + showNoneItem: true, + choices: [ + "Ford", + "Vauxhall", + "Volkswagen", + "Nissan", + "Audi", + "Mercedes-Benz", + "BMW", + "Peugeot", + "Toyota", + "Citroen" + ] + }, + { + type: "checkbox", + name: "cars3", + title: "Checkbox", + isRequired: true, + showSelectAllItem: true, + showNoneItem: true, + colCount: 4, + choices: [ + "Ford", + "Vauxhall", + "Volkswagen", + "Nissan", + "Audi", + "Mercedes-Benz", + "BMW", + "Peugeot", + "Toyota", + "Citroen" + ] + }, + { + type: "radiogroup", + name: "cars4", + title: "Radiogroup", + isRequired: true, + colCount: 4, + choices: [ + "None", + "Ford", + "Vauxhall", + "Volkswagen", + "Nissan", + "Audi", + "Mercedes-Benz", + "BMW", + "Peugeot", + "Toyota", + "Citroen" + ] + }, + { + type: "imagepicker", + name: "choosepicture", + title: "Imagepicker", + imageHeight: "150px", + imageWidth: "225px", + choices: [ + { + value: "lion", + imageLink: + "https://surveyjs.io/Content/Images/examples/image-picker/lion.jpg" + }, + { + value: "giraffe", + imageLink: + "https://surveyjs.io/Content/Images/examples/image-picker/giraffe.jpg" + }, + { + value: "panda", + imageLink: + "https://surveyjs.io/Content/Images/examples/image-picker/panda.jpg" + }, + { + value: "camel", + imageLink: + "https://surveyjs.io/Content/Images/examples/image-picker/camel.jpg" + } + ] + }, + { + type: "imagepicker", + name: "choosevideo", + title: "Imagepicker", + imageHeight: "300px", + imageWidth: "450px", + "contentMode": "video", + choices: [ + { + value: "short_but_high", + imageLink: + "https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4" + }, + { + value: "long_but_poor", + imageLink: + "https://sample-videos.com/video123/mp4/240/big_buck_bunny_240p_1mb.mp4" + } + ] + }, + ] +}; + +frameworks.forEach((framework) => { + fixture`${framework} a11y:${title}`.page`${url}${framework}`.beforeEach( + async (t) => { + await initSurvey(framework, json); + } + ); + + test("axe check", async (t) => { + const axeContext = { include: [[".sv_p_root"]] }; + const axeOptions = { + runOnly: { + type: "tag", + values: axeTags + }, + rules: { + //https://github.com/dequelabs/axe-core/blob/develop/doc/rule-descriptions.md + "color-contrast": { + enabled: false + }, + "document-title": { + enabled: false + }, + "landmark-one-main": { + enabled: false + }, + "page-has-heading-one": { + enabled: false + }, + "region": { + enabled: false + } + } + }; + const { error, violations } = await axeCheck(t, axeContext, axeOptions); + await t.expect(violations.length === 0).ok(createReport(violations)); + }); +}); \ No newline at end of file diff --git a/accessibilityTests/textbase.ts b/accessibilityTests/textbase.ts index 2cbca172d2..47ca2ac48b 100644 --- a/accessibilityTests/textbase.ts +++ b/accessibilityTests/textbase.ts @@ -1,4 +1,4 @@ -import { frameworks, url, initSurvey } from "./helper"; +import { frameworks, url, initSurvey, axeTags } from "./helper"; import { fixture, test } from "testcafe"; import { axeCheck, createReport } from "axe-testcafe"; const title = "textbase"; @@ -39,6 +39,11 @@ const json = { "title": "Title 2", "inputType": "email" }, + { + type: "comment", + name: "suggestions", + title: "Comment" + }, ] } ] @@ -56,7 +61,7 @@ frameworks.forEach((framework) => { const axeOptions = { runOnly: { type: "tag", - values: ["wcag21a", "wcag21aa"/*, 'wcag412'*/] + values: axeTags }, rules: { //https://github.com/dequelabs/axe-core/blob/develop/doc/rule-descriptions.md