Commits
Kristof De Langhe authored 2d638a738e9
1 1 | import { ThemeEffects } from './theme.effects'; |
2 - | import { of as observableOf } from 'rxjs'; |
3 2 | import { TestBed } from '@angular/core/testing'; |
4 3 | import { provideMockActions } from '@ngrx/effects/testing'; |
5 - | import { LinkService } from '../../core/cache/builders/link.service'; |
6 4 | import { cold, hot } from 'jasmine-marbles'; |
7 5 | import { ROOT_EFFECTS_INIT } from '@ngrx/effects'; |
8 6 | import { SetThemeAction } from './theme.actions'; |
9 - | import { Theme } from '../../../config/theme.model'; |
10 7 | import { provideMockStore } from '@ngrx/store/testing'; |
11 - | import { ROUTER_NAVIGATED } from '@ngrx/router-store'; |
12 - | import { ResolverActionTypes } from '../../core/resolving/resolver.actions'; |
13 - | import { Community } from '../../core/shared/community.model'; |
14 - | import { COMMUNITY } from '../../core/shared/community.resource-type'; |
15 - | import { NoOpAction } from '../ngrx/no-op.action'; |
16 - | import { ITEM } from '../../core/shared/item.resource-type'; |
17 - | import { DSpaceObject } from '../../core/shared/dspace-object.model'; |
18 - | import { Item } from '../../core/shared/item.model'; |
19 - | import { Collection } from '../../core/shared/collection.model'; |
20 - | import { COLLECTION } from '../../core/shared/collection.resource-type'; |
21 - | import { |
22 - | createNoContentRemoteDataObject$, |
23 - | createSuccessfulRemoteDataObject$ |
24 - | } from '../remote-data.utils'; |
25 8 | import { BASE_THEME_NAME } from './theme.constants'; |
26 9 | |
27 - | /** |
28 - | * LinkService able to mock recursively resolving DSO parent links |
29 - | * Every time resolveLinkWithoutAttaching is called, it returns the next object in the array of ancestorDSOs until |
30 - | * none are left, after which it returns a no-content remote-date |
31 - | */ |
32 - | class MockLinkService { |
33 - | index = -1; |
34 - | |
35 - | constructor(private ancestorDSOs: DSpaceObject[]) { |
36 - | } |
37 - | |
38 - | resolveLinkWithoutAttaching() { |
39 - | if (this.index >= this.ancestorDSOs.length - 1) { |
40 - | return createNoContentRemoteDataObject$(); |
41 - | } else { |
42 - | this.index++; |
43 - | return createSuccessfulRemoteDataObject$(this.ancestorDSOs[this.index]); |
44 - | } |
45 - | } |
46 - | } |
47 - | |
48 10 | describe('ThemeEffects', () => { |
49 11 | let themeEffects: ThemeEffects; |
50 - | let linkService: LinkService; |
51 12 | let initialState; |
52 13 | |
53 - | let ancestorDSOs: DSpaceObject[]; |
54 - | |
55 14 | function init() { |
56 - | ancestorDSOs = [ |
57 - | Object.assign(new Collection(), { |
58 - | type: COLLECTION.value, |
59 - | uuid: 'collection-uuid', |
60 - | _links: { owningCommunity: { href: 'owning-community-link' } } |
61 - | }), |
62 - | Object.assign(new Community(), { |
63 - | type: COMMUNITY.value, |
64 - | uuid: 'sub-community-uuid', |
65 - | _links: { parentCommunity: { href: 'parent-community-link' } } |
66 - | }), |
67 - | Object.assign(new Community(), { |
68 - | type: COMMUNITY.value, |
69 - | uuid: 'top-community-uuid', |
70 - | }), |
71 - | ]; |
72 - | linkService = new MockLinkService(ancestorDSOs) as any; |
73 15 | initialState = { |
74 16 | theme: { |
75 17 | currentTheme: 'custom', |
76 18 | }, |
77 19 | }; |
78 20 | } |
79 21 | |
80 22 | function setupEffectsWithActions(mockActions) { |
81 23 | init(); |
82 24 | TestBed.configureTestingModule({ |
83 25 | providers: [ |
84 26 | ThemeEffects, |
85 - | { provide: LinkService, useValue: linkService }, |
86 27 | provideMockStore({ initialState }), |
87 28 | provideMockActions(() => mockActions) |
88 29 | ] |
89 30 | }); |
90 31 | |
91 32 | themeEffects = TestBed.inject(ThemeEffects); |
92 33 | } |
93 34 | |
94 35 | describe('initTheme$', () => { |
95 36 | beforeEach(() => { |
103 44 | }); |
104 45 | |
105 46 | it('should set the default theme', () => { |
106 47 | const expected = cold('--b-', { |
107 48 | b: new SetThemeAction(BASE_THEME_NAME) |
108 49 | }); |
109 50 | |
110 51 | expect(themeEffects.initTheme$).toBeObservable(expected); |
111 52 | }); |
112 53 | }); |
113 - | |
114 - | describe('updateThemeOnRouteChange$', () => { |
115 - | const url = '/test/route'; |
116 - | const dso = Object.assign(new Community(), { |
117 - | type: COMMUNITY.value, |
118 - | uuid: '0958c910-2037-42a9-81c7-dca80e3892b4', |
119 - | }); |
120 - | |
121 - | function spyOnPrivateMethods() { |
122 - | spyOn((themeEffects as any), 'getAncestorDSOs').and.returnValue(() => observableOf([dso])); |
123 - | spyOn((themeEffects as any), 'matchThemeToDSOs').and.returnValue(new Theme({ name: 'custom' })); |
124 - | spyOn((themeEffects as any), 'getActionForMatch').and.returnValue(new SetThemeAction('custom')); |
125 - | } |
126 - | |
127 - | describe('when a resolved action is present', () => { |
128 - | beforeEach(() => { |
129 - | setupEffectsWithActions( |
130 - | hot('--ab-', { |
131 - | a: { |
132 - | type: ROUTER_NAVIGATED, |
133 - | payload: { routerState: { url } }, |
134 - | }, |
135 - | b: { |
136 - | type: ResolverActionTypes.RESOLVED, |
137 - | payload: { url, dso }, |
138 - | } |
139 - | }) |
140 - | ); |
141 - | spyOnPrivateMethods(); |
142 - | }); |
143 - | |
144 - | it('should set the theme it receives from the DSO', () => { |
145 - | const expected = cold('--b-', { |
146 - | b: new SetThemeAction('custom') |
147 - | }); |
148 - | |
149 - | expect(themeEffects.updateThemeOnRouteChange$).toBeObservable(expected); |
150 - | }); |
151 - | }); |
152 - | |
153 - | describe('when no resolved action is present', () => { |
154 - | beforeEach(() => { |
155 - | setupEffectsWithActions( |
156 - | hot('--a-', { |
157 - | a: { |
158 - | type: ROUTER_NAVIGATED, |
159 - | payload: { routerState: { url } }, |
160 - | }, |
161 - | }) |
162 - | ); |
163 - | spyOnPrivateMethods(); |
164 - | }); |
165 - | |
166 - | it('should set the theme it receives from the route url', () => { |
167 - | const expected = cold('--b-', { |
168 - | b: new SetThemeAction('custom') |
169 - | }); |
170 - | |
171 - | expect(themeEffects.updateThemeOnRouteChange$).toBeObservable(expected); |
172 - | }); |
173 - | }); |
174 - | |
175 - | describe('when no themes are present', () => { |
176 - | beforeEach(() => { |
177 - | setupEffectsWithActions( |
178 - | hot('--a-', { |
179 - | a: { |
180 - | type: ROUTER_NAVIGATED, |
181 - | payload: { routerState: { url } }, |
182 - | }, |
183 - | }) |
184 - | ); |
185 - | (themeEffects as any).themes = []; |
186 - | }); |
187 - | |
188 - | it('should return an empty action', () => { |
189 - | const expected = cold('--b-', { |
190 - | b: new NoOpAction() |
191 - | }); |
192 - | |
193 - | expect(themeEffects.updateThemeOnRouteChange$).toBeObservable(expected); |
194 - | }); |
195 - | }); |
196 - | }); |
197 - | |
198 - | describe('private functions', () => { |
199 - | beforeEach(() => { |
200 - | setupEffectsWithActions(hot('-', {})); |
201 - | }); |
202 - | |
203 - | describe('getActionForMatch', () => { |
204 - | it('should return a SET action if the new theme differs from the current theme', () => { |
205 - | const theme = new Theme({ name: 'new-theme' }); |
206 - | expect((themeEffects as any).getActionForMatch(theme, 'old-theme')).toEqual(new SetThemeAction('new-theme')); |
207 - | }); |
208 - | |
209 - | it('should return an empty action if the new theme equals the current theme', () => { |
210 - | const theme = new Theme({ name: 'old-theme' }); |
211 - | expect((themeEffects as any).getActionForMatch(theme, 'old-theme')).toEqual(new NoOpAction()); |
212 - | }); |
213 - | }); |
214 - | |
215 - | describe('matchThemeToDSOs', () => { |
216 - | let themes: Theme[]; |
217 - | let nonMatchingTheme: Theme; |
218 - | let itemMatchingTheme: Theme; |
219 - | let communityMatchingTheme: Theme; |
220 - | let dsos: DSpaceObject[]; |
221 - | |
222 - | beforeEach(() => { |
223 - | nonMatchingTheme = Object.assign(new Theme({ name: 'non-matching-theme' }), { |
224 - | matches: () => false |
225 - | }); |
226 - | itemMatchingTheme = Object.assign(new Theme({ name: 'item-matching-theme' }), { |
227 - | matches: (url, dso) => (dso as any).type === ITEM.value |
228 - | }); |
229 - | communityMatchingTheme = Object.assign(new Theme({ name: 'community-matching-theme' }), { |
230 - | matches: (url, dso) => (dso as any).type === COMMUNITY.value |
231 - | }); |
232 - | dsos = [ |
233 - | Object.assign(new Item(), { |
234 - | type: ITEM.value, |
235 - | uuid: 'item-uuid', |
236 - | }), |
237 - | Object.assign(new Collection(), { |
238 - | type: COLLECTION.value, |
239 - | uuid: 'collection-uuid', |
240 - | }), |
241 - | Object.assign(new Community(), { |
242 - | type: COMMUNITY.value, |
243 - | uuid: 'community-uuid', |
244 - | }), |
245 - | ]; |
246 - | }); |
247 - | |
248 - | describe('when no themes match any of the DSOs', () => { |
249 - | beforeEach(() => { |
250 - | themes = [ nonMatchingTheme ]; |
251 - | themeEffects.themes = themes; |
252 - | }); |
253 - | |
254 - | it('should return undefined', () => { |
255 - | expect((themeEffects as any).matchThemeToDSOs(dsos, '')).toBeUndefined(); |
256 - | }); |
257 - | }); |
258 - | |
259 - | describe('when one of the themes match a DSOs', () => { |
260 - | beforeEach(() => { |
261 - | themes = [ nonMatchingTheme, itemMatchingTheme ]; |
262 - | themeEffects.themes = themes; |
263 - | }); |
264 - | |
265 - | it('should return the matching theme', () => { |
266 - | expect((themeEffects as any).matchThemeToDSOs(dsos, '')).toEqual(itemMatchingTheme); |
267 - | }); |
268 - | }); |
269 - | |
270 - | describe('when multiple themes match some of the DSOs', () => { |
271 - | it('should return the first matching theme', () => { |
272 - | themes = [ nonMatchingTheme, itemMatchingTheme, communityMatchingTheme ]; |
273 - | themeEffects.themes = themes; |
274 - | expect((themeEffects as any).matchThemeToDSOs(dsos, '')).toEqual(itemMatchingTheme); |
275 - | |
276 - | themes = [ nonMatchingTheme, communityMatchingTheme, itemMatchingTheme ]; |
277 - | themeEffects.themes = themes; |
278 - | expect((themeEffects as any).matchThemeToDSOs(dsos, '')).toEqual(communityMatchingTheme); |
279 - | }); |
280 - | }); |
281 - | }); |
282 - | |
283 - | describe('getAncestorDSOs', () => { |
284 - | it('should return an array of the provided DSO and its ancestors', (done) => { |
285 - | const dso = Object.assign(new Item(), { |
286 - | type: ITEM.value, |
287 - | uuid: 'item-uuid', |
288 - | _links: { owningCollection: { href: 'owning-collection-link' } }, |
289 - | }); |
290 - | |
291 - | observableOf(dso).pipe( |
292 - | (themeEffects as any).getAncestorDSOs() |
293 - | ).subscribe((result) => { |
294 - | expect(result).toEqual([dso, ancestorDSOs]); |
295 - | done(); |
296 - | }); |
297 - | }); |
298 - | |
299 - | it('should return an array of just the provided DSO if it doesn\'t have any parents', (done) => { |
300 - | const dso = { |
301 - | type: ITEM.value, |
302 - | uuid: 'item-uuid', |
303 - | }; |
304 - | |
305 - | observableOf(dso).pipe( |
306 - | (themeEffects as any).getAncestorDSOs() |
307 - | ).subscribe((result) => { |
308 - | expect(result).toEqual([dso]); |
309 - | done(); |
310 - | }); |
311 - | }); |
312 - | }); |
313 - | }); |
314 54 | }); |