From f7796700a75885e81f819e5700ee008454e46902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 11 Nov 2022 12:27:03 +0100 Subject: [PATCH 1/8] MOBILE-4065 a11y: Fix placeholder color --- src/theme/theme.base.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/theme/theme.base.scss b/src/theme/theme.base.scss index 1d3f41565..eaf80569e 100644 --- a/src/theme/theme.base.scss +++ b/src/theme/theme.base.scss @@ -1564,6 +1564,11 @@ ion-modal:focus-visible { box-shadow: none; } +input { + --placeholder-color: var(--ion-placeholder-color); + --placeholder-opacity: .85; +} + ion-input .native-input { &:focus, &:focus-visible { box-shadow: none; From 8b8719559375ad784ee16be23bbb9fb579e565c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 11 Nov 2022 13:22:22 +0100 Subject: [PATCH 2/8] MOBILE-4065 a11y: Change approach to add aria-label to eye button --- src/core/components/show-password/core-show-password.html | 2 +- src/core/components/show-password/show-password.ts | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/core/components/show-password/core-show-password.html b/src/core/components/show-password/core-show-password.html index bd1589bb6..5a260c94a 100644 --- a/src/core/components/show-password/core-show-password.html +++ b/src/core/components/show-password/core-show-password.html @@ -1,5 +1,5 @@ - diff --git a/src/core/components/show-password/show-password.ts b/src/core/components/show-password/show-password.ts index 22faec88f..2ab247d28 100644 --- a/src/core/components/show-password/show-password.ts +++ b/src/core/components/show-password/show-password.ts @@ -45,7 +45,6 @@ export class CoreShowPasswordComponent implements OnInit, AfterViewInit { @ContentChild(IonInput) ionInput?: IonInput; shown = false; // Whether the password is shown. - label = ''; // Label for the button to show/hide. protected input?: HTMLInputElement; // Input affected. protected element: HTMLElement; // Current element. @@ -97,7 +96,6 @@ export class CoreShowPasswordComponent implements OnInit, AfterViewInit { * @param input The input element. */ protected setData(input: HTMLInputElement): void { - this.label = this.shown ? 'core.hide' : 'core.show'; input.type = this.shown ? 'text' : 'password'; } From 824a804887ef498178a741694fcc487802e67692 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 11 Nov 2022 13:33:30 +0100 Subject: [PATCH 3/8] MOBILE-4065 a11y: Improve my courses view aria labels --- scripts/langindex.json | 4 ++-- .../components/myoverview/addon-block-myoverview.html | 4 ++-- src/addons/block/myoverview/lang.json | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/langindex.json b/scripts/langindex.json index d99604aae..9964a93ae 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -40,14 +40,14 @@ "addon.block_learningplans.pluginname": "block_lp", "addon.block_myoverview.all": "block_myoverview", "addon.block_myoverview.allincludinghidden": "block_myoverview", + "addon.block_myoverview.aria:card": "block_myoverview", + "addon.block_myoverview.aria:list": "block_myoverview", "addon.block_myoverview.browseallcourses": "local_moodlemobileapp", - "addon.block_myoverview.card": "block_myoverview", "addon.block_myoverview.favourites": "block_myoverview", "addon.block_myoverview.future": "block_myoverview", "addon.block_myoverview.hiddencourses": "block_myoverview", "addon.block_myoverview.inprogress": "block_myoverview", "addon.block_myoverview.lastaccessed": "block_myoverview", - "addon.block_myoverview.list": "block_myoverview", "addon.block_myoverview.nocoursesenrolled": "local_moodlemobileapp", "addon.block_myoverview.nocoursesenrolleddescription": "local_moodlemobileapp", "addon.block_myoverview.noresult": "local_moodlemobileapp", diff --git a/src/addons/block/myoverview/components/myoverview/addon-block-myoverview.html b/src/addons/block/myoverview/components/myoverview/addon-block-myoverview.html index 233d107de..cce9ec632 100644 --- a/src/addons/block/myoverview/components/myoverview/addon-block-myoverview.html +++ b/src/addons/block/myoverview/components/myoverview/addon-block-myoverview.html @@ -89,11 +89,11 @@ + [attr.aria-label]="'addon.block_myoverview.aria:list' | translate"> + [attr.aria-label]="'addon.block_myoverview.aria:card' | translate"> diff --git a/src/addons/block/myoverview/lang.json b/src/addons/block/myoverview/lang.json index 5c381252e..3738950f0 100644 --- a/src/addons/block/myoverview/lang.json +++ b/src/addons/block/myoverview/lang.json @@ -1,14 +1,14 @@ { "all": "All", "allincludinghidden": "All (including removed from view)", + "aria:card": "Switch to card view", + "aria:list": "Switch to list view", "browseallcourses": "Browse all courses", - "card": "Card", "favourites": "Starred", "future": "Future", "hiddencourses": "Removed from view", "inprogress": "In progress", "lastaccessed": "Last accessed", - "list": "List", "nocoursesenrolled": "You're not enrolled in any courses yet.", "nocoursesenrolleddescription": "Browse all available courses below and start learning.", "noresult": "Your search didn't match any courses.", From 622f6ee2651ffb0762be219cccf656a9e7c0c5f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 11 Nov 2022 14:27:14 +0100 Subject: [PATCH 4/8] MOBILE-4065 a11y: Get keyboard access to course index accordions --- .../features/course/components/course-index/course-index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/features/course/components/course-index/course-index.html b/src/core/features/course/components/course-index/course-index.html index 7fca7f10e..dd97d4628 100644 --- a/src/core/features/course/components/course-index/course-index.html +++ b/src/core/features/course/components/course-index/course-index.html @@ -29,10 +29,10 @@ [class.item-current]="selectedId === section.id" [class.item-dimmed]="!section.visible" [class.item-hightlighted]="section.highlighted" detail="false" sticky="true"> + [class.expandable-status-icon-expanded]="section.expanded" tabindex="0"> From 6500f3546298173f6abda1ee1427adbe298015c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 11 Nov 2022 14:36:47 +0100 Subject: [PATCH 5/8] MOBILE-4065 a11y: Change module name tag to div --- .../course/components/module-info/core-course-module-info.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/features/course/components/module-info/core-course-module-info.html b/src/core/features/course/components/module-info/core-course-module-info.html index 1e00b23b0..af4f10c7c 100644 --- a/src/core/features/course/components/module-info/core-course-module-info.html +++ b/src/core/features/course/components/module-info/core-course-module-info.html @@ -2,7 +2,7 @@ -

{{moduleNameTranslated}}

+
{{moduleNameTranslated}}

From c97d92fd041f046d7559da8ac9f3cc3dcb4abe61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 11 Nov 2022 14:44:54 +0100 Subject: [PATCH 6/8] MOBILE-4065 a11y: Add h2 to sections on course summary --- .../course/pages/course-summary/course-summary.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/features/course/pages/course-summary/course-summary.html b/src/core/features/course/pages/course-summary/course-summary.html index 56e89e0fa..994a79a98 100644 --- a/src/core/features/course/pages/course-summary/course-summary.html +++ b/src/core/features/course/pages/course-summary/course-summary.html @@ -71,9 +71,9 @@ -

+

{{'core.course.coursesummary' | translate}} -

+

@@ -86,9 +86,9 @@ class="expandable-status-icon" [class.expandable-status-icon-expanded]="contactsExpanded"> -

+

{{ 'core.teachers' | translate }} -

+

From 1a8c4fde04526667b941ed7da2474c7eee26ee4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Mon, 14 Nov 2022 14:18:25 +0100 Subject: [PATCH 7/8] MOBILE-4065 a11y: Course downloads does not stop click propagation --- .../pages/course-storage/course-storage.html | 14 ++++----- .../pages/course-storage/course-storage.ts | 30 ++++++++++++++++--- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/addons/storagemanager/pages/course-storage/course-storage.html b/src/addons/storagemanager/pages/course-storage/course-storage.html index 8d2a1b7b3..8ec983aab 100644 --- a/src/addons/storagemanager/pages/course-storage/course-storage.html +++ b/src/addons/storagemanager/pages/course-storage/course-storage.html @@ -29,13 +29,13 @@ {{ 'core.calculating' | translate }} - + {{ prefetchCourseData.statusTranslatable | translate }} - @@ -90,8 +90,8 @@ {{section.count}} / {{section.total}} - + @@ -128,9 +128,9 @@ + (action)="prefetchModule(module)"> - diff --git a/src/addons/storagemanager/pages/course-storage/course-storage.ts b/src/addons/storagemanager/pages/course-storage/course-storage.ts index 3c3d001ef..5e7a143d5 100644 --- a/src/addons/storagemanager/pages/course-storage/course-storage.ts +++ b/src/addons/storagemanager/pages/course-storage/course-storage.ts @@ -352,8 +352,13 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { * The user has requested a delete for the whole course data. * * (This works by deleting data for each module on the course that has data.) + * + * @param event Event object. */ - async deleteForCourse(): Promise { + async deleteForCourse(event: Event): Promise { + event.stopPropagation(); + event.preventDefault(); + try { await CoreDomUtils.showDeleteConfirm( 'addon.storagemanager.confirmdeletedatafrom', @@ -384,9 +389,13 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { * * (This works by deleting data for each module in the section that has data.) * + * @param event Event object. * @param section Section object with information about section and modules */ - async deleteForSection(section: AddonStorageManagerCourseSection): Promise { + async deleteForSection(event: Event, section: AddonStorageManagerCourseSection): Promise { + event.stopPropagation(); + event.preventDefault(); + try { await CoreDomUtils.showDeleteConfirm( 'addon.storagemanager.confirmdeletedatafrom', @@ -413,10 +422,18 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { /** * The user has requested a delete for a module's data * + * @param event Event object. * @param module Module details * @param section Section the module belongs to. */ - async deleteForModule(module: AddonStorageManagerModule, section: AddonStorageManagerCourseSection): Promise { + async deleteForModule( + event: Event, + module: AddonStorageManagerModule, + section: AddonStorageManagerCourseSection, + ): Promise { + event.stopPropagation(); + event.preventDefault(); + if (module.totalSize === 0) { return; } @@ -635,8 +652,13 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { /** * Prefetch the whole course. + * + * @param event Event object. */ - async prefetchCourse(): Promise { + async prefetchCourse(event: Event): Promise { + event.stopPropagation(); + event.preventDefault(); + const courses = await CoreCourses.getUserCourses(true); let course = courses.find((course) => course.id == this.courseId); if (!course) { From 8f518d1b79a66b4ccb5df387d251be1fcc9253c7 Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Mon, 14 Nov 2022 14:53:14 +0100 Subject: [PATCH 8/8] MOBILE-4065 tests: Fix translate mock --- src/testing/utils.ts | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/testing/utils.ts b/src/testing/utils.ts index 18acf31a3..0072cb595 100644 --- a/src/testing/utils.ts +++ b/src/testing/utils.ts @@ -12,10 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { AbstractType, Component, CUSTOM_ELEMENTS_SCHEMA, Type, ViewChild } from '@angular/core'; +import { AbstractType, Component, CUSTOM_ELEMENTS_SCHEMA, EventEmitter, Type, ViewChild } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { Observable, Subject } from 'rxjs'; +import { Observable, of, Subject } from 'rxjs'; import { sep } from 'path'; import { CORE_SITE_SCHEMAS } from '@services/sites'; @@ -33,7 +33,7 @@ import { TranslateService, TranslateStore } from '@ngx-translate/core'; import { CoreIonLoadingElement } from '@classes/ion-loading'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { DefaultUrlSerializer, UrlSerializer } from '@angular/router'; -import { CoreUtils } from '@services/utils/utils'; +import { CoreUtils, CoreUtilsProvider } from '@services/utils/utils'; abstract class WrapperComponent { @@ -45,8 +45,14 @@ type ServiceInjectionToken = AbstractType | Type | string; let testBedInitialized = false; const textUtils = new CoreTextUtilsProvider(); -const DEFAULT_SERVICE_SINGLETON_MOCKS: [CoreSingletonProxy, Record][] = [ - [Translate, mock({ instant: key => key })], +const DEFAULT_SERVICE_SINGLETON_MOCKS: [CoreSingletonProxy, unknown][] = [ + [Translate, mock({ + instant: key => key, + get: key => of(key), + onTranslationChange: new EventEmitter(), + onLangChange: new EventEmitter(), + onDefaultLangChange: new EventEmitter(), + })], [CoreDB, mock({ getDB: () => mock() })], [CoreNavigator, mock({ navigateToSitePath: () => Promise.resolve(true) })], [ApplicationInit, mock({ @@ -66,7 +72,7 @@ const DEFAULT_SERVICE_SINGLETON_MOCKS: [CoreSingletonProxy, Record Promise.resolve(mock({ dismiss: jest.fn() })), })], - [CoreUtils, mock({ + [CoreUtils, mock(new CoreUtilsProvider(), { nextTick: () => Promise.resolve(), })], ]; @@ -273,8 +279,17 @@ export function mockSingleton( const methods = Array.isArray(methodsOrProperties) ? methodsOrProperties : []; const instance = getServiceInstance(singleton.injectionToken) as T; const mockInstance = mock(instance, methods); + const mockInstancePrototype = Object.getPrototypeOf(mockInstance); - Object.assign(mockInstance as Record, properties); + for (const [name, value] of Object.entries(properties)) { + const descriptor = Object.getOwnPropertyDescriptor(mockInstancePrototype, name); + + if (descriptor && !descriptor.writable) { + continue; + } + + mockInstance[name] = value; + } singleton.setInstance(mockInstance);