Skip to content
Permalink
Browse files

feat: add message support and other stuff

  • Loading branch information
ike18t committed Feb 21, 2018
1 parent f44136b commit eddabe83127eee700f481fddb2e5142822632549
@@ -18,7 +18,7 @@ describe('FlipadelphiaComponent', () => {
.then(() => {
mockFlipperService = new Mock<FlipperService>({ disable: Mock.ANY_FUNC,
enable: Mock.ANY_FUNC,
isEnabled: () => false }).Object;
isEnabled: () => true }).Object;
fixture = TestBed.createComponent(FlipadelphiaComponent);
componentInstance = fixture.componentInstance;
componentInstance.flipadelphia = new TestFlipadelphia(mockFlipperService);
@@ -32,26 +32,41 @@ describe('FlipadelphiaComponent', () => {
});

it('should have active feature toggles checked', () => {
new Mock<FlipperService>(mockFlipperService).extend({ isEnabled: () => true });
fixture.detectChanges();
const checkboxes = fixture.debugElement.queryAll(By.css('input[type=checkbox]:checked'));
expect(checkboxes.length).toBe(3);
expect(checkboxes.length).toBe(5);
});

it('should not have inactive feature toggles checked', () => {
new Mock<FlipperService>(mockFlipperService).extend({ isEnabled: () => false });
fixture.detectChanges();
const checkboxes = fixture.debugElement.queryAll(By.css('input[type=checkbox]:not(:checked)'));
expect(checkboxes.length).toBe(2);
expect(checkboxes.length).toBe(5);
});

it('should call enable on the service when you check a toggle', () => {
it('should call enable on the service when you check a disabled toggle', () => {
new Mock<FlipperService>(mockFlipperService).extend({ isEnabled: () => false });
fixture.detectChanges();
const unchecked = fixture.debugElement.query(By.css('#bar'));
unchecked.nativeElement.click();
fixture.detectChanges();
expect(mockFlipperService.enable).toHaveBeenCalledWith('bar');
});

it('should update the toggle state to false when you uncheck one', () => {
const checked = fixture.debugElement.query(By.css('#foo'));
it('should call disable on the service when you check a enabled toggle', () => {
new Mock<FlipperService>(mockFlipperService).extend({ isEnabled: () => true });
fixture.detectChanges();
const checked = fixture.debugElement.query(By.css('#bar'));
checked.nativeElement.click();
fixture.detectChanges();
expect(mockFlipperService.disable).toHaveBeenCalledWith('foo');
expect(mockFlipperService.disable).toHaveBeenCalledWith('bar');
});

it('should update the toggle state to false when you uncheck one', () => {
const checked = fixture.debugElement.query(By.css('#bar'));
checked.nativeElement.click(); // Disable
fixture.detectChanges();
expect(mockFlipperService.disable).toHaveBeenCalledWith('bar');
});
});
@@ -3,27 +3,35 @@ import { Flipadelphia } from './flipadelphia';

@Component({
selector: 'flipadelphia',
styles: [
'ul { list-style: none; }',
'.message { display: block; }'
],
template: `
<ul>
<li *ngFor='let flip of flips'>
<li *ngFor='let flipName of flipKeys'>
<label>
<input type='checkbox'
[id]='flip'
[checked]='flipadelphia[flip]'
[id]='flipName'
[checked]='flipadelphia[flipName]'
(change)='setToggleState($event.target)'/>
{{ flip }}
{{ flipName }} (default: {{ flips[flipName].default }})
</label>
<span class="message">{{ flips[flipName].message }}</span>
</li>
</ul>
`
})
export class FlipadelphiaComponent implements OnInit {
@Input('flipadelphiaInstance') flipadelphia: Flipadelphia;

flips: string[] = [];
flips: Flips = {};
get flipKeys() {
return Object.keys(this.flips);
}

ngOnInit() {
this.flips = (this.flipadelphia.constructor as any).flips as string[];
this.flips = (this.flipadelphia.constructor as any).flips as Flips;
}

public setToggleState(target: HTMLInputElement) {
@@ -34,3 +42,7 @@ export class FlipadelphiaComponent implements OnInit {
}
}
}

export interface Flips {
[key: string]: boolean;
}
@@ -3,15 +3,10 @@ import { FlipperService } from './flipper.service';
import { TestFlipadelphia } from './test-fixtures';

describe('flipadelphia', () => {
it('uses the default value provided in the fixture', () => {
it('returns the value from the service', () => {
const flipperService = new Mock<FlipperService>({ isEnabled: () => false });
const flipadelphia = new TestFlipadelphia(flipperService.Object);
expect(flipadelphia.bazz).toBe(true);
});

it('overrides the default value provided in the fixture', () => {
const flipperService = new Mock<FlipperService>({ isEnabled: () => true });
const flipadelphia = new TestFlipadelphia(flipperService.Object);
expect(flipadelphia.bar).toBe(true);
expect(flipadelphia.foo).toBe(false);
expect(flipperService.Object.isEnabled).toHaveBeenCalledWith('foo', true);
});
});
@@ -9,14 +9,15 @@ export class Flipadelphia {
}
}

export function Flip(defaultValue: boolean = false):
export function Flip(defaultValue: boolean = false, message?: string):
<T extends Flipadelphia & Record<K, boolean>, K extends string>(target: T, key: K) => void {
return function<T extends Flipadelphia & Record<K, boolean>, K extends string>(target: T, key: K) {
const flipadelphiaConstructor = target.constructor as any;
flipadelphiaConstructor.flips = (flipadelphiaConstructor.flips || []).concat(key);
flipadelphiaConstructor.flips = flipadelphiaConstructor.flips || {};
flipadelphiaConstructor.flips[key] = { default: defaultValue, message };
Object.defineProperty(target, key, {
get(): boolean {
return defaultValue || this.flipperService.isEnabled(key);
return this.flipperService.isEnabled(key, defaultValue);
}
});
};
@@ -1,5 +1,5 @@
export interface FlipperService {
disable(toggleName: string): void;
enable(toggleName: string): void;
isEnabled(key: string): boolean;
isEnabled(key: string, defaultValue: boolean): boolean;
}
@@ -6,63 +6,61 @@ describe('LocalStorageFlipperService', () => {
let mockStorage: Mock<Storage>;

beforeEach(() => {
service = new LocalStorageFlipperService((mockStorage = new Mock<Storage>()).Object);
service = new LocalStorageFlipperService('FLIPADELPHIA', (mockStorage = new Mock<Storage>()).Object);
});

describe('isEnabled', () => {
it('returns true if the key is returned from the persisted array', () => {
mockStorage.extend({ getItem: () => '["foo", "bar"]' });
expect(service.isEnabled('bar')).toBe(true);
beforeEach(() => {
mockStorage.extend({ getItem: () => '{"foo": true, "bar": false }' });
});

it('returns false if the key is not returned from the persisted array and the initial value is false', () => {
mockStorage.extend({ getItem: () => '["foo"]' });
expect(service.isEnabled('bar')).toBe(false);
it('returns true if the key is returned from the persisted object', () => {
expect(service.isEnabled('foo')).toBe(true);
});

it('uses the correct key for local storage lookup', () => {
mockStorage.extend({ getItem: () => '[]' });
it('returns false if the value is false in the persisted object and the initial value is false', () => {
expect(service.isEnabled('bar', false)).toBe(false);
});

it('returns true if the default value true and there is no key in storage', () => {
expect(service.isEnabled('bah', true)).toBe(true);
});

it('uses the provided key for local storage lookup', () => {
service.isEnabled('bar');
expect(mockStorage.Object.getItem).toHaveBeenCalledWith('FLIPADELPHIA');
});
});

describe('enable', () => {
it('does not add an already enabled toggle to storage', () => {
mockStorage.extend({ getItem: () => '["bar"]',
setItem: Mock.ANY_FUNC });
service.enable('bar');
expect(mockStorage.Object.setItem).not.toHaveBeenCalled();
});

it('adds toggle to storage', () => {
mockStorage.extend({ getItem: () => '[]',
mockStorage.extend({ getItem: () => '{"foo":false}',
setItem: Mock.ANY_FUNC });
service.enable('bar');
expect(mockStorage.Object.setItem).toHaveBeenCalledWith('FLIPADELPHIA', '["bar"]');
expect(mockStorage.Object.setItem).toHaveBeenCalledWith('FLIPADELPHIA', '{"foo":false,"bar":true}');
});

it('appends the toggle to storage array', () => {
mockStorage.extend({ getItem: () => '["foo"]',
it('updates value in storage', () => {
mockStorage.extend({ getItem: () => '{"bar": false}',
setItem: Mock.ANY_FUNC });
service.enable('bar');
expect(mockStorage.Object.setItem).toHaveBeenCalledWith('FLIPADELPHIA', '["foo","bar"]');
expect(mockStorage.Object.setItem).toHaveBeenCalledWith('FLIPADELPHIA', '{"bar":true}');
});
});

describe('disable', () => {
it('does not write to local storage if the toggle is already disabled', () => {
mockStorage.extend({ getItem: () => '[]',
it('adds toggle to storage', () => {
mockStorage.extend({ getItem: () => '{"foo":false}',
setItem: Mock.ANY_FUNC });
service.disable('bar');
expect(mockStorage.Object.setItem).not.toHaveBeenCalled();
expect(mockStorage.Object.setItem).toHaveBeenCalledWith('FLIPADELPHIA', '{"foo":false,"bar":false}');
});

it('removes the toggle from local storage', () => {
mockStorage.extend({ getItem: () => '["foo"]',
it('updates value in storage', () => {
mockStorage.extend({ getItem: () => '{"bar": true}',
setItem: Mock.ANY_FUNC });
service.disable('foo');
expect(mockStorage.Object.setItem).toHaveBeenCalledWith('FLIPADELPHIA', '[]');
service.disable('bar');
expect(mockStorage.Object.setItem).toHaveBeenCalledWith('FLIPADELPHIA', '{"bar":false}');
});
});
});
@@ -1,31 +1,29 @@
import { FlipperService } from './flipper.service';

export class LocalStorageFlipperService implements FlipperService {
readonly LOCAL_STORAGE_KEY = 'FLIPADELPHIA';
constructor(private readonly storageKey = 'FLIPADELPHIA',
private readonly localStorage = window.localStorage) {}

constructor(public localStorage = window.localStorage) {}

private get enabledToggles() {
return JSON.parse(this.localStorage.getItem(this.LOCAL_STORAGE_KEY) as string) || [] as string[];
private get toggles(): { [key: string]: boolean } {
return JSON.parse(this.localStorage.getItem(this.storageKey) as string) || {};
}

disable(toggleName: string): void {
if (!this.isEnabled(toggleName)) {
return;
}
this.localStorage.setItem(this.LOCAL_STORAGE_KEY,
JSON.stringify(this.enabledToggles.filter((toggle: string) => toggle !== toggleName)));
this.toggle(toggleName, false);
}

enable(toggleName: string): void {
if (this.isEnabled(toggleName)) {
return;
}
this.localStorage.setItem(this.LOCAL_STORAGE_KEY,
JSON.stringify(this.enabledToggles.concat(toggleName)));
this.toggle(toggleName, true);
}

isEnabled(toggleName: string, defaultValue: boolean = false): boolean {
return this.toggles[toggleName] || defaultValue;
}

isEnabled(toggleName: string): boolean {
return this.enabledToggles.includes(toggleName);
private toggle(toggleName: string, enable: boolean) {
const toggles = this.toggles;
toggles[toggleName] = enable;
this.localStorage.setItem(this.storageKey,
JSON.stringify(toggles));
}
}

0 comments on commit eddabe8

Please sign in to comment.
You can’t perform that action at this time.