diff --git a/README.md b/README.md index 5290488..1b51151 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,8 @@ The following attributes, set within *contentObjects.json*, configure the defaul **displayTitle** (string): This text is displayed on the menu item. +>**titleAriaLabel** (string): This will be read out by screen readers instead of **title** for the menu button text. Use if **title** contains punctuation or html syntax that shouldn't be read. + **body** (string): Optional text that appears on the menu item. Often used to inform the learner about the menu choice. If no **pageBody** is supplied, this text will also appear as the body text of the page header. **pageBody** (string): Optional text that appears as the body text of the page header. If this text is not provided, the **body** text will be used (if it is supplied). Reference [*adapt-contrib-vanilla/templates/page.hbs*](https://github.com/adaptlearning/adapt-contrib-vanilla/blob/master/templates/page.hbs). diff --git a/example.json b/example.json index 6bd8e4c..ee549bf 100644 --- a/example.json +++ b/example.json @@ -50,6 +50,7 @@ "_isHidden": false, "title": "Welcome to Adapt Learning", "displayTitle": "Welcome to Adapt Learning", + "titleAriaLabel": "", "body": "This page contains a working Adapt page with all core bundled components and plugins working.", "_graphic": { "alt": "", diff --git a/migrations/v7.js b/migrations/v7.js new file mode 100644 index 0000000..c5fc123 --- /dev/null +++ b/migrations/v7.js @@ -0,0 +1,52 @@ +import { describe, whereContent, whereFromPlugin, mutateContent, checkContent, updatePlugin, testStopWhere, testSuccessWhere } from 'adapt-migrations'; +import _ from 'lodash'; + +function getBoxMenus(content) { + return content.filter(({ _type, _component }) => + (_type === 'menu' || _type === 'course') && + (!_component || _component === 'boxMenu')); +} + +describe('Box menu - v@@CURRENT_VERSION to v@@RELEASE_VERSION', async () => { + let boxMenus; + + whereFromPlugin('Box menu - from v@@CURRENT_VERSION', { name: 'adapt-contrib-boxMenu', version: '<@@RELEASE_VERSION' }); + + whereContent('Box menu - where boxMenus', async (content) => { + boxMenus = getBoxMenus(content).filter(({ _boxMenu }) => _boxMenu); + return boxMenus.length; + }); + + mutateContent('Box menu - add titleAriaLabel to contentObjects', async (content) => { + boxMenus.titleAriaLabel = ''; + return true; + }); + + checkContent('Box menu - check titleAriaLabel to contentObjects', async (content) => { + const isValid = boxMenus.every(menu => _.has(menu, 'titleAriaLabel')); + if (!isValid) throw new Error('Box menu - global attribute titleAriaLabel'); + return true; + }); + + updatePlugin('Box menu - update to v@@RELEASE_VERSION', { name: 'adapt-contrib-boxMenu', version: '@@RELEASE_VERSION', framework: '">=5.48.4' }); + + testSuccessWhere('Multiple boxMenu, both course and menu', { + fromPlugins: [{ name: 'adapt-contrib-boxMenu', version: '@@CURRENT_VERSION' }], + content: [ + { _type: 'course', _boxMenu: { _backgroundImage: {} } }, + { _type: 'menu', _boxMenu: { _backgroundImage: {} } }, + { _type: 'menu', _boxMenu: {} } + ] + }); + + testSuccessWhere('boxMenu with course globals', { + fromPlugins: [{ name: 'adapt-contrib-boxMenu', version: '@@CURRENT_VERSION' }], + content: [ + { _type: 'course', _globals: { _menu: { _boxMenu: {} } } } + ] + }); + + testStopWhere('incorrect version', { + fromPlugins: [{ name: 'adapt-contrib-boxMenu', version: '@@RELEASE_VERSION' }] + }); +}); diff --git a/properties.schema b/properties.schema index e94566a..acd87f1 100644 --- a/properties.schema +++ b/properties.schema @@ -289,6 +289,15 @@ "type": "object", "required": false, "properties": { + "titleAriaLabel": { + "type": "string", + "inputType": "Text", + "validators": [], + "title": "Title ARIA label", + "help": "Alternative title text read out by screen readers for the menu button text", + "required": false, + "translatable": true + }, "_renderAsGroup": { "type": "boolean", "required": false, diff --git a/schema/contentobject.schema.json b/schema/contentobject.schema.json index faa8bba..aa067ac 100644 --- a/schema/contentobject.schema.json +++ b/schema/contentobject.schema.json @@ -13,6 +13,15 @@ "title": "Box Menu", "default": {}, "properties": { + "titleAriaLabel": { + "type": "string", + "title": "Title ARIA label", + "description": "Alternative title text read out by screen readers for the menu button text", + "default": "", + "_adapt": { + "translatable": true + } + }, "_renderAsGroup": { "type": "boolean", "title": "Make this a parent group of menu items", diff --git a/templates/boxMenuItem.jsx b/templates/boxMenuItem.jsx index 1af381c..b7c6dc6 100644 --- a/templates/boxMenuItem.jsx +++ b/templates/boxMenuItem.jsx @@ -14,6 +14,7 @@ export default function BoxMenuItem (props) { _isLocked, _isComplete, title, + titleAriaLabel, _isOptional, _nthChild, _totalChild @@ -31,8 +32,9 @@ export default function BoxMenuItem (props) { const locked = _isLocked ? _globals?._accessibility?._ariaLabels?.locked : linkText; const optional = _isOptional ? _globals?._accessibility?._ariaLabels?.optional : ''; const itemCount = compile(_globals?._menu?._boxMenu?.itemCount || '', { _nthChild, _totalChild }); + const itemTitle = titleAriaLabel || title; const ariaLabel = [ - completion, locked, `${title}.`, `${itemCount}.`, `${optional}` + completion, locked, `${itemTitle}.`, `${itemCount}.`, `${optional}` ].filter(Boolean).join(' '); return (