Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accessibility settings page #3613

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8f708d0
119602: Add AccessibilitySettingsService
AAwouters Oct 22, 2024
b72ce73
119602: Add Accessibility Settings page
AAwouters Oct 22, 2024
d224a2c
119602: Integrate accessibility settings into live-region
AAwouters Oct 22, 2024
6a49df5
119602: Integrate accessibility settings into notifications-board
AAwouters Oct 25, 2024
cad086c
119602: Add AccessibilitySettingsService stub & fix live-region test
AAwouters Oct 25, 2024
52eabec
119602: Add AccessibilitySettingsService tests
AAwouters Oct 25, 2024
82fd953
119602: Add AccessibilitySettingsComponent tests
AAwouters Nov 5, 2024
37455a8
119602: Make AccessibilitySettings cookie expiration configurable
AAwouters Nov 5, 2024
0451559
119602: Add link to AccessibilitySettings on profile page
AAwouters Nov 5, 2024
ed0fcd9
Merge branch 'accessibility-settings-7.6' into accessibility-settings…
AAwouters Nov 6, 2024
bb7f0cd
119602: Improve profile page link accessibility
AAwouters Nov 7, 2024
fe90d39
119602: Add placeholder in form
AAwouters Nov 18, 2024
ecb00a9
119602: Implement converting of accessibility settings values
AAwouters Nov 18, 2024
fdfa6e2
119602: Update values after converters implementation
AAwouters Nov 18, 2024
dc8a699
119602: Rework convertion & form format
AAwouters Nov 22, 2024
b16cec6
119602: Add automatic notification hiding toggle
AAwouters Nov 22, 2024
cae1394
119602: Allow resetting accessibility settings
AAwouters Nov 22, 2024
287d028
119602: Make input boxes smaller
AAwouters Nov 22, 2024
9ac92e0
119602: Add unit after input fields
AAwouters Nov 22, 2024
5a28e66
119602: Move hints to contextHelp
AAwouters Nov 25, 2024
ced163a
119602: Remove obsolete tests
AAwouters Nov 25, 2024
ec016e8
119602: Update AccessibilitySettingsService stub
AAwouters Nov 25, 2024
deb4a63
119602: Add additional accessibilitySettingsComponent tests
AAwouters Nov 25, 2024
c38352e
119602: Update doc comments
AAwouters Nov 25, 2024
cdec488
119602: Compare notifications by id to correctly update store
AAwouters Nov 29, 2024
010b2f9
119602: Improve types & docs
AAwouters Dec 11, 2024
c71c666
119602: Improve notification hiding toggle useability
AAwouters Dec 11, 2024
ff7c9ba
Merge branch 'accessibility-settings-7.6' into accessibility-settings…
AAwouters Dec 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions config/config.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -516,3 +516,8 @@ liveRegion:
messageTimeOutDurationMs: 30000
# The visibility of the live region. Setting this to true is only useful for debugging purposes.
isVisible: false

# Configuration for storing accessibility settings, used by the AccessibilitySettingsService
accessibility:
# The duration in days after which the accessibility settings cookie expires
cookieExpirationDuration: 7
11 changes: 11 additions & 0 deletions src/app/accessibility/accessibility-settings.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Config } from '../../config/config.interface';

/**
* Configuration interface used by the AccessibilitySettingsService
*/
export class AccessibilitySettingsConfig implements Config {
/**
* The duration in days after which the accessibility settings cookie expires
*/
cookieExpirationDuration: number;
}
366 changes: 366 additions & 0 deletions src/app/accessibility/accessibility-settings.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,366 @@
import {
fakeAsync,
flush,
} from '@angular/core/testing';
import { of } from 'rxjs';

import { AuthService } from '../core/auth/auth.service';
import { EPersonDataService } from '../core/eperson/eperson-data.service';
import { EPerson } from '../core/eperson/models/eperson.model';
import { CookieService } from '../core/services/cookie.service';
import { CookieServiceMock } from '../shared/mocks/cookie.service.mock';
import {
createFailedRemoteDataObject$,
createSuccessfulRemoteDataObject$,
} from '../shared/remote-data.utils';
import { AuthServiceStub } from '../shared/testing/auth-service.stub';
import {
ACCESSIBILITY_COOKIE,
ACCESSIBILITY_SETTINGS_METADATA_KEY,
AccessibilitySettings,
AccessibilitySettingsService,
} from './accessibility-settings.service';


describe('accessibilitySettingsService', () => {
let service: AccessibilitySettingsService;
let cookieService: CookieServiceMock;
let authService: AuthServiceStub;
let ePersonService: EPersonDataService;

beforeEach(() => {
cookieService = new CookieServiceMock();
authService = new AuthServiceStub();

ePersonService = jasmine.createSpyObj('ePersonService', {
createPatchFromCache: of([{
op: 'add',
value: null,
}]),
patch: of({}),
});

service = new AccessibilitySettingsService(
cookieService as unknown as CookieService,
authService as unknown as AuthService,
ePersonService,
);
});

describe('get', () => {
it('should return the setting if it is set', () => {
const settings: AccessibilitySettings = {
notificationTimeOut: '1000',
};

service.getAll = jasmine.createSpy('getAll').and.returnValue(of(settings));

service.get('notificationTimeOut', 'default').subscribe(value =>
expect(value).toEqual('1000'),
);
});

it('should return the default value if the setting is not set', () => {
const settings: AccessibilitySettings = {
notificationTimeOut: '1000',
};

service.getAll = jasmine.createSpy('getAll').and.returnValue(of(settings));

service.get('liveRegionTimeOut', 'default').subscribe(value =>
expect(value).toEqual('default'),
);
});
});

describe('getAsNumber', () => {
it('should return the setting as number if the value for the setting can be parsed to a number', () => {
service.get = jasmine.createSpy('get').and.returnValue(of('1000'));

service.getAsNumber('notificationTimeOut').subscribe(value =>
expect(value).toEqual(1000),
);
});

it('should return the default value if no value is set for the setting', () => {
service.get = jasmine.createSpy('get').and.returnValue(of(null));

service.getAsNumber('notificationTimeOut', 123).subscribe(value =>
expect(value).toEqual(123),
);
});

it('should return the default value if the value for the setting can not be parsed to a number', () => {
service.get = jasmine.createSpy('get').and.returnValue(of('text'));

service.getAsNumber('notificationTimeOut', 123).subscribe(value =>
expect(value).toEqual(123),
);
});
});

describe('getAll', () => {
it('should attempt to get the settings from metadata first', () => {
service.getAllSettingsFromAuthenticatedUserMetadata =
jasmine.createSpy('getAllSettingsFromAuthenticatedUserMetadata').and.returnValue(of({ }));

service.getAll().subscribe();
expect(service.getAllSettingsFromAuthenticatedUserMetadata).toHaveBeenCalled();
});

it('should attempt to get the settings from the cookie if the settings from metadata are empty', () => {
service.getAllSettingsFromAuthenticatedUserMetadata =
jasmine.createSpy('getAllSettingsFromAuthenticatedUserMetadata').and.returnValue(of({ }));

service.getAllSettingsFromCookie = jasmine.createSpy('getAllSettingsFromCookie').and.returnValue({ });

service.getAll().subscribe();
expect(service.getAllSettingsFromCookie).toHaveBeenCalled();
});

it('should not attempt to get the settings from the cookie if the settings from metadata are not empty', () => {
const settings: AccessibilitySettings = {
notificationTimeOut: '1000',
};

service.getAllSettingsFromAuthenticatedUserMetadata =
jasmine.createSpy('getAllSettingsFromAuthenticatedUserMetadata').and.returnValue(of(settings));

service.getAllSettingsFromCookie = jasmine.createSpy('getAllSettingsFromCookie').and.returnValue({ });

service.getAll().subscribe();
expect(service.getAllSettingsFromCookie).not.toHaveBeenCalled();
});

it('should return an empty object if both are empty', () => {
service.getAllSettingsFromAuthenticatedUserMetadata =
jasmine.createSpy('getAllSettingsFromAuthenticatedUserMetadata').and.returnValue(of({ }));

service.getAllSettingsFromCookie = jasmine.createSpy('getAllSettingsFromCookie').and.returnValue({ });

service.getAll().subscribe(value => expect(value).toEqual({}));
});
});

describe('getAllSettingsFromCookie', () => {
it('should retrieve the settings from the cookie', () => {
cookieService.get = jasmine.createSpy();

service.getAllSettingsFromCookie();
expect(cookieService.get).toHaveBeenCalledWith(ACCESSIBILITY_COOKIE);
});
});

describe('getAllSettingsFromAuthenticatedUserMetadata', () => {
it('should retrieve all settings from the user\'s metadata', () => {
const settings = { 'liveRegionTimeOut': '1000' };

const user = new EPerson();
user.setMetadata(ACCESSIBILITY_SETTINGS_METADATA_KEY, null, JSON.stringify(settings));

authService.getAuthenticatedUserFromStoreIfAuthenticated =
jasmine.createSpy('getAuthenticatedUserFromStoreIfAuthenticated').and.returnValue(of(user));

service.getAllSettingsFromAuthenticatedUserMetadata().subscribe(value =>
expect(value).toEqual(settings),
);
});
});

describe('set', () => {
it('should correctly update the chosen setting', () => {
service.updateSettings = jasmine.createSpy('updateSettings');

service.set('liveRegionTimeOut', '500');
expect(service.updateSettings).toHaveBeenCalledWith({ liveRegionTimeOut: '500' });
});
});

describe('setSettings', () => {
beforeEach(() => {
service.setSettingsInCookie = jasmine.createSpy('setSettingsInCookie');
});

it('should attempt to set settings in metadata', () => {
service.setSettingsInAuthenticatedUserMetadata =
jasmine.createSpy('setSettingsInAuthenticatedUserMetadata').and.returnValue(of(false));

const settings: AccessibilitySettings = {
notificationTimeOut: '1000',
};

service.setSettings(settings).subscribe();
expect(service.setSettingsInAuthenticatedUserMetadata).toHaveBeenCalledWith(settings);
});

it('should set settings in cookie if metadata failed', () => {
service.setSettingsInAuthenticatedUserMetadata =
jasmine.createSpy('setSettingsInAuthenticatedUserMetadata').and.returnValue(of(false));

const settings: AccessibilitySettings = {
notificationTimeOut: '1000',
};

service.setSettings(settings).subscribe();
expect(service.setSettingsInCookie).toHaveBeenCalled();
});

it('should not set settings in cookie if metadata succeeded', () => {
service.setSettingsInAuthenticatedUserMetadata =
jasmine.createSpy('setSettingsInAuthenticatedUserMetadata').and.returnValue(of(true));

const settings: AccessibilitySettings = {
notificationTimeOut: '1000',
};

service.setSettings(settings).subscribe();
expect(service.setSettingsInCookie).not.toHaveBeenCalled();
});

it('should return \'metadata\' if settings are stored in metadata', () => {
service.setSettingsInAuthenticatedUserMetadata =
jasmine.createSpy('setSettingsInAuthenticatedUserMetadata').and.returnValue(of(true));

const settings: AccessibilitySettings = {
notificationTimeOut: '1000',
};

service.setSettings(settings).subscribe(value =>
expect(value).toEqual('metadata'),
);
});

it('should return \'cookie\' if settings are stored in cookie', () => {
service.setSettingsInAuthenticatedUserMetadata =
jasmine.createSpy('setSettingsInAuthenticatedUserMetadata').and.returnValue(of(false));

const settings: AccessibilitySettings = {
notificationTimeOut: '1000',
};

service.setSettings(settings).subscribe(value =>
expect(value).toEqual('cookie'),
);
});
});

describe('updateSettings', () => {
it('should call setSettings with the updated settings', () => {
const beforeSettings: AccessibilitySettings = {
notificationTimeOut: '1000',
};

service.getAll = jasmine.createSpy('getAll').and.returnValue(of(beforeSettings));
service.setSettings = jasmine.createSpy('setSettings').and.returnValue(of('cookie'));

const newSettings: AccessibilitySettings = {
liveRegionTimeOut: '2000',
};

const combinedSettings: AccessibilitySettings = {
notificationTimeOut: '1000',
liveRegionTimeOut: '2000',
};

service.updateSettings(newSettings).subscribe();
expect(service.setSettings).toHaveBeenCalledWith(combinedSettings);
});
});

describe('setSettingsInAuthenticatedUserMetadata', () => {
beforeEach(() => {
service.setSettingsInMetadata = jasmine.createSpy('setSettingsInMetadata').and.returnValue(of(null));
});

it('should store settings in metadata when the user is authenticated', fakeAsync(() => {
const user = new EPerson();
authService.getAuthenticatedUserFromStoreIfAuthenticated = jasmine.createSpy().and.returnValue(of(user));

service.setSettingsInAuthenticatedUserMetadata({}).subscribe();
flush();

expect(service.setSettingsInMetadata).toHaveBeenCalled();
}));

it('should emit false when the user is not authenticated', fakeAsync(() => {
authService.getAuthenticatedUserFromStoreIfAuthenticated = jasmine.createSpy().and.returnValue(of(null));

service.setSettingsInAuthenticatedUserMetadata({})
.subscribe(value => expect(value).toBeFalse());
flush();

expect(service.setSettingsInMetadata).not.toHaveBeenCalled();
}));
});

describe('setSettingsInMetadata', () => {
const ePerson = new EPerson();

beforeEach(() => {
ePerson.setMetadata = jasmine.createSpy('setMetadata');
ePerson.removeMetadata = jasmine.createSpy('removeMetadata');
});

it('should set the settings in metadata', () => {
service.setSettingsInMetadata(ePerson, { ['liveRegionTimeOut']: '500' }).subscribe();
expect(ePerson.setMetadata).toHaveBeenCalled();
});

it('should remove the metadata when the settings are emtpy', () => {
service.setSettingsInMetadata(ePerson, {}).subscribe();
expect(ePerson.setMetadata).not.toHaveBeenCalled();
expect(ePerson.removeMetadata).toHaveBeenCalled();
});

it('should create a patch with the metadata changes', () => {
service.setSettingsInMetadata(ePerson, { ['liveRegionTimeOut']: '500' }).subscribe();
expect(ePersonService.createPatchFromCache).toHaveBeenCalled();
});

it('should send the patch request', () => {
service.setSettingsInMetadata(ePerson, { ['liveRegionTimeOut']: '500' }).subscribe();
expect(ePersonService.patch).toHaveBeenCalled();
});

it('should emit true when the update succeeded', fakeAsync(() => {
ePersonService.patch = jasmine.createSpy().and.returnValue(createSuccessfulRemoteDataObject$({}));

service.setSettingsInMetadata(ePerson, { ['liveRegionTimeOut']: '500' })
.subscribe(value => {
expect(value).toBeTrue();
});

flush();
}));

it('should emit false when the update failed', fakeAsync(() => {
ePersonService.patch = jasmine.createSpy().and.returnValue(createFailedRemoteDataObject$());

service.setSettingsInMetadata(ePerson, { ['liveRegionTimeOut']: '500' })
.subscribe(value => {
expect(value).toBeFalse();
});

flush();
}));
});

describe('setSettingsInCookie', () => {
beforeEach(() => {
cookieService.set = jasmine.createSpy('set');
cookieService.remove = jasmine.createSpy('remove');
});

it('should store the settings in a cookie', () => {
service.setSettingsInCookie({ ['liveRegionTimeOut']: '500' });
expect(cookieService.set).toHaveBeenCalled();
});

it('should remove the cookie when the settings are empty', () => {
service.setSettingsInCookie({});
expect(cookieService.set).not.toHaveBeenCalled();
expect(cookieService.remove).toHaveBeenCalled();
});
});

});
Loading
Loading