diff --git a/.editorconfig b/.editorconfig
index c2cdfb8a..a39be81e 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -11,6 +11,9 @@ root = true
indent_style = space
indent_size = 2
+[*.json]
+indent_size = 4
+
# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
diff --git a/app/images/Thumbs.db b/app/images/Thumbs.db
deleted file mode 100644
index b33ba1d8..00000000
Binary files a/app/images/Thumbs.db and /dev/null differ
diff --git a/app/images/w3c.png b/app/images/w3c.png
deleted file mode 100644
index 5d73d1e2..00000000
Binary files a/app/images/w3c.png and /dev/null differ
diff --git a/app/images/w3c.svg b/app/images/w3c.svg
new file mode 100644
index 00000000..b26d95e0
--- /dev/null
+++ b/app/images/w3c.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/images/wai.png b/app/images/wai.png
deleted file mode 100644
index 0c6be961..00000000
Binary files a/app/images/wai.png and /dev/null differ
diff --git a/app/images/wai.svg b/app/images/wai.svg
new file mode 100644
index 00000000..c9fabf5e
--- /dev/null
+++ b/app/images/wai.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/index.html b/app/index.html
index e72d30eb..c258f828 100644
--- a/app/index.html
+++ b/app/index.html
@@ -49,12 +49,16 @@
+
+
+
+
@@ -109,6 +113,7 @@
+
diff --git a/app/locale/EN/audit.json b/app/locale/EN/audit.json
index 5bc34475..d18d23ed 100644
--- a/app/locale/EN/audit.json
+++ b/app/locale/EN/audit.json
@@ -1,31 +1,32 @@
{
- "TITLE": "Step 4: Audit the Selected Sample",
- "INTRO": "Record the outcome from evaluating the web pages selected in the previous step. Compare the results between the structured page and randomly selected pages, and if needed, adjust the selected sample in the previous step. More guidance on this step is provided in WCAG-EM Step 4: Audit the Selected Sample. {{spec.text}}
{{ "AUDIT.UNDERSTAND" | translate }} {{spec.number}}
{{ "AUDIT.HOW_TO" | translate }} {{spec.number}}
Note: For each WCAG 2.0 success criteria, you can enter 'Results for the entire sample' and you can enter results for individual web pages. You can choose to enter either or both. To enter individual results, select the web page(s) under 'Sample to Evaluate' (in the left column); then under the specific success criteria, select 'Show web pages to enter individual results'",
- "INTRO_0": "",
- "HD_SAMPLE_SELECT": "Sample to Evaluate",
- "INF_AUDIT_SAMPLE": "This section lists the web pages you selected in the previous step. The web pages that are checked in this section are listed when 'Show web pages to enter individual results' is activated in the 'Success Criteria to Evaluate' section. You can show an individual web page that you are evaluating, or show several web pages at the same time.
The chainlink icon opens the web page in a separate browser window.",
+ "BTN_COLLAPSE_PAGES": "Hide web pages to enter individual results",
"BTN_COMPLETE_SELECTED": "Set selected to complete",
- "BTN_UNCOMPLETE_SELECTED": "Set selected to incomplete",
+ "BTN_EXPAND_PAGES": "Show web pages to enter individual results",
"BTN_OPEN_SELECTED": "Open selected pages",
- "NO_SAMPLE": "No sample available. Create a sample in step 2 and step 3.",
- "TESTED": "Tested",
- "HD_CRITERIA": "Success Criteria to Evaluate",
- "INF_AUDIT_CRITERIA": "This section lists the WCAG 2.0 success criteria. Use the filter to show or hide success criteria of different levels (A, AA and AAA). You can select results as: 'Not checked', 'Passed', 'Failed', 'Not present', and 'Cannot tell'; and you can provide details, comments, or other observations made during evaluation in the accompanying text box.",
- "FILTER": "Filter",
- "PRINCIPLE": "Principle",
- "NOTE": "Note",
"BTN_SHOW_TEXT": "Show criterion text",
- "UNDERSTAND": "Understanding",
+ "BTN_UNCOMPLETE_SELECTED": "Set selected to incomplete",
+ "CLICK_TO_DELETE": "Click to delete",
+ "FILTER": "Show",
+ "FILTER_NEW_IN_WCAG21": "Added in WCAG 2.1",
+ "HD_CRITERIA": "Success Criteria to Evaluate",
+ "HD_SAMPLE_SELECT": "Sample to Evaluate",
"HOW_TO": "How to meet",
- "SAMPLE_FINDINGS": "Results for the entire sample",
- "BTN_EXPAND_PAGES": "Show web pages to enter individual results",
- "BTN_COLLAPSE_PAGES": "Hide web pages to enter individual results",
+ "INF_AUDIT_CRITERIA": "This section lists the WCAG 2 success criteria. Use the filter to show or hide success criteria of different levels (A, AA and AAA). You can select results as: 'Not checked', 'Passed', 'Failed', 'Not present', and 'Cannot tell'; and you can provide details, comments, or other observations made during evaluation in the accompanying text box.",
+ "INF_AUDIT_SAMPLE": "This section lists the web pages you selected in the previous step. The web pages that are checked in this section are listed when 'Show web pages to enter individual results' is activated in the 'Success Criteria to Evaluate' section. You can show an individual web page that you are evaluating, or show several web pages at the same time.
The chainlink icon opens the web page in a separate browser window.",
+ "INTRO_0": "",
+ "INTRO": "Record the outcome from evaluating the web pages selected in the previous step. Compare the results between the structured page and randomly selected pages, and if needed, adjust the selected sample in the previous step. More guidance on this step is provided in WCAG-EM Step 4: Audit the Selected Sample.
Note: For each WCAG 2 success criteria, you can enter 'Results for the entire sample' and you can enter results for individual web pages. You can choose to enter either or both. To enter individual results, select the web page(s) under 'Sample to Evaluate' (in the left column); then under the specific success criteria, select 'Show web pages to enter individual results'",
"LABEL_OUTCOME": "Outcome",
- "UNTESTED": "{{critCount}} untested",
+ "LABEL_PAGE_HANDLE": "Short page name",
"NO_PAGE_SELECTED": "No pages selected under Sample to Evaluate",
+ "NO_SAMPLE": "No sample available. Create a sample in step 2 and step 3.",
+ "NOTE": "Note",
"PLH_ASSERT_DESC": "Observations made during evaluation",
- "CLICK_TO_DELETE": "Click to delete",
- "LABEL_PAGE_HANDLE": "Short page name",
+ "PRINCIPLE": "Principle",
+ "RESULTS_FOR": "Results for",
+ "SAMPLE_FINDINGS": "Results for the entire sample",
"SELECT_ALL": "Select all web pages",
- "RESULTS_FOR": "Results for"
-}
\ No newline at end of file
+ "TESTED": "Tested",
+ "TITLE": "Step 4: Audit the Selected Sample",
+ "UNDERSTAND": "Understanding",
+ "UNTESTED": "{{critCount}} untested"
+}
diff --git a/app/locale/EN/explore.json b/app/locale/EN/explore.json
index e9cc54d5..7e259a1e 100644
--- a/app/locale/EN/explore.json
+++ b/app/locale/EN/explore.json
@@ -2,7 +2,7 @@
"TITLE": "Step 2: Explore the Target Website",
"INTRO": "Explore the website to understand its purpose, functionality, and use. This step helps you determine which web pages to use for the evaluation in the next steps. Identify the web technologies used to provide the website and, if you want, take notes on other aspects of the website. Usually it is best to get input from the website owners and developers for this step. More guidance on this step is provided in WCAG-EM Step 2: Explore the Target Website.",
"HD_RELIEDUP_TECH": "Web Technologies Relied Upon",
- "INF_RELIEDUP_TECH": "Identify the web technologies relied upon according to WCAG 2.0 to provide the website. For more information, see WCAG-EM Step 2.d: Identify Web Technologies Relied Upon.
Note: To add other technologies, select 'Others' and use the 'Web Technology' and 'Specification Address (URL)' field. The 'Specification Address (URL)' field should identify the web technology specification.",
+ "INF_RELIEDUP_TECH": "Identify the web technologies relied upon according to WCAG 2 to provide the website. For more information, see WCAG-EM Step 2.d: Identify Web Technologies Relied Upon.
Note: To add other technologies, select 'Others' and use the 'Web Technology' and 'Specification Address (URL)' field. The 'Specification Address (URL)' field should identify the web technology specification.",
"LABEL_TECH": "Web Technology",
"LABEL_TECH_SPEC": "Specification Address (URL)",
"PLH_TECH": "E.g. HTML5, CSS, DOM",
@@ -16,4 +16,4 @@
"LABEL_VARIETY_PAGE_TYPES": "Variety of web page types",
"INF_VARIETY_PAGE_TYPES": "You can use this field to take notes about the types (as opposed to instances) of web pages that you find on the web site. This includes notes about different styles, layouts, structures, and functionality provided on the website. For more information, see WCAG-EM Step 2.c: Identify the Variety of Web Page Types.
Note: 'Web pages' include different 'web page states'; see definition of web page states.",
"LABEL_OTHER": "Others..."
-}
\ No newline at end of file
+}
diff --git a/app/locale/EN/import.json b/app/locale/EN/import.json
new file mode 100644
index 00000000..db4dc110
--- /dev/null
+++ b/app/locale/EN/import.json
@@ -0,0 +1,5 @@
+{
+ "TITLE": "Import a WCAG EARL report",
+ "INTRO": "If you have used another application to generate a WCAG EARL-report that falls into the scope of this evaluation, you may be able to import these and add them to the evaluation audit result. The file itself should be: JSON-LD parseable, consist of objects in EARL format and evaluation tests should be related to WCAG.",
+ "LABEL_SELECT_FILE": "Select a JSON-LD file"
+}
diff --git a/app/locale/EN/nav.json b/app/locale/EN/nav.json
index a4600736..9f436e74 100644
--- a/app/locale/EN/nav.json
+++ b/app/locale/EN/nav.json
@@ -1,5 +1,7 @@
-{
+{
"MENU": "Menu",
+ "MENU_IMPORT": "Import",
+ "MENU_IMPORT_TITLE": "Start EARL report import wizzard",
"MENU_NEW": "New Report",
"MENU_OPEN": "Open",
"MENU_SAVE": "Save",
@@ -25,4 +27,4 @@
"STEP_REPORT": "Report Findings",
"STEP_VIEWREPORT": "View Report",
"BTN_BACK_TO_EVAL": "Back"
-}
\ No newline at end of file
+}
diff --git a/app/locale/EN/scope.json b/app/locale/EN/scope.json
index b19b7134..dc55b120 100644
--- a/app/locale/EN/scope.json
+++ b/app/locale/EN/scope.json
@@ -9,6 +9,10 @@
"INF_SITE_SCOPE_LI1": "'All web content of the online shop of Example Org. located at http://www.example.org/shop/'",
"INF_SITE_SCOPE_LI2": "'All web content of the mobile version of the public website of Example Org. located at http://m.example.org'",
"INF_SITE_SCOPE_1": "WCAG-EM Step 1.a: Define the Scope of the Website",
+ "LABEL_WCAG_VERSION": "WCAG Version",
+ "INFO_WCAG_VERSION": "Select the WCAG version to use. Version 2.1 (default) or 2.0",
+ "WCAG21": "WCAG 2.1",
+ "WCAG20": "WCAG 2.0",
"LABEL_CONFORMANCE_TGT": "Conformance target",
"INF_CONF_TGT": "Select a target WCAG 2 conformance level ('A', 'AA', or 'AAA') for the evaluation. For more information, see WCAG-EM Step 1.b: Define the Conformance Target. This selection determines which conformance level filters are active by default in 'step 4: Audit the Sample'.",
"LABEL_SUPPORT_BASE": "Accessibility support baseline",
diff --git a/app/locale/EN/start.json b/app/locale/EN/start.json
index 61e3f359..eccc46b9 100644
--- a/app/locale/EN/start.json
+++ b/app/locale/EN/start.json
@@ -2,7 +2,7 @@
"TITLE": "WCAG-EM Report Tool",
"SUBTITLE": "Website Accessibility Evaluation Report Generator",
"INTRO_HD": "What this tool does",
- "INTRO_1": "This tool helps you generate a report according to the Website Accessibility Conformance Evaluation Methodology (WCAG-EM). It does not perform any accessibility checks. It helps you follow the steps of WCAG-EM, to generate a structured report from the input that you provide. It is designed for experienced evaluators who know Web Content Accessibility Guidelines (WCAG) 2.0 and are somewhat familiar with WCAG-EM. For an introduction to WCAG-EM, see the WCAG-EM Overview.",
+ "INTRO_1": "This tool helps you generate a report according to the Website Accessibility Conformance Evaluation Methodology (WCAG-EM). It does not perform any accessibility checks. It helps you follow the steps of WCAG-EM, to generate a structured report from the input that you provide. It is designed for experienced evaluators who know Web Content Accessibility Guidelines (WCAG) 2 and are somewhat familiar with WCAG-EM. For an introduction to WCAG-EM, see the WCAG-EM Overview.",
"INTRO_2": "Note: This tool does not automatically save the information that you enter. To save your data in a file locally on your computer, use Windows shortcut keys Ctrl+S or Mac shortcut keys {{mac}} to open the Save dialog. (Or the 'Save' link at the top of the page will open the Save Evaluation Report page and from there the 'Save data file locally to your computer' link will open the Save dialog.)",
"USAGE_HD": "How this tool works",
"USAGE_LI1": "All functionality provided by this tool is now loaded and running locally in your web browser. You don't need an Internet connection beyond this point. When you close your web browser window, any unsaved data is lost.",
@@ -13,5 +13,5 @@
"TIPS_LI1": "You can go back and forth between the steps in any order. None of the fields are required.",
"TIPS_LI2": "To get more information about a field, select the {{info}} icon next to the field label.",
"TIPS_LI3": "The tool provides your report as HTML and CSS files. You can download these files from the 'View Report' page. You can then change the report content and visual design.",
- "TIPS_LI4": "You can include in your report WCAG 2.0 success criteria beyond the conformance target. For example, the website is only required to meet Level AA, yet you want to also include Level AAA success criteria in the report. In step 4, use the level filter to show higher level criteria. Any criteria with a result will always be included in the report."
-}
\ No newline at end of file
+ "TIPS_LI4": "You can include in your report WCAG 2 success criteria beyond the conformance target. For example, the website is only required to meet Level AA, yet you want to also include Level AAA success criteria in the report. In step 4, use the level filter to show higher level criteria. Any criteria with a result will always be included in the report."
+}
diff --git a/app/scripts/app.js b/app/scripts/app.js
index 24e53aae..99c3c270 100644
--- a/app/scripts/app.js
+++ b/app/scripts/app.js
@@ -11,10 +11,11 @@ angular.module('wcagReporter', [
$compileProvider
.aHrefSanitizationWhitelist(/^\s*(https?|data|blob):/);
- $routeProvider.when('/', {
- templateUrl: 'views/start.html',
- controller: 'StartCtrl'
- })
+ $routeProvider
+ .when('/', {
+ templateUrl: 'views/start.html',
+ controller: 'StartCtrl'
+ })
.when('/evaluation/scope', {
templateUrl: 'views/evaluation/scope.html',
controller: 'EvalScopeCtrl'
@@ -50,6 +51,10 @@ angular.module('wcagReporter', [
.when('/error', {
templateUrl: 'views/error.html'
})
+ .when('/import', {
+ templateUrl: 'views/import.html',
+ controller: 'ImportCtrl'
+ })
.otherwise({
redirectTo: '/error'
});
diff --git a/app/scripts/controllers/evaluation/audit/criteria.js b/app/scripts/controllers/evaluation/audit/criteria.js
index 4881dc4b..53c9e7e0 100644
--- a/app/scripts/controllers/evaluation/audit/criteria.js
+++ b/app/scripts/controllers/evaluation/audit/criteria.js
@@ -13,6 +13,7 @@ angular.module('wcagReporter')
$timeout
) {
var principlesOrigin = [];
+ var activeFilters = [];
$scope.criteria = evalAuditModel.getCriteriaSorted();
@@ -22,10 +23,6 @@ angular.module('wcagReporter')
var tgtPrinciple = origin[target.length];
target.push(tgtPrinciple);
- // tgtPrinciple.guidelines.forEach(function (g) {
- // g.hideCriteria = true;
- // });
-
if (target.length !== origin.length) {
$timeout(function () {
buildPrinciples(target, origin);
@@ -33,6 +30,84 @@ angular.module('wcagReporter')
}
}
+ // Read from critFilter
+ function getActiveFilters () {
+ var filters = $scope.critFilter;
+ var activatedFilters = [];
+
+ for (var filter in filters) {
+ // levels is an object with level key value boolean
+ if (
+ Object.prototype.hasOwnProperty.call($scope.critFilter, filter) &&
+ typeof filters[filter] === 'object'
+ ) {
+ for (var filterOption in filters[filter]) {
+ if (
+ Object.prototype.hasOwnProperty.call(filters[filter], filterOption) &&
+ filters[filter][filterOption] === true
+ ) {
+ activatedFilters.push(filterOption);
+ }
+ }
+ }
+
+ // version is a string; WCAG21, WCAG20 or WCAG20 WCAG21
+ if (
+ Object.prototype.hasOwnProperty.call($scope.critFilter, filter) &&
+ typeof filters[filter] === 'string'
+ ) {
+ filters[filter].split(' ')
+ .forEach(function (filterOption) {
+ activatedFilters.push(filterOption);
+ });
+ }
+ }
+
+ return activatedFilters.slice();
+ }
+
+ function setActiveFilters () {
+ activeFilters = getActiveFilters();
+ }
+
+ function filteredByLevel () {
+ var levelFilter = $scope.critFilter.levels;
+
+ for (var level in levelFilter) {
+ if (
+ Object.prototype.hasOwnProperty.call(levelFilter, level) &&
+ levelFilter[level] === true
+ ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ function criterionMatchFilter (criterion) {
+ var versionActive = (activeFilters.indexOf(criterion.versions[0]) !== -1);
+ var levelActive = (activeFilters.indexOf(criterion.level) !== -1);
+
+ if (
+ versionActive &&
+ levelActive
+ ) {
+ return true;
+ }
+
+ // Version filtering is always on so if no level is filtered
+ // show criteria based on version occurence alone
+ if (
+ versionActive &&
+ !filteredByLevel()
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+
$scope.principles = [];
if (wcag2spec.isLoaded()) {
@@ -46,54 +121,47 @@ angular.module('wcagReporter')
buildPrinciples($scope.principles, principlesOrigin);
});
+ $scope.handleFilterChange = function handleFilterChange () {
+ setActiveFilters();
+ };
+
if ($rootScope.rootHide.criteria) {
- $scope.critFilter = $rootScope.rootHide.criteria;
+ $scope.critFilter = $rootScope.rootHide.criteria;
} else {
- $scope.critFilter = {
- 'wai:WCAG2A-Conformance': evalScopeModel.matchConformTarget('wai:WCAG2A-Conformance'),
- 'wai:WCAG2AA-Conformance': evalScopeModel.matchConformTarget('wai:WCAG2AA-Conformance'),
- 'wai:WCAG2AAA-Conformance': evalScopeModel.matchConformTarget('wai:WCAG2AAA-Conformance')
+ $scope.critFilter = {
+ version: evalScopeModel.wcagVersion === 'WCAG21'
+ ? 'WCAG21 WCAG20'
+ : 'WCAG20',
+ levels: {
+ 'wai:WCAG2A-Conformance': evalScopeModel.matchConformTarget('wai:WCAG2A-Conformance'),
+ 'wai:WCAG2AA-Conformance': evalScopeModel.matchConformTarget('wai:WCAG2AA-Conformance'),
+ 'wai:WCAG2AAA-Conformance': evalScopeModel.matchConformTarget('wai:WCAG2AAA-Conformance')
+ }
};
- $rootScope.rootHide.criteria = $scope.critFilter;
+
+ $rootScope.rootHide.criteria = $scope.critFilter;
}
+ setActiveFilters();
$scope.isCriterionVisible = function (critSpec) {
// Check if the level of this criterion should be shown
- if ($scope.critFilter[critSpec.level] !== true) {
- return false;
- }
-
- // Check if the assert has an outcome, if no, don't show the criterion
- var critAssert = evalAuditModel.getCritAssert(critSpec.id);
- if (typeof critAssert !== 'object' ||
- typeof critAssert.result !== 'object') {
+ if (!criterionMatchFilter(critSpec)) {
return false;
}
- // Check if the outcome is set to hidden
- return true;
+ return true;
};
$scope.isGuidelineVisible = function (guideline) {
- var visible = false;
- guideline.successcriteria.forEach(function (critSpec) {
- // Only check the criterion if a previous check hasn't already returned true
- if (visible || $scope.isCriterionVisible(critSpec)) {
- visible = true;
- }
- });
- return visible;
+ return guideline.successcriteria.some(function (criterion) {
+ return $scope.isCriterionVisible(criterion);
+ });
};
$scope.isPrincipleVisible = function (principle) {
- var visible = false;
- principle.guidelines.forEach(function (guideline) {
- // Only check the criterion if a previous check hasn't already returned true
- if (visible || $scope.isGuidelineVisible(guideline)) {
- visible = true;
- }
+ return principle.guidelines.some(function (guideline) {
+ return $scope.isGuidelineVisible(guideline);
});
- return visible;
};
var untested = [
diff --git a/app/scripts/controllers/evaluation/explore.js b/app/scripts/controllers/evaluation/explore.js
index 20487f12..712365db 100644
--- a/app/scripts/controllers/evaluation/explore.js
+++ b/app/scripts/controllers/evaluation/explore.js
@@ -1,24 +1,31 @@
'use strict';
-angular.module('wcagReporter')
- .controller(
- 'EvalExploreCtrl',
- function ($scope, appState, $timeout, evalExploreModel) {
- $scope.state = appState.moveToState('explore');
- $scope.exploreModel = evalExploreModel;
+angular
+ .module('wcagReporter')
+ .controller('EvalExploreCtrl', function (
+ $scope,
+ appState,
+ $timeout,
+ evalExploreModel
+ ) {
+ $scope.state = appState.moveToState('explore');
+ $scope.exploreModel = evalExploreModel;
- $scope.updateSpec = function (tech) {
- if (techMap[tech.title]) {
- tech.id = techMap[tech.title];
- }
- };
+ $scope.updateSpec = function (tech) {
+ if (techMap[tech.title]) {
+ tech.id = techMap[tech.title];
+ }
+ };
- $scope.knownTech = angular.copy(evalExploreModel.knownTech);
- $scope.otherTech = [];
+ $scope.knownTech = angular.copy(evalExploreModel.knownTech);
+ $scope.otherTech = [];
- // set relied upon technologies in the right field
- evalExploreModel.reliedUponTechnology.forEach(function (tech) {
+ // set relied upon technologies in the right field
+ evalExploreModel.reliedUponTechnology
+ .forEach(function (tech) {
var index = $scope.knownTech
+ // Find exact matching index in knownTech of reliedUponTechnology
+ // it will be an user defined technology otherwise
.reduce(function (index, currTech, currIndex) {
if (currTech.id === tech.id && currTech.title === tech.title) {
return currIndex;
@@ -30,78 +37,81 @@ angular.module('wcagReporter')
if (index !== -1) {
$scope.knownTech[index].checked = true;
} else {
- // Push the tech to the other tech field
+ // Push the tech to the other tech field (it is user defined)
$scope.otherTech.push(tech);
}
});
- // Add an empty field by default
- if ($scope.otherTech.length === 0) {
- $scope.otherTech.push({ type: 'Technology' });
+ // Add an empty field by default
+ if ($scope.otherTech.length === 0) {
+ $scope.otherTech.push({ type: 'Technology' });
+ } else {
+ $scope.rootHide.OtherTech = $scope.rootHide.OtherTech || true;
+ }
+
+ $scope.changeTech = function (tech) {
+ if (tech.checked) {
+ var newTech = angular.extend({}, tech);
+ delete newTech.checked;
+ evalExploreModel.reliedUponTechnology.push(newTech);
} else {
- $scope.rootHide.OtherTech = $scope.rootHide.OtherTech || true;
+ evalExploreModel.reliedUponTechnology = evalExploreModel.reliedUponTechnology
+ .filter(function (item) {
+ return item.title !== tech.title && item.id !== tech.id;
+ });
}
+ };
- $scope.changeTech = function (tech) {
- if (tech.checked) {
- var newTech = angular.extend({}, tech);
- delete newTech.checked;
- evalExploreModel.reliedUponTechnology.push(newTech);
- } else {
- evalExploreModel.reliedUponTechnology = evalExploreModel.reliedUponTechnology
- .filter(function (item) {
- return item.title !== tech.title && item.id !== tech.id;
- });
- }
- };
+ $scope.updateOtherTech = function (tech) {
+ var index = evalExploreModel.reliedUponTechnology.indexOf(tech);
+ var isEmpty = !tech.title && !tech.id;
+ if (index === -1 && !isEmpty) {
+ evalExploreModel.reliedUponTechnology.push(tech);
+ } else if (index !== -1 && isEmpty) {
+ evalExploreModel.reliedUponTechnology.splice(index, 1);
+ }
+ };
- $scope.updateOtherTech = function (tech) {
- var index = evalExploreModel.reliedUponTechnology.indexOf(tech);
- var isEmpty = !tech.title && !tech.id;
- if (index === -1 && !isEmpty) {
- evalExploreModel.reliedUponTechnology.push(tech);
- } else if (index !== -1 && isEmpty) {
- evalExploreModel.reliedUponTechnology.splice(index, 1);
- }
- };
+ $scope.addTechnology = function ($event) {
+ $scope.otherTech.push({ type: 'Technology' });
- $scope.addTechnology = function ($event) {
- $scope.otherTech.push({ type: 'Technology' });
+ // evalExploreModel.addReliedUponTech();
+ if ($event) {
+ var button = angular.element($event.delegateTarget);
- // evalExploreModel.addReliedUponTech();
- if ($event) {
- var button = angular.element($event.delegateTarget);
- $timeout(function () {
- var inputs = button.prev()
- .find('input');
- inputs[inputs.length - 2].select();
- }, 100);
- }
- };
+ $timeout(function () {
+ var inputs = button.prev()
+ .find('input');
- $scope.removeTechnology = function ($index, $event) {
- var tech = $scope.otherTech[$index];
- var index = evalExploreModel.reliedUponTechnology.indexOf(tech);
- evalExploreModel.reliedUponTechnology.splice(index, 1);
- $scope.otherTech.splice($index, 1);
+ if (inputs.length > 0) {
+ inputs[0].select();
+ }
+ });
+ }
+ };
- // evalExploreModel.reliedUponTechnology.splice($index,1);
- // We need this timeout to prevent Angular UI from throwing an error
- if ($event) {
- $timeout(function () {
- angular.element($event.delegateTarget)
- .closest('fieldset')
- .parent()
- .children()
- .last()
- .focus();
- });
- }
- };
+ $scope.removeTechnology = function ($index, $event) {
+ var tech = $scope.otherTech[$index];
+ var index = evalExploreModel.reliedUponTechnology.indexOf(tech);
+ evalExploreModel.reliedUponTechnology.splice(index, 1);
+ $scope.otherTech.splice($index, 1);
- var techMap = {};
- $scope.knownTech.forEach(function (knownTech) {
- techMap[knownTech.title] = knownTech.id;
- });
- }
- );
+ // evalExploreModel.reliedUponTechnology.splice($index,1);
+ // We need this timeout to prevent Angular UI from throwing an error
+ if ($event) {
+ $timeout(function () {
+ angular.element($event.delegateTarget)
+ .closest('fieldset')
+ .parent()
+ .children()
+ .last()
+ .focus();
+ });
+ }
+ };
+
+ var techMap = {};
+ $scope.knownTech.forEach(function (knownTech) {
+ techMap[knownTech.title] = knownTech.id;
+ });
+ });
diff --git a/app/scripts/controllers/evaluation/scope.js b/app/scripts/controllers/evaluation/scope.js
index 8e508e7e..140b17e6 100644
--- a/app/scripts/controllers/evaluation/scope.js
+++ b/app/scripts/controllers/evaluation/scope.js
@@ -13,6 +13,15 @@ angular.module('wcagReporter')
$scope.state = appState.moveToState('scope');
$scope.scopeModel = evalScopeModel;
+ $scope.wcagVersionOptions = evalScopeModel.wcagVersionOptions
+ .reduce(function (versions, version) {
+ var translateKey = 'SCOPE.' + version;
+
+ versions[version] = $filter('translate')(translateKey);
+
+ return versions;
+ }, {});
+
$scope.conformanceOptions = evalScopeModel.conformanceOptions
.reduce(function (tgt, lvl) {
tgt[lvl] = $filter('rdfToLabel')(lvl);
diff --git a/app/scripts/controllers/import.js b/app/scripts/controllers/import.js
new file mode 100644
index 00000000..42bb2788
--- /dev/null
+++ b/app/scripts/controllers/import.js
@@ -0,0 +1,392 @@
+'use strict';
+
+angular
+ .module('wcagReporter')
+ .controller('ImportCtrl', function (
+ fileReader,
+ $scope,
+ $rootScope,
+ evalContextV3,
+ evalModel,
+ types,
+ isObjectLiteral,
+ wcagSpecIdMap
+ ) {
+ var JSONLD = window.jsonld;
+ var FEEDBACK = {
+ ERROR: {
+ type: 'error',
+ class: 'danger'
+ },
+ PENDING: {
+ type: 'pending',
+ class: 'info'
+ },
+ SUCCESS: {
+ type: 'success',
+ class: 'success'
+ }
+ };
+
+ $scope.assertionImport = [];
+
+ $scope.allowedMime = [
+ 'application/json',
+ 'application/ld+json'
+ ].join(',');
+
+ $scope.feedback = false;
+ $scope.importFile = undefined;
+ $scope.importConfirmed = undefined;
+
+ /**
+ * Assertions that get imported need to be validated against
+ * 1. test: should be directly known / related to WCAG
+ * 2. subject should be related to one of the samples
+ * 3. result: being an earl:TestResult
+ * 4. assertedBy: Nice to know who / what made this assertion
+ * @param {earl:Assertion} assertion [description]
+ * @return {boolean} validity
+ */
+ function isValidAssertion (assertion) {
+ function hasRequiredKeys (_assertion) {
+ var assertionKeys = Object.keys(_assertion);
+ var requiredKeys = [
+ 'test',
+ 'subject',
+ 'result',
+ 'assertedBy'
+ ];
+
+ var key;
+
+ for (key in requiredKeys) {
+ if (assertionKeys.indexOf(requiredKeys[key]) === -1) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ function isSampleRelated (subject) {
+ var sampleUrls = evalModel.sampleModel.getPages()
+ .map(function getUrls (page) {
+ var pageUrl;
+
+ if (page.source !== undefined) {
+ try {
+ pageUrl = new URL(page.source);
+ } catch (e) {
+ console.error(e);
+
+ return page.source;
+ }
+
+ return pageUrl.href;
+ }
+ });
+
+ var subjectUrl = '';
+
+ if (typeof subject === 'string') {
+ try {
+ subjectUrl = new URL(subject).href;
+ } catch (e) {
+ console.error('Expected valid url in import assertion subject.');
+
+ return false;
+ }
+ }
+
+ if (
+ isObjectLiteral(subject) &&
+ subject.source !== undefined
+ ) {
+ try {
+ subjectUrl = new URL(subject.source).href;
+ } catch (e) {
+ console.error('Expected valid url in import assertion subject.');
+
+ return false;
+ }
+ }
+
+ return (sampleUrls.indexOf(subjectUrl) >= 0);
+ }
+
+ function isWcagRelated (assertionTest) {
+ if (
+ typeof assertionTest === 'string' &&
+ isWcagId(assertionTest)
+ ) {
+ return true;
+ }
+
+ if (
+ isObjectLiteral(assertionTest) &&
+ assertionTest.id !== undefined &&
+ isWcagId(assertionTest.id)
+ ) {
+ setWcagId(assertion, assertionTest.id);
+ return true;
+ }
+
+ if (
+ isObjectLiteral(assertionTest) &&
+ assertionTest.isPartOf !== undefined &&
+ typeof assertionTest.isPartOf === 'string' &&
+ isWcagId(assertionTest.isPartOf)
+ ) {
+ setWcagId(assertion, assertionTest.isPartOf);
+ return true;
+ }
+
+ return false;
+ }
+
+ function hasResult (_assertion) {
+ var result = _assertion.result;
+
+ function hasOutcomeValue (_result) {
+ var earlOutcome = types.EARL.OUTCOME;
+ var outcomeValues = [
+ earlOutcome.PASSED,
+ earlOutcome.FAILED,
+ earlOutcome.CANT_TELL,
+ earlOutcome.INAPPLICABLE,
+ earlOutcome.UNTESTED
+ ];
+ var outcomeClasses = [
+ earlOutcome.PASS,
+ earlOutcome.FAIL,
+ earlOutcome.CANNOT_TELL,
+ earlOutcome.NOT_APPLICABLE,
+ earlOutcome.NOT_TESTED
+ ];
+
+ if (_result.outcome === undefined) {
+ return false;
+ }
+
+ if (
+ typeof _result.outcome === 'string' &&
+ outcomeValues.indexOf(_result.outcome) >= 0
+ ) {
+ return true;
+ }
+
+ if (
+ isObjectLiteral(_result.outcome) &&
+ _result.outcome['@type'] !== undefined &&
+ outcomeClasses.indexOf(_result.outcome['@type']) >= 0
+ ) {
+ return true;
+ }
+ }
+
+ if (!hasOutcomeValue(result)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ if (!hasRequiredKeys(assertion)) {
+ return false;
+ }
+
+ if (!isSampleRelated(assertion.subject)) {
+ return false;
+ }
+
+ if (!isWcagRelated(assertion.test)) {
+ return false;
+ }
+
+ if (!hasResult(assertion)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ function isWcagId (testId) {
+ var _id = testId.split(':')[1];
+
+ // Find existing wcag id
+ return wcagSpecIdMap.some(function (wcagIdSet) {
+ return wcagIdSet.indexOf(_id) >= 0;
+ });
+ }
+
+ function upgradeWcagId (wcagId) {
+ var _id = wcagId.split(':')[1];
+ var wcagIdSet = wcagSpecIdMap.filter(function (idSet) {
+ return idSet.indexOf(_id) >= 0;
+ })[0];
+ var idCount = wcagIdSet.length;
+
+ return 'WCAG2:' + wcagIdSet[idCount - 1];
+ }
+
+ function setWcagId (assertion, wcagId) {
+ var wcagVersion = wcagId.split(':')[0];
+
+ if (wcagVersion !== 'WCAG2') {
+ wcagId = upgradeWcagId(wcagId);
+ }
+ assertion.wcagId = wcagId;
+ }
+
+ function getWcagId (assertion) {
+ if (typeof assertion.test === 'string') {
+ return assertion.test;
+ }
+
+ return assertion.wcagId || false;
+ }
+
+ /**
+ * Tries to insert all found assertions from the import
+ * into the auditModel specific criteria
+ */
+ function insertAssertions () {
+ var assertions = $scope.assertionImport;
+ var assertionsCount = assertions.length;
+ var assertion, wcagId;
+
+ for (var i = 0; i < assertionsCount; i++) {
+ assertion = assertions[i];
+ wcagId = getWcagId(assertion);
+
+ if (wcagId) {
+ evalModel.auditModel.updateCritAssert(wcagId, assertion);
+ }
+ }
+
+ $scope.feedback = FEEDBACK.SUCCESS;
+ $scope.feedback.message = 'Import successfull! Imported ' + assertionsCount + ' assertions.';
+ }
+
+ function resetImport () {
+ $scope.feedback = false;
+ $scope.importFile = undefined;
+ $scope.importConfirmed = undefined;
+ $scope.assertionImport.length = 0;
+ }
+
+ function handleLoad (defer, feedback) {
+ defer.then(
+ function success (result) {
+ var resultJson = JSON.parse(result);
+ var context = angular.copy(evalContextV3);
+ context.WCAG20 = 'https://www.w3.org/TR/WCAG20/#';
+ context.isPartOf = {
+ '@id': 'dct:isPartOf',
+ '@type': '@id'
+ };
+
+ JSONLD.frame(
+ resultJson,
+ {
+ '@context': context,
+ '@graph': [
+ {
+ '@type': 'Assertion'
+ }
+ ]
+ },
+ function (error, framed) {
+ if (error) {
+ feedback = FEEDBACK.ERROR;
+ feedback.message = error.message;
+ return;
+ }
+
+ var graph = framed['@graph'];
+ var graphSize = graph.length;
+ var currentAssertion;
+
+ for (var i = 0; i < graphSize; i++) {
+ currentAssertion = graph[i];
+
+ if (isValidAssertion(currentAssertion)) {
+ $scope.assertionImport.push(currentAssertion);
+ }
+ }
+
+ if ($scope.assertionImport.length > 0) {
+ $scope.feedback = FEEDBACK.PENDING;
+ $scope.feedback.message = 'Ready to import ' + $scope.assertionImport.length + ' assertions.';
+ } else {
+ $scope.feedback = FEEDBACK.ERROR;
+ $scope.feedback.message = 'No Assertions found in file “' + $scope.importFile.name + '”';
+
+ $scope.importFile = null;
+ }
+
+ $scope.$apply();
+ }
+ );
+ },
+ function error (e) {
+ feedback = FEEDBACK.ERROR;
+ if (e.message) {
+ feedback.message = e.message;
+ } else {
+ feedback.message = e;
+ }
+ }
+ );
+ }
+
+ function isJson (file) {
+ if ($scope.allowedMime.indexOf(file.type) >= 0) {
+ return true;
+ }
+
+ return false;
+ }
+
+ $scope.loadFile = function loadFile (source) {
+ $scope.feedback = FEEDBACK.PENDING;
+
+ if (!isJson(source)) {
+ $scope.feedback = FEEDBACK.ERROR;
+ $scope.feedback.message = 'Expected to open a json-file, the filename must end with either “.json” or “.jsonld”.';
+ $scope.$apply();
+
+ return;
+ }
+
+ $scope.importFile = {
+ name: source.name
+ };
+
+ handleLoad(fileReader.readAsText(source, $scope), $scope.feedback);
+ };
+
+ $scope.handleConfirmation = function handleConfirmation (confirmed) {
+ if (confirmed === undefined) {
+ confirmed = false;
+ }
+
+ if (confirmed) {
+ $scope.feedback = FEEDBACK.PENDING;
+ $scope.feedback.message = 'Inserting ' + $scope.assertionImport.length + ' assertions from “' + $scope.importFile.name + '”';
+
+ insertAssertions();
+
+ $scope.importConfirmed = confirmed;
+ } else {
+ resetImport();
+ $scope.feedback = FEEDBACK.PENDING;
+ $scope.feedback.message = 'Import aborted. Choose another file or go back to the evaluation.';
+ }
+ };
+
+ $scope.handleDoneClick = function handleDoneClick () {
+ $rootScope.setEvalLocation();
+ };
+ });
diff --git a/app/scripts/models/class/CriterionAssert.js b/app/scripts/models/class/CriterionAssert.js
index f1296c2e..bc8e1725 100644
--- a/app/scripts/models/class/CriterionAssert.js
+++ b/app/scripts/models/class/CriterionAssert.js
@@ -2,6 +2,7 @@
angular.module('wcagReporter')
.service('CriterionAssert', function (
+ types,
evalSampleModel,
$filter,
TestCaseAssert,
@@ -19,9 +20,11 @@ angular.module('wcagReporter')
}
this.test = idref;
+ this.mode = types.EARL.MODE.MANUAL;
this.hasPart = [];
this.result = {
- outcome: 'earl:untested',
+ type: types.EARL.RESULT.class,
+ outcome: types.EARL.OUTCOME.UNTESTED,
description: ''
};
@@ -161,12 +164,12 @@ angular.module('wcagReporter')
}, false);
return hasPart || !!critAssert.result.description ||
- critAssert.result.outcome !== 'earl:untested';
+ critAssert.result.outcome !== types.EARL.OUTCOME.UNTESTED;
};
CriterionAssert.updateMetadata = function (critAssert) {
critAssert.assertedBy = currentUser.id;
- critAssert.mode = 'earl:manual';
+ critAssert.mode = types.EARL.MODE.MANUAL;
critAssert.result.date = $filter('date')(Date.now(), 'yyyy-MM-dd HH:mm:ss Z');
};
diff --git a/app/scripts/models/class/TestCaseAssert.js b/app/scripts/models/class/TestCaseAssert.js
index 561d28d8..80e3bf11 100644
--- a/app/scripts/models/class/TestCaseAssert.js
+++ b/app/scripts/models/class/TestCaseAssert.js
@@ -1,10 +1,16 @@
'use strict';
-angular.module('wcagReporter')
- .service('TestCaseAssert', function (evalSampleModel, currentUser) {
+angular
+ .module('wcagReporter')
+ .service('TestCaseAssert', function (
+ types,
+ evalSampleModel,
+ currentUser
+ ) {
var protoResult = {
+ type: types.EARL.RESULT.class,
description: '',
- outcome: 'earl:untested'
+ outcome: types.EARL.OUTCOME.UNTESTED
};
function TestCaseAssert () {
@@ -16,7 +22,7 @@ angular.module('wcagReporter')
}
this.subject = [];
- this.result = Object.create(protoResult);
+ this.result = angular.copy(protoResult);
}
TestCaseAssert.isDefined = function (tc) {
@@ -34,7 +40,7 @@ angular.module('wcagReporter')
testCase: undefined,
result: undefined,
multiPage: false,
- mode: 'earl:manual',
+ mode: types.EARL.MODE.MANUAL,
isDefined: function () {
return TestCaseAssert.isDefined(this);
},
diff --git a/app/scripts/models/evaluation.js b/app/scripts/models/evaluation.js
index 2aa49d68..bd31cf5b 100644
--- a/app/scripts/models/evaluation.js
+++ b/app/scripts/models/evaluation.js
@@ -1,22 +1,20 @@
'use strict';
-/**
- *
- */
-angular.module('wcagReporter')
+angular
+ .module('wcagReporter')
.factory('evalModel', function (
evalScopeModel,
evalExploreModel,
evalSampleModel,
evalAuditModel,
evalReportModel,
- evalContextV2,
+ evalContextV3,
currentUser
) {
var evalModel = {
id: undefined,
type: 'Evaluation',
- context: evalContextV2,
+ context: evalContextV3,
scopeModel: evalScopeModel,
exploreModel: evalExploreModel,
sampleModel: evalSampleModel,
diff --git a/app/scripts/models/evaluation/audit.js b/app/scripts/models/evaluation/audit.js
index 823949d4..5331925d 100644
--- a/app/scripts/models/evaluation/audit.js
+++ b/app/scripts/models/evaluation/audit.js
@@ -5,7 +5,9 @@ angular.module('wcagReporter')
TestCaseAssert,
evalScopeModel,
wcag2spec,
- CriterionAssert
+ CriterionAssert,
+ types,
+ $filter
) {
var auditModel;
var criteria = {};
@@ -21,6 +23,38 @@ angular.module('wcagReporter')
});
});
+ function updateAssertion (assertion, update) {
+ var testResult = update.result;
+
+ function composeImportResult (result) {
+ var composed = '\n\n';
+ composed += '*Imported finding*';
+ composed += '\noutcome: ' + $filter('rdfToLabel')(result.outcome);
+ if (result.description) {
+ composed += '\n' + result.description;
+ }
+
+ return composed;
+ }
+
+ assertion.result.description += composeImportResult(testResult);
+
+ // Remove empty lines at start of description
+ assertion.result.description = assertion.result.description.replace(/^\s+/, '');
+
+ // Decide what outcome should be set.
+ // Set Failed if imported result is Failed
+ // This forces the evaluator to check the import and this is the only outcome
+ // that can be set with certainty by automatic assertors.
+ if (
+ // Dont try to modify if it already has failed outcome
+ assertion.result.outcome !== types.EARL.OUTCOME.FAILED &&
+ testResult.outcome === types.EARL.OUTCOME.FAILED
+ ) {
+ assertion.result.outcome = types.EARL.OUTCOME.FAILED;
+ }
+ }
+
auditModel = {
criteria: criteria,
@@ -61,8 +95,9 @@ angular.module('wcagReporter')
if (!angular.isArray(evalData.auditResult)) {
evalData.auditResult = [evalData.auditResult];
}
- criteria = {};
- auditModel.criteria = criteria;
+ // NOTE: Why was this done? (Reset criteria to imported criteria)
+ // criteria = {};
+ // auditModel.criteria = criteria;
evalData.auditResult.forEach(auditModel.addCritAssert);
}
@@ -105,6 +140,21 @@ angular.module('wcagReporter')
criteria[newCrit.test] = newCrit;
},
+ updateCritAssert: function updateCritAssert (id, data) {
+ if (data === undefined) {
+ return;
+ } else if (typeof data !== 'object') {
+ return;
+ }
+
+ // First try to get a matching criteria before anything else
+ var criterion = auditModel.getCritAssert(id);
+
+ if (data.result) {
+ updateAssertion(criterion, data);
+ }
+ },
+
addPageForAsserts: function (page) {
Object.keys(criteria)
.forEach(function (critName) {
diff --git a/app/scripts/models/evaluation/explore.js b/app/scripts/models/evaluation/explore.js
index 6bc405f0..5fe9be05 100644
--- a/app/scripts/models/evaluation/explore.js
+++ b/app/scripts/models/evaluation/explore.js
@@ -1,22 +1,27 @@
'use strict';
-angular.module('wcagReporter')
- .service('evalExploreModel', function (knownTech, evalSampleModel) {
+angular
+ .module('wcagReporter')
+ .service('evalExploreModel', function (
+ knownTech,
+ evalSampleModel
+ ) {
var exploreModel = {
- commonPages: [],
- otherRelevantPages: [],
knownTech: knownTech
};
var basicProps = [
'reliedUponTechnology',
'essentialFunctionality',
- 'pageTypeVariety'
+ 'pageTypeVariety',
+ 'commonPages',
+ 'otherRelevantPages'
];
// add all properties to this
- basicProps.forEach(function (prop) {
- exploreModel[prop] = undefined;
- });
+ basicProps
+ .forEach(function (prop) {
+ exploreModel[prop] = undefined;
+ });
exploreModel.reliedUponTechnology = [];
@@ -24,19 +29,23 @@ angular.module('wcagReporter')
if (!angular.isArray(evalData.reliedUponTechnology)) {
evalData.reliedUponTechnology = [evalData.reliedUponTechnology];
}
- basicProps.forEach(function (prop) {
- if (evalData[prop]) {
- exploreModel[prop] = evalData[prop];
- }
- });
+
+ basicProps
+ .forEach(function (prop) {
+ if (evalData[prop]) {
+ exploreModel[prop] = evalData[prop];
+ }
+ });
};
exploreModel.exportData = function () {
var exportData = {};
- basicProps.forEach(function (prop) {
- exportData[prop] = exploreModel[prop];
- });
+ basicProps
+ .forEach(function (prop) {
+ exportData[prop] = exploreModel[prop];
+ });
+
return exportData;
};
diff --git a/app/scripts/models/evaluation/scope.js b/app/scripts/models/evaluation/scope.js
index bd20aa5e..1b8ac5a4 100644
--- a/app/scripts/models/evaluation/scope.js
+++ b/app/scripts/models/evaluation/scope.js
@@ -4,7 +4,8 @@ angular.module('wcagReporter')
.service('evalScopeModel', function () {
var scopeModel = {
type: 'EvaluationScope',
- conformanceTarget: 'wai:WCAG2AA-Conformance',
+ wcagVersion: 'WCAG21',
+ conformanceTarget: 'wai:WCAG2AA-Conformance',
additionalEvalRequirement: '',
website: {
type: [
@@ -33,6 +34,11 @@ angular.module('wcagReporter')
};
};
+ scopeModel.wcagVersionOptions = [
+ 'WCAG21',
+ 'WCAG20'
+ ];
+
scopeModel.conformanceOptions = [
'wai:WCAG2A-Conformance',
'wai:WCAG2AA-Conformance',
diff --git a/app/scripts/models/import.js b/app/scripts/models/import.js
index d0e47d58..034d7053 100644
--- a/app/scripts/models/import.js
+++ b/app/scripts/models/import.js
@@ -1,178 +1,222 @@
'use strict';
-/**
- *
- */
-angular.module('wcagReporter')
- .factory(
- 'wcagReporterImport',
- function ($rootScope, evalModel, currentUser, reportStorage, importV1, changeLanguage) {
- var jsonld = window.jsonld;
-
- function objectCollide (obj1, obj2) {
- Object.keys(obj1)
- .forEach(function (prop) {
- if (typeof obj1[prop] !== 'function' &&
- typeof obj2[prop] !== 'undefined') {
- obj1[prop] = obj2[prop];
- }
- });
- }
-
- function compactEach (callback) {
- var testCallback;
- var results = [];
- var calls = 0;
- var evalType = evalModel.context['@vocab'] + evalModel.type;
- var personType = currentUser['@context']['@vocab'] + currentUser.type;
-
- testCallback = function (err, compacted) {
- results.push(compacted);
- if (results.length === calls) {
- callback(results);
- }
- };
-
- return function (evalObj) {
- calls += 1;
-
- if (evalObj['@type'] &&
- evalObj['@type'].indexOf(evalType) !== -1) {
- // Compact with the evaluation context
- jsonld.compact(
- evalObj,
- evalModel.context,
- testCallback
- );
- } else if (evalObj['@type'] &&
- evalObj['@type'].indexOf(personType) !== -1) {
- // Compact with the FOAF context
- jsonld.compact(
- evalObj,
- currentUser['@context'],
- testCallback
- );
- } else {
- results.push(evalObj);
+angular
+ .module('wcagReporter')
+ .factory('wcagReporterImport', function (
+ $rootScope,
+ evalModel,
+ currentUser,
+ reportStorage,
+ importV1,
+ changeLanguage
+ ) {
+ var jsonld = window.jsonld;
+
+ /**
+ * OBJECT MODIFIER
+ * Add to or replace object 1's keys with object 2's keys
+ * @param {Object} obj1 Object that needs to be updated
+ * @param {Object} obj2 Object with keys that need to replace or be added to obj1
+ * @return {undefined} Modifies object 1 with object 2 keys
+ */
+ function objectCollide (obj1, obj2) {
+ Object.keys(obj1)
+ .forEach(function (prop) {
+ if (
+ typeof obj1[prop] !== 'function' &&
+ typeof obj2[prop] !== 'undefined'
+ ) {
+ obj1[prop] = obj2[prop];
}
- };
+ });
+ }
+
+ function compactEach (callback) {
+ var results = [];
+ var calls = 0;
+ var evalType = evalModel.context['@vocab'] + evalModel.type;
+ var personType = currentUser['@context']['@vocab'] + currentUser.type;
+
+ function testCallback (err, compacted) {
+ if (err) {
+ // Something json-ldish is not ok here, exit.
+ // This should not be the case anytime since the data should have
+ // been checked before importing.
+ console.error(err);
+ return;
+ }
+
+ results.push(compacted);
+
+ if (results.length === calls) {
+ callback(results);
+ }
}
- /**
+ return function (evalObj) {
+ calls += 1;
+
+ if (
+ evalObj['@type'] &&
+ evalObj['@type'].indexOf(evalType) !== -1
+ ) {
+ // Compact with the evaluation context
+ jsonld.compact(
+ evalObj,
+ evalModel.context,
+ testCallback
+ );
+ } else if (
+ evalObj['@type'] &&
+ evalObj['@type'].indexOf(personType) !== -1
+ ) {
+ // Compact with the FOAF context
+ jsonld.compact(
+ evalObj,
+ currentUser['@context'],
+ testCallback
+ );
+ } else {
+ results.push(evalObj);
+ }
+ };
+ }
+
+ /**
* Inject evaluation data into the reporter
* @param {[Object]} evalData
*/
- function updateEvalModel (evalData) {
- if (evalData.evaluationScope) {
- objectCollide(evalModel.scopeModel, evalData.evaluationScope);
- }
+ function updateEvalModel (evalData) {
+ if (evalData.evaluationScope) {
+ objectCollide(evalModel.scopeModel, evalData.evaluationScope);
+ }
- evalModel.id = evalData.id;
- evalModel.type = evalData.type;
+ evalModel.id = evalData.id;
+ evalModel.type = evalData.type;
- evalModel.sampleModel.importData(evalData);
+ evalModel.sampleModel.importData(evalData);
+ evalModel.reportModel.importData(evalData);
+ evalModel.auditModel.importData(evalData);
+ evalModel.exploreModel.importData(evalData);
+ evalModel.otherData = evalData.otherData;
+ }
- evalModel.reportModel.importData(evalData);
+ var importModel = {
- evalModel.auditModel.importData(evalData);
- evalModel.exploreModel.importData(evalData);
- evalModel.otherData = evalData.otherData;
- }
+ storage: reportStorage,
- var importModel = {
-
- storage: reportStorage,
-
- /**
- * Import an evaluation from a JSON string
- * @param {string} json Evaluation
- */
- fromJson: function (json) {
- importModel.fromObject(angular.fromJson(json));
- },
-
- getFromUrl: function () {
- return reportStorage.get()
- .then(function (data) {
- importModel.fromJson(data);
- return data;
- });
- },
-
- fromObject: function (evalData) {
- // Check if an old format needs to be converted:
- if (angular.isArray(evalData['@graph']) &&
- typeof evalData['@graph'][0] === 'object' &&
- evalData['@graph'][0].type.toLowerCase() === 'evaluation') {
- // Fix an older import format
- evalData['@graph'] = importV1(evalData['@graph']);
- }
- jsonld.expand(evalData, function (err, expanded) {
- if (err) {
- console.error(err);
- }
- importModel.fromExpanded(expanded);
+ /**
+ * Import an evaluation from a JSON string
+ * @param {string} json Evaluation
+ * @return {undefined}
+ */
+ fromJson: function (json) {
+ importModel.fromObject(angular.fromJson(json));
+ },
+
+ getFromUrl: function () {
+ return reportStorage.get()
+ .then(function (data) {
+ importModel.fromJson(data);
+ return data;
});
- },
-
- fromExpanded: function (evalData) {
- evalData.forEach(compactEach(function (results) {
- var evaluation = results.reduce(function (result, data) {
- if (data.type === 'Evaluation') {
- if (typeof result !== 'undefined') {
- throw new Error('Only one evaluation object allowed in JSON data');
- }
- return data;
- }
- return result;
- }, undefined);
+ },
+
+ fromObject: function (evalData) {
+ // Check if an old format needs to be converted:
+ var graphData = evalData['@graph'] || null;
+
+ if (
+ angular.isArray(graphData) &&
+ !importV1.isLatestVersion(graphData)
+ ) {
+ // Fix an older import format
+ evalData['@graph'] = importV1(graphData);
+ }
- if (!evaluation) {
- throw new Error('No evaluation found in data');
- }
+ jsonld.expand(evalData, function (err, expanded) {
+ if (err) {
+ console.error(err);
+ }
- // If the creator has an id, give that id to the current user
- if (typeof evaluation.creator === 'string' &&
- evaluation.creator.indexOf('_:') === 0) {
- currentUser.id = evaluation.creator;
- }
- evaluation.creator = currentUser;
- var foundUser = false;
- // Find the first Person that matches the ID of the current user
- results.forEach(function (data) {
- if (!foundUser && data.type === 'Person' &&
- data.id === currentUser.id) {
- // overwrite the current user with the new data
- angular.extend(currentUser, data);
- foundUser = true;
+ importModel.fromExpanded(expanded);
+ });
+ },
+
+ fromExpanded: function (evalData) {
+ evalData
+ .forEach(
+ compactEach(function (results) {
+ var evaluation = results
+ .reduce(function (result, data) {
+ if (data.type === 'Evaluation') {
+ if (typeof result !== 'undefined') {
+ throw new Error('Only one evaluation object allowed in JSON data');
+ }
+
+ return data;
+ }
+
+ return result;
+ }, undefined);
+
+ if (!evaluation) {
+ throw new Error('No evaluation found in data');
}
- });
- // Take all data that isn't the evaluation or the current user
- evaluation.otherData = results.reduce(function (otherData, data) {
- if (data !== evaluation && data.id !== currentUser.id) {
- otherData.push(data);
+ // If the creator has an id, give that id to the current user
+ if (typeof evaluation.creator === 'string' &&
+ evaluation.creator.indexOf('_:') === 0) {
+ currentUser.id = evaluation.creator;
}
- return otherData;
- }, [currentUser]);
-
- if (evaluation.lang) {
- // This is a workaround for what seems to be a bug in the
- // JSON-LD lib. It outputs ['e', 'n'] instead of 'en', so we
- // join to fix this.
- if (angular.isArray(evaluation.lang)) {
- evaluation.lang = evaluation.lang.join('');
+
+ evaluation.creator = currentUser;
+
+ var foundUser = false;
+
+ // Find the first Person that matches the ID of the current user
+ results
+ .forEach(function (data) {
+ if (
+ !foundUser &&
+ data.type === 'Person' &&
+ data.id === currentUser.id
+ ) {
+ // overwrite the current user with the new data
+ angular.extend(currentUser, data);
+ foundUser = true;
+ }
+ });
+
+ // Take all data that isn't the evaluation or the current user
+ evaluation.otherData = results
+ .reduce(function (otherData, data) {
+ if (data !== evaluation && data.id !== currentUser.id) {
+ otherData.push(data);
+ }
+
+ return otherData;
+ }, [currentUser]);
+
+ if (evaluation.lang) {
+ // This is a workaround for what seems to be a bug in the
+ // JSON-LD lib. It outputs ['e', 'n'] instead of 'en', so we
+ // join to fix this.
+ if (angular.isArray(evaluation.lang)) {
+ evaluation.lang = evaluation.lang.join('');
+ }
+
+ changeLanguage(evaluation.lang);
}
- changeLanguage(evaluation.lang);
- }
- // Put the evaluation as the first on the list
- $rootScope.$apply(function () {
- updateEvalModel(evaluation);
- });
- }));
- }
- };
- return importModel;
- }
- );
+ // Put the evaluation as the first on the list
+ $rootScope.$apply(function () {
+ updateEvalModel(evaluation);
+ });
+ })
+ );
+ }
+ };
+
+ return importModel;
+ });
diff --git a/app/scripts/models/import/importV1.js b/app/scripts/models/import/importV1.js
index c1af0a9c..25214c1a 100644
--- a/app/scripts/models/import/importV1.js
+++ b/app/scripts/models/import/importV1.js
@@ -1,23 +1,68 @@
'use strict';
-angular.module('wcagReporter')
- .factory('importV1', function (evalContextV1, evalContextV2, $filter) {
- var getUrl = $filter('getUrl');
+/**
+ * ImportV1; imports and migrates jsonld data
+ * TODO: Use JSONLD API
+ */
+angular
+ .module('wcagReporter')
+ .factory('importV1', function (
+ types,
+ wcagSpecIdMap,
+ evalContextV1,
+ evalContextV2,
+ evalContextV3,
+ $filter
+ ) {
+ var getUrl = $filter('getUrl');
+ var isLatestVersion = isV3Evaluation;
+ /**
+ * Converts json-ld @graph contents
+ * @param {Array} '@graph'-contents
+ * @return {Array} new updated '@graph'-contents
+ */
function convertor (importArray) {
- return importArray.map(function (importObj) {
- // upgrade from v1 to v2
- if (isV1Evaluation(importObj)) {
- importObj = upgradeToV2(importObj);
+ return importArray
+ .map(function (importObj) {
+ // upgrade from v1 to v2
+ if (isV1Evaluation(importObj)) {
+ importObj = upgradeToV2(importObj);
+ }
+
+ if (isV2Evaluation(importObj)) {
+ importObj = upgradeToV3(importObj);
+ }
// Correct the foaf namespace
- } else if (typeof importObj === 'object' &&
- typeof importObj['@context'] === 'object' &&
- importObj['@context']['@vocab'] === 'http://xmlns.com/foaf/spec/#') {
- importObj['@context']['@vocab'] = 'http://xmlns.com/foaf/0.1/';
- }
- return importObj;
- });
+ if (
+ typeof importObj === 'object' &&
+ typeof importObj['@context'] === 'object' &&
+ importObj['@context']['@vocab'] === 'http://xmlns.com/foaf/spec/#'
+ ) {
+ importObj['@context']['@vocab'] = 'http://xmlns.com/foaf/0.1/';
+ }
+
+ return importObj;
+ });
+ }
+
+ /**
+ * Updates the test WCAG ID to latest version.
+ * The test ID exist of 2 parts: [WCAG2, {{'HTML_REPORT.HD_SPECIFICS' | translate }}
{{'HTML_REPORT.HD_DOCS' | translate }}
-
-
\ No newline at end of file
+
diff --git a/app/views/directives/successCriterion.html b/app/views/directives/successCriterion.html
index 7802de3f..f4475c42 100644
--- a/app/views/directives/successCriterion.html
+++ b/app/views/directives/successCriterion.html
@@ -12,7 +12,7 @@
{{'AUDIT.BTN_SHOW_TEXT' | translate}}
-
+
+
Overview: www.w3.org/WAI/intro/wcag
- www.w3.org/WAI/WCAG20/quickref/
+
+ www.w3.org/WAI/WCAG21/quickref/
+ WCAG Evaluation Methodology (WCAG-EM)
Overview: www.w3.org/WAI/eval/conformance
{{ setTitle( translate('ERROR.TITLE') ) }}