Commit b9f6daf63e63ab7fbd937504a85d1ec69035e37d
1 parent
bbeb5848
Exists in
master
and in
11 other branches
better css definition to the design toggler. increased the test coverage to the …
…designModeToggler Component
Showing
9 changed files
with
212 additions
and
9 deletions
Show diff stats
@@ -0,0 +1,31 @@ | @@ -0,0 +1,31 @@ | ||
1 | +import {DesignModeService} from './designMode.service'; | ||
2 | + | ||
3 | +describe('DesignMode Service', () => { | ||
4 | + let service: DesignModeService; | ||
5 | + | ||
6 | + beforeEach(() => { | ||
7 | + service = new DesignModeService(); | ||
8 | + }); | ||
9 | + | ||
10 | + it('has the designModeOn equals false as default', () => { | ||
11 | + expect(service.isInDesignMode()).toBeFalsy(); | ||
12 | + }); | ||
13 | + | ||
14 | + it('allows set the designMode value', () => { | ||
15 | + spyOn(service.onToggle, 'next').and.stub(); | ||
16 | + service.setInDesignMode(true); | ||
17 | + expect(service.isInDesignMode).toBeTruthy(); | ||
18 | + }); | ||
19 | + | ||
20 | + it('emits the onToggle event when changing the designModeOn property', () => { | ||
21 | + spyOn(service.onToggle, 'next').and.stub(); | ||
22 | + service.setInDesignMode(true); | ||
23 | + expect(service.onToggle.next).toHaveBeenCalled(); | ||
24 | + }); | ||
25 | + | ||
26 | + it('does not emit onToggle event when there is no change on designModeOn property', () => { | ||
27 | + spyOn(service.onToggle, 'next').and.stub(); | ||
28 | + service.setInDesignMode(false); | ||
29 | + expect(service.onToggle.next).not.toHaveBeenCalled(); | ||
30 | + }); | ||
31 | +}); | ||
0 | \ No newline at end of file | 32 | \ No newline at end of file |
src/app/admin/designMode.service.ts
@@ -12,8 +12,10 @@ export class DesignModeService { | @@ -12,8 +12,10 @@ export class DesignModeService { | ||
12 | } | 12 | } |
13 | 13 | ||
14 | setInDesignMode(value: boolean) { | 14 | setInDesignMode(value: boolean) { |
15 | - this.designModeOn = value; | ||
16 | - this.onToggle.next(this.designModeOn); | 15 | + if (this.designModeOn !== value) { |
16 | + this.designModeOn = value; | ||
17 | + this.onToggle.next(this.designModeOn); | ||
18 | + } | ||
17 | } | 19 | } |
18 | 20 | ||
19 | constructor() { | 21 | constructor() { |
@@ -0,0 +1,59 @@ | @@ -0,0 +1,59 @@ | ||
1 | +import {ComponentTestHelper, createClass} from './../../spec/component-test-helper'; | ||
2 | +import * as helpers from './../../spec/helpers'; | ||
3 | +import {DesignModeTogglerComponent} from './designModeToggler.component'; | ||
4 | +import {DesignModeService} from './designMode.service'; | ||
5 | + | ||
6 | +describe('DesignModeToggler Component', () => { | ||
7 | + const htmlTemplate: string = '<noosfero-design-toggler></noosfero-design-toggler>'; | ||
8 | + | ||
9 | + let helper: ComponentTestHelper<DesignModeTogglerComponent>; | ||
10 | + beforeEach(() => { | ||
11 | + angular.mock.module('templates'); | ||
12 | + angular.mock.module('ngSanitize'); | ||
13 | + angular.mock.module('toggle-switch'); | ||
14 | + }); | ||
15 | + | ||
16 | + let designModeService: DesignModeService; | ||
17 | + beforeEach((done) => { | ||
18 | + designModeService = new DesignModeService(); | ||
19 | + let cls = createClass({ | ||
20 | + template: htmlTemplate, | ||
21 | + directives: [DesignModeTogglerComponent], | ||
22 | + providers: [ | ||
23 | + helpers.createProviderToValue('DesignModeService', designModeService) | ||
24 | + ] | ||
25 | + }); | ||
26 | + helper = new ComponentTestHelper<DesignModeTogglerComponent>(cls, done); | ||
27 | + }); | ||
28 | + | ||
29 | + it('changes css classes representing the switch is on or off', () => { | ||
30 | + expect(helper.debugElement.query('div.switch-animate').hasClass('switch-off')).toBeTruthy(); | ||
31 | + expect(helper.debugElement.query('div.switch-animate').hasClass('switch-on')).toBeFalsy(); | ||
32 | + helper.component.inDesignMode = true; | ||
33 | + helper.detectChanges(); | ||
34 | + expect(helper.debugElement.query('div.switch-animate').hasClass('switch-on')).toBeTruthy(); | ||
35 | + expect(helper.debugElement.query('div.switch-animate').hasClass('switch-off')).toBeFalsy(); | ||
36 | + }); | ||
37 | + | ||
38 | + it('emits event with value "true" when changing inDesignMode to On', (done) => { | ||
39 | + designModeService.onToggle.subscribe((designModeOn: boolean) => { | ||
40 | + expect(designModeOn).toBeTruthy(); | ||
41 | + done(); | ||
42 | + }); | ||
43 | + helper.component.inDesignMode = true; | ||
44 | + helper.detectChanges(); | ||
45 | + }); | ||
46 | + | ||
47 | + it('emits events with value "false" when changing inDesignMode to Off', (done) => { | ||
48 | + helper.component.inDesignMode = true; | ||
49 | + helper.detectChanges(); | ||
50 | + | ||
51 | + designModeService.onToggle.subscribe((designModeOn: boolean) => { | ||
52 | + expect(designModeOn).toBeFalsy(); | ||
53 | + done(); | ||
54 | + }); | ||
55 | + | ||
56 | + helper.component.inDesignMode = false; | ||
57 | + helper.detectChanges(); | ||
58 | + }); | ||
59 | +}); | ||
0 | \ No newline at end of file | 60 | \ No newline at end of file |
src/app/admin/designModeToggler.component.ts
@@ -4,11 +4,12 @@ import {DesignModeService} from './designMode.service'; | @@ -4,11 +4,12 @@ import {DesignModeService} from './designMode.service'; | ||
4 | selector: 'noosfero-design-toggler', | 4 | selector: 'noosfero-design-toggler', |
5 | templateUrl: 'app/admin/designModeToggler.html' | 5 | templateUrl: 'app/admin/designModeToggler.html' |
6 | }) | 6 | }) |
7 | -@Inject(DesignModeService, '$scope') | 7 | +@Inject(DesignModeService) |
8 | export class DesignModeTogglerComponent { | 8 | export class DesignModeTogglerComponent { |
9 | 9 | ||
10 | + icon: string = " <i class='glyphicon glyphicon-wrench'></i> "; | ||
10 | 11 | ||
11 | - constructor(private designModeService: DesignModeService, private $scope: ng.IScope) { | 12 | + constructor(private designModeService: DesignModeService) { |
12 | } | 13 | } |
13 | 14 | ||
14 | private _inDesignMode: boolean = false; | 15 | private _inDesignMode: boolean = false; |
@@ -20,5 +21,4 @@ export class DesignModeTogglerComponent { | @@ -20,5 +21,4 @@ export class DesignModeTogglerComponent { | ||
20 | set inDesignMode(value: boolean) { | 21 | set inDesignMode(value: boolean) { |
21 | this.designModeService.setInDesignMode(value); | 22 | this.designModeService.setInDesignMode(value); |
22 | }; | 23 | }; |
23 | - | ||
24 | } | 24 | } |
25 | \ No newline at end of file | 25 | \ No newline at end of file |
src/app/admin/designModeToggler.html
@@ -3,5 +3,6 @@ | @@ -3,5 +3,6 @@ | ||
3 | ng-model="ctrl.inDesignMode" | 3 | ng-model="ctrl.inDesignMode" |
4 | on-label="{{'designMode.toggle.ON' | translate}}" | 4 | on-label="{{'designMode.toggle.ON' | translate}}" |
5 | off-label="{{'designMode.toggle.OFF' | translate}}" | 5 | off-label="{{'designMode.toggle.OFF' | translate}}" |
6 | - knob-label="{{'designMode.label' | translate}}"> | 6 | + class="switch-small" |
7 | + knob-label="{{ ctrl.icon + ('designMode.label' | translate) }}"> | ||
7 | </toggle-switch> | 8 | </toggle-switch> |
8 | \ No newline at end of file | 9 | \ No newline at end of file |
src/app/layout/scss/skins/_whbl.scss
@@ -289,6 +289,31 @@ $whbl-font-color: #16191c; | @@ -289,6 +289,31 @@ $whbl-font-color: #16191c; | ||
289 | .pace .pace-progress { | 289 | .pace .pace-progress { |
290 | background-color: #fff; | 290 | background-color: #fff; |
291 | } | 291 | } |
292 | + | ||
293 | + noosfero-design-toggler .ats-switch .knob i { | ||
294 | + color: #999999; | ||
295 | + } | ||
296 | + | ||
297 | + .ats-switch .knob { | ||
298 | + padding-right: 15px; | ||
299 | + } | ||
300 | + | ||
301 | + .ats-switch .glyphicon { | ||
302 | + top: 2px; | ||
303 | + } | ||
304 | + | ||
305 | + .ats-switch .switch-left { | ||
306 | + background-color: #41b941; | ||
307 | + font-weight: bold; | ||
308 | + color: white; | ||
309 | + } | ||
310 | + | ||
311 | + .ats-switch .switch-right { | ||
312 | + background-color: #ce3b3b; | ||
313 | + font-weight: bold; | ||
314 | + color: white; | ||
315 | + } | ||
316 | + | ||
292 | } | 317 | } |
293 | .rtl.skin-whbl #content-wrapper { | 318 | .rtl.skin-whbl #content-wrapper { |
294 | border-left: 0; | 319 | border-left: 0; |
src/app/layout/services/body-state-classes.service.spec.ts
@@ -4,7 +4,7 @@ import {AuthService} from "./../../login/auth.service"; | @@ -4,7 +4,7 @@ import {AuthService} from "./../../login/auth.service"; | ||
4 | import {AuthEvents} from "./../../login/auth-events"; | 4 | import {AuthEvents} from "./../../login/auth-events"; |
5 | 5 | ||
6 | import {EventEmitter} from 'ng-forward'; | 6 | import {EventEmitter} from 'ng-forward'; |
7 | - | 7 | +import {DesignModeService} from './../../admin/designMode.service'; |
8 | 8 | ||
9 | describe("BodyStateClasses Service", () => { | 9 | describe("BodyStateClasses Service", () => { |
10 | 10 | ||
@@ -19,10 +19,13 @@ describe("BodyStateClasses Service", () => { | @@ -19,10 +19,13 @@ describe("BodyStateClasses Service", () => { | ||
19 | }, | 19 | }, |
20 | authService: any = helpers.mocks.authService, | 20 | authService: any = helpers.mocks.authService, |
21 | bodyEl: { className: string }, | 21 | bodyEl: { className: string }, |
22 | - bodyElJq: any; | 22 | + bodyElJq: any, |
23 | + designModeService = new DesignModeService(); | ||
24 | + | ||
25 | + | ||
23 | 26 | ||
24 | let getService = (): BodyStateClassesService => { | 27 | let getService = (): BodyStateClassesService => { |
25 | - return new BodyStateClassesService($rootScope, $document, $state, authService); | 28 | + return new BodyStateClassesService($rootScope, $document, $state, authService, designModeService); |
26 | }; | 29 | }; |
27 | 30 | ||
28 | beforeEach(() => { | 31 | beforeEach(() => { |
@@ -168,4 +171,30 @@ describe("BodyStateClasses Service", () => { | @@ -168,4 +171,30 @@ describe("BodyStateClasses Service", () => { | ||
168 | 171 | ||
169 | expect(contentWrapperMock.removeClass).toHaveBeenCalledWith(BodyStateClassesService.CONTENT_WRAPPER_FULL); | 172 | expect(contentWrapperMock.removeClass).toHaveBeenCalledWith(BodyStateClassesService.CONTENT_WRAPPER_FULL); |
170 | }); | 173 | }); |
174 | + | ||
175 | + it("should add the class noosfero-design-on when designMode is changed to true", () => { | ||
176 | + let fnOnToggle: Function = null; | ||
177 | + designModeService.onToggle = <any> { | ||
178 | + subscribe: (fn: Function) => { | ||
179 | + fnOnToggle = fn; | ||
180 | + }, | ||
181 | + next: (value: boolean) => { | ||
182 | + fnOnToggle.apply(designModeService, [value]); | ||
183 | + } | ||
184 | + }; | ||
185 | + | ||
186 | + let service = getService(); | ||
187 | + | ||
188 | + bodyElJq.addClass = jasmine.createSpy("addClass"); | ||
189 | + bodyElJq.removeClass = jasmine.createSpy("removeClass"); | ||
190 | + service["bodyElement"] = bodyElJq; | ||
191 | + | ||
192 | + service.start(); | ||
193 | + | ||
194 | + debugger; | ||
195 | + designModeService.setInDesignMode(true); | ||
196 | + | ||
197 | + | ||
198 | + expect(bodyElJq.addClass).toHaveBeenCalledWith(BodyStateClassesService.DESIGN_MODE_ON_CLASSNAME); | ||
199 | + }); | ||
171 | }); | 200 | }); |
src/app/shared/components/bootstrap-switcher/bootstrap-switcher.component.ts
0 → 100644
@@ -0,0 +1,51 @@ | @@ -0,0 +1,51 @@ | ||
1 | +import {Component, Input, Output, EventEmitter} from 'ng-forward'; | ||
2 | + | ||
3 | + | ||
4 | +export interface BootstrapSwitcherItem { | ||
5 | + value: any; | ||
6 | + label: string; | ||
7 | +} | ||
8 | +@Component({ | ||
9 | + selector: 'noosfero-bootstrap-switcher', | ||
10 | + template: ` | ||
11 | + <span class="switcher-label" ng-bind="ctrl.label | translate"></span> | ||
12 | + <div class="btn-group switcher"> | ||
13 | + <button ng-repeat="option in ctrl.options track by $index" | ||
14 | + (click)="ctrl.switcherClick(option)" | ||
15 | + ng-class="ctrl.getCssClassForItem(option)" | ||
16 | + class="btn btn-xs" ng-bind="option.label | translate"> | ||
17 | + </button> | ||
18 | + </div> | ||
19 | + `, | ||
20 | + inputs: ['label', 'options', 'defaultOption'], | ||
21 | + outputs: ['onSwitch'] | ||
22 | +}) | ||
23 | +export class BootstrapSwitcherComponent { | ||
24 | + @Input('activeClass') activeClass: string = 'active btn-danger'; | ||
25 | + @Input('defaultClass') defaultClass: string = 'btn-default'; | ||
26 | + @Input('label') label: string; | ||
27 | + @Input('options') options: BootstrapSwitcherItem[]; | ||
28 | + @Input('defaultOption') defaultOption: BootstrapSwitcherItem; | ||
29 | + @Output('onSwitch') onSwitch: EventEmitter<BootstrapSwitcherItem> = new EventEmitter<BootstrapSwitcherItem>(); | ||
30 | + | ||
31 | + selectedOption: BootstrapSwitcherItem = null; | ||
32 | + | ||
33 | + constructor() { } | ||
34 | + | ||
35 | + ngOnInit() { | ||
36 | + this.selectedOption = this.defaultOption; | ||
37 | + } | ||
38 | + | ||
39 | + isSelectedOption(value: BootstrapSwitcherItem): boolean { | ||
40 | + return this.selectedOption === value; | ||
41 | + } | ||
42 | + | ||
43 | + getCssClassForItem(value: BootstrapSwitcherItem): string { | ||
44 | + return this.isSelectedOption(value) ? this.activeClass : this.defaultClass; | ||
45 | + } | ||
46 | + | ||
47 | + switcherClick(value: BootstrapSwitcherItem) { | ||
48 | + this.selectedOption = value; | ||
49 | + this.onSwitch.next(value); | ||
50 | + } | ||
51 | +} | ||
0 | \ No newline at end of file | 52 | \ No newline at end of file |