Make addon-enabled as a clean architecture
This commit is contained in:
parent
05ef6a8ca3
commit
e76ca380f7
16 changed files with 218 additions and 96 deletions
|
@ -14,10 +14,10 @@ export default class ContentMessageClient {
|
|||
}
|
||||
|
||||
async getAddonEnabled(tabId: number): Promise<boolean> {
|
||||
let { enabled } = await browser.tabs.sendMessage(tabId, {
|
||||
let enabled = await browser.tabs.sendMessage(tabId, {
|
||||
type: messages.ADDON_ENABLED_QUERY,
|
||||
}) as { enabled: boolean };
|
||||
return enabled;
|
||||
});
|
||||
return enabled as any as boolean;
|
||||
}
|
||||
|
||||
toggleAddonEnabled(tabId: number): Promise<void> {
|
||||
|
|
|
@ -25,7 +25,7 @@ export default class MessageListener {
|
|||
) {
|
||||
browser.runtime.onMessage.addListener(
|
||||
(msg: any, sender: WebExtMessageSender) => {
|
||||
listener(valueOf(msg), sender);
|
||||
return listener(valueOf(msg), sender);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
import * as messages from '../../shared/messages';
|
||||
import * as actions from './index';
|
||||
|
||||
const enable = (): Promise<actions.AddonAction> => setEnabled(true);
|
||||
|
||||
const disable = (): Promise<actions.AddonAction> => setEnabled(false);
|
||||
|
||||
const setEnabled = async(enabled: boolean): Promise<actions.AddonAction> => {
|
||||
await browser.runtime.sendMessage({
|
||||
type: messages.ADDON_ENABLED_RESPONSE,
|
||||
enabled,
|
||||
});
|
||||
return {
|
||||
type: actions.ADDON_SET_ENABLED,
|
||||
enabled,
|
||||
};
|
||||
};
|
||||
|
||||
export { enable, disable, setEnabled };
|
|
@ -2,9 +2,6 @@ import Redux from 'redux';
|
|||
import Settings from '../../shared/Settings';
|
||||
import * as keyUtils from '../../shared/utils/keys';
|
||||
|
||||
// Enable/disable
|
||||
export const ADDON_SET_ENABLED = 'addon.set.enabled';
|
||||
|
||||
// Find
|
||||
export const FIND_SET_KEYWORD = 'find.set.keyword';
|
||||
|
||||
|
@ -34,11 +31,6 @@ export const MARK_SET_LOCAL = 'mark.set.local';
|
|||
|
||||
export const NOOP = 'noop';
|
||||
|
||||
export interface AddonSetEnabledAction extends Redux.Action {
|
||||
type: typeof ADDON_SET_ENABLED;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export interface FindSetKeywordAction extends Redux.Action {
|
||||
type: typeof FIND_SET_KEYWORD;
|
||||
keyword: string;
|
||||
|
@ -101,7 +93,6 @@ export interface NoopAction extends Redux.Action {
|
|||
type: typeof NOOP;
|
||||
}
|
||||
|
||||
export type AddonAction = AddonSetEnabledAction;
|
||||
export type FindAction = FindSetKeywordAction | NoopAction;
|
||||
export type SettingAction = SettingSetAction;
|
||||
export type InputAction = InputKeyPressAction | InputClearKeysAction;
|
||||
|
@ -113,7 +104,6 @@ export type MarkAction =
|
|||
MarkCancelAction | MarkSetLocalAction | NoopAction;
|
||||
|
||||
export type Action =
|
||||
AddonAction |
|
||||
FindAction |
|
||||
SettingAction |
|
||||
InputAction |
|
||||
|
|
|
@ -6,23 +6,28 @@ import * as navigates from '../navigates';
|
|||
import * as focuses from '../focuses';
|
||||
import * as urls from '../urls';
|
||||
import * as consoleFrames from '../console-frames';
|
||||
import * as addonActions from './addon';
|
||||
import * as markActions from './mark';
|
||||
|
||||
import AddonEnabledUseCase from '../usecases/AddonEnabledUseCase';
|
||||
|
||||
let addonEnabledUseCase = new AddonEnabledUseCase();
|
||||
|
||||
// eslint-disable-next-line complexity, max-lines-per-function
|
||||
const exec = (
|
||||
const exec = async(
|
||||
operation: operations.Operation,
|
||||
settings: any,
|
||||
addonEnabled: boolean,
|
||||
): Promise<actions.Action> | actions.Action => {
|
||||
): Promise<actions.Action> => {
|
||||
let smoothscroll = settings.properties.smoothscroll;
|
||||
switch (operation.type) {
|
||||
case operations.ADDON_ENABLE:
|
||||
return addonActions.enable();
|
||||
await addonEnabledUseCase.enable();
|
||||
return { type: actions.NOOP };
|
||||
case operations.ADDON_DISABLE:
|
||||
return addonActions.disable();
|
||||
await addonEnabledUseCase.disable();
|
||||
return { type: actions.NOOP };
|
||||
case operations.ADDON_TOGGLE_ENABLED:
|
||||
return addonActions.setEnabled(!addonEnabled);
|
||||
await addonEnabledUseCase.toggle();
|
||||
return { type: actions.NOOP };
|
||||
case operations.FIND_NEXT:
|
||||
window.top.postMessage(JSON.stringify({
|
||||
type: messages.FIND_NEXT,
|
||||
|
|
16
src/content/client/AddonIndicatorClient.ts
Normal file
16
src/content/client/AddonIndicatorClient.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import * as messages from '../../shared/messages';
|
||||
|
||||
export default interface AddonIndicatorClient {
|
||||
setEnabled(enabled: boolean): Promise<void>;
|
||||
|
||||
// eslint-disable-next-line semi
|
||||
}
|
||||
|
||||
export class AddonIndicatorClientImpl implements AddonIndicatorClient {
|
||||
setEnabled(enabled: boolean): Promise<void> {
|
||||
return browser.runtime.sendMessage({
|
||||
type: messages.ADDON_ENABLED_RESPONSE,
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -5,11 +5,14 @@ import KeymapperComponent from './keymapper';
|
|||
import * as settingActions from '../../actions/setting';
|
||||
import * as messages from '../../../shared/messages';
|
||||
import MessageListener from '../../MessageListener';
|
||||
import * as addonActions from '../../actions/addon';
|
||||
import * as blacklists from '../../../shared/blacklists';
|
||||
import * as keys from '../../../shared/utils/keys';
|
||||
import * as actions from '../../actions';
|
||||
|
||||
import AddonEnabledUseCase from '../../usecases/AddonEnabledUseCase';
|
||||
|
||||
let addonEnabledUseCase = new AddonEnabledUseCase();
|
||||
|
||||
export default class Common {
|
||||
private win: Window;
|
||||
|
||||
|
@ -34,12 +37,11 @@ export default class Common {
|
|||
}
|
||||
|
||||
onMessage(message: messages.Message) {
|
||||
let { enabled } = this.store.getState().addon;
|
||||
switch (message.type) {
|
||||
case messages.SETTINGS_CHANGED:
|
||||
return this.reloadSettings();
|
||||
case messages.ADDON_TOGGLE_ENABLED:
|
||||
this.store.dispatch(addonActions.setEnabled(!enabled));
|
||||
addonEnabledUseCase.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,7 +52,11 @@ export default class Common {
|
|||
let enabled = !blacklists.includes(
|
||||
action.settings.blacklist, this.win.location.href
|
||||
);
|
||||
this.store.dispatch(addonActions.setEnabled(enabled));
|
||||
if (enabled) {
|
||||
addonEnabledUseCase.enable();
|
||||
} else {
|
||||
addonEnabledUseCase.disable();
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
// Sometime sendMessage fails when background script is not ready.
|
||||
|
|
|
@ -3,6 +3,10 @@ import * as operationActions from '../../actions/operation';
|
|||
import * as operations from '../../../shared/operations';
|
||||
import * as keyUtils from '../../../shared/utils/keys';
|
||||
|
||||
import AddonEnabledUseCase from '../../usecases/AddonEnabledUseCase';
|
||||
|
||||
let addonEnabledUseCase = new AddonEnabledUseCase();
|
||||
|
||||
const mapStartsWith = (
|
||||
mapping: keyUtils.Key[],
|
||||
keys: keyUtils.Key[],
|
||||
|
@ -41,7 +45,7 @@ export default class KeymapperComponent {
|
|||
(mapping: keyUtils.Key[]) => {
|
||||
return mapStartsWith(mapping, input.keys);
|
||||
});
|
||||
if (!state.addon.enabled) {
|
||||
if (!addonEnabledUseCase.getEnabled()) {
|
||||
// available keymaps are only ADDON_ENABLE and ADDON_TOGGLE_ENABLED if
|
||||
// the addon disabled
|
||||
matched = matched.filter((keys) => {
|
||||
|
@ -59,7 +63,7 @@ export default class KeymapperComponent {
|
|||
}
|
||||
let operation = keymaps.get(matched[0]) as operations.Operation;
|
||||
let act = operationActions.exec(
|
||||
operation, state.setting, state.addon.enabled
|
||||
operation, state.setting,
|
||||
);
|
||||
this.store.dispatch(act);
|
||||
this.store.dispatch(inputActions.clearKeys());
|
||||
|
|
|
@ -5,15 +5,15 @@ import * as consoleFrames from '../../console-frames';
|
|||
import * as messages from '../../../shared/messages';
|
||||
import MessageListener from '../../MessageListener';
|
||||
import * as scrolls from '../../scrolls';
|
||||
import AddonEnabledUseCase from '../../usecases/AddonEnabledUseCase';
|
||||
|
||||
let addonEnabledUseCase = new AddonEnabledUseCase();
|
||||
|
||||
export default class TopContent {
|
||||
private win: Window;
|
||||
|
||||
private store: any;
|
||||
|
||||
constructor(win: Window, store: any) {
|
||||
this.win = win;
|
||||
this.store = store;
|
||||
|
||||
new CommonComponent(win, store); // eslint-disable-line no-new
|
||||
new FollowController(win, store); // eslint-disable-line no-new
|
||||
|
@ -36,14 +36,11 @@ export default class TopContent {
|
|||
}
|
||||
|
||||
onBackgroundMessage(message: messages.Message) {
|
||||
let addonState = this.store.getState().addon;
|
||||
let addonEnabled = addonEnabledUseCase.getEnabled();
|
||||
|
||||
switch (message.type) {
|
||||
case messages.ADDON_ENABLED_QUERY:
|
||||
return Promise.resolve({
|
||||
type: messages.ADDON_ENABLED_RESPONSE,
|
||||
enabled: addonState.enabled,
|
||||
});
|
||||
return Promise.resolve(addonEnabled);
|
||||
case messages.TAB_SCROLL_TO:
|
||||
return scrolls.scrollTo(message.x, message.y, false);
|
||||
}
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
import * as actions from '../actions';
|
||||
|
||||
export interface State {
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
const defaultState: State = {
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
export default function reducer(
|
||||
state: State = defaultState,
|
||||
action: actions.AddonAction,
|
||||
): State {
|
||||
switch (action.type) {
|
||||
case actions.ADDON_SET_ENABLED:
|
||||
return { ...state,
|
||||
enabled: action.enabled, };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
import { combineReducers } from 'redux';
|
||||
import addon, { State as AddonState } from './addon';
|
||||
import find, { State as FindState } from './find';
|
||||
import setting, { State as SettingState } from './setting';
|
||||
import input, { State as InputState } from './input';
|
||||
|
@ -8,7 +7,6 @@ import followController, { State as FollowControllerState }
|
|||
import mark, { State as MarkState } from './mark';
|
||||
|
||||
export interface State {
|
||||
addon: AddonState;
|
||||
find: FindState;
|
||||
setting: SettingState;
|
||||
input: InputState;
|
||||
|
@ -17,5 +15,5 @@ export interface State {
|
|||
}
|
||||
|
||||
export default combineReducers({
|
||||
addon, find, setting, input, followController, mark,
|
||||
find, setting, input, followController, mark,
|
||||
});
|
||||
|
|
19
src/content/repositories/AddonEnabledRepository.ts
Normal file
19
src/content/repositories/AddonEnabledRepository.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
let enabled: boolean = false;
|
||||
|
||||
export default interface AddonEnabledRepository {
|
||||
set(on: boolean): void;
|
||||
|
||||
get(): boolean;
|
||||
|
||||
// eslint-disable-next-line semi
|
||||
}
|
||||
|
||||
export class AddonEnabledRepositoryImpl implements AddonEnabledRepository {
|
||||
set(on: boolean): void {
|
||||
enabled = on;
|
||||
}
|
||||
|
||||
get(): boolean {
|
||||
return enabled;
|
||||
}
|
||||
}
|
40
src/content/usecases/AddonEnabledUseCase.ts
Normal file
40
src/content/usecases/AddonEnabledUseCase.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
import AddonIndicatorClient, { AddonIndicatorClientImpl }
|
||||
from '../client/AddonIndicatorClient';
|
||||
import AddonEnabledRepository, { AddonEnabledRepositoryImpl }
|
||||
from '../repositories/AddonEnabledRepository';
|
||||
|
||||
export default class AddonEnabledUseCase {
|
||||
private indicator: AddonIndicatorClient;
|
||||
|
||||
private repository: AddonEnabledRepository;
|
||||
|
||||
constructor({
|
||||
indicator = new AddonIndicatorClientImpl(),
|
||||
repository = new AddonEnabledRepositoryImpl(),
|
||||
} = {}) {
|
||||
this.indicator = indicator;
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
async enable(): Promise<void> {
|
||||
await this.setEnabled(true);
|
||||
}
|
||||
|
||||
async disable(): Promise<void> {
|
||||
await this.setEnabled(false);
|
||||
}
|
||||
|
||||
async toggle(): Promise<void> {
|
||||
let current = this.repository.get();
|
||||
await this.setEnabled(!current);
|
||||
}
|
||||
|
||||
getEnabled(): boolean {
|
||||
return this.repository.get();
|
||||
}
|
||||
|
||||
private async setEnabled(on: boolean): Promise<void> {
|
||||
this.repository.set(on);
|
||||
await this.indicator.setEnabled(on);
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
import * as actions from 'content/actions';
|
||||
import addonReducer from 'content/reducers/addon';
|
||||
|
||||
describe("addon reducer", () => {
|
||||
it('return the initial state', () => {
|
||||
let state = addonReducer(undefined, {});
|
||||
expect(state).to.have.property('enabled', true);
|
||||
});
|
||||
|
||||
it('return next state for ADDON_SET_ENABLED', () => {
|
||||
let action = { type: actions.ADDON_SET_ENABLED, enabled: true };
|
||||
let prev = { enabled: false };
|
||||
let state = addonReducer(prev, action);
|
||||
|
||||
expect(state.enabled).is.equal(true);
|
||||
});
|
||||
});
|
15
test/content/repositories/AddonEnabledRepository.test.ts
Normal file
15
test/content/repositories/AddonEnabledRepository.test.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { AddonEnabledRepositoryImpl } from '../../../src/content/repositories/AddonEnabledRepository';
|
||||
import { expect } from 'chai';
|
||||
|
||||
describe('AddonEnabledRepositoryImpl', () => {
|
||||
it('updates and gets current value', () => {
|
||||
let sut = new AddonEnabledRepositoryImpl();
|
||||
|
||||
sut.set(true);
|
||||
expect(sut.get()).to.be.true;
|
||||
|
||||
sut.set(false);
|
||||
expect(sut.get()).to.be.false;
|
||||
});
|
||||
});
|
||||
|
90
test/content/usecases/AddonEnabledUseCase.test.ts
Normal file
90
test/content/usecases/AddonEnabledUseCase.test.ts
Normal file
|
@ -0,0 +1,90 @@
|
|||
import AddonEnabledRepository from '../../../src/content/repositories/AddonEnabledRepository';
|
||||
import AddonEnabledUseCase from '../../../src/content/usecases/AddonEnabledUseCase';
|
||||
import AddonIndicatorClient from '../../../src/content/client/AddonIndicatorClient';
|
||||
import { expect } from 'chai';
|
||||
|
||||
class MockAddonEnabledRepository implements AddonEnabledRepository {
|
||||
private enabled: boolean;
|
||||
|
||||
constructor(init: boolean) {
|
||||
this.enabled = init;
|
||||
}
|
||||
|
||||
set(on: boolean): void {
|
||||
this.enabled = on;
|
||||
}
|
||||
|
||||
get(): boolean {
|
||||
return this.enabled;
|
||||
}
|
||||
}
|
||||
|
||||
class MockAddonIndicatorClient implements AddonIndicatorClient {
|
||||
public enabled: boolean;
|
||||
|
||||
constructor(init: boolean) {
|
||||
this.enabled = init;
|
||||
}
|
||||
|
||||
async setEnabled(enabled: boolean): Promise<void> {
|
||||
this.enabled = enabled;
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
describe('AddonEnabledUseCase', () => {
|
||||
let repository: MockAddonEnabledRepository;
|
||||
let indicator: MockAddonIndicatorClient;
|
||||
let sut: AddonEnabledUseCase;
|
||||
|
||||
beforeEach(() => {
|
||||
repository = new MockAddonEnabledRepository(true);
|
||||
indicator = new MockAddonIndicatorClient(false);
|
||||
sut = new AddonEnabledUseCase({ repository, indicator });
|
||||
});
|
||||
|
||||
describe('#enable', () => {
|
||||
it('store and indicate as enabled', async() => {
|
||||
await sut.enable();
|
||||
|
||||
expect(repository.get()).to.be.true;
|
||||
expect(indicator.enabled).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('#disable', async() => {
|
||||
it('store and indicate as disabled', async() => {
|
||||
await sut.disable();
|
||||
|
||||
expect(repository.get()).to.be.false;
|
||||
expect(indicator.enabled).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('#toggle', () => {
|
||||
it('toggled enabled and disabled', async() => {
|
||||
repository.set(true);
|
||||
await sut.toggle();
|
||||
|
||||
expect(repository.get()).to.be.false;
|
||||
expect(indicator.enabled).to.be.false;
|
||||
|
||||
repository.set(false);
|
||||
|
||||
await sut.toggle();
|
||||
|
||||
expect(repository.get()).to.be.true;
|
||||
expect(indicator.enabled).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getEnabled', () => {
|
||||
it('returns current addon enabled', () => {
|
||||
repository.set(true);
|
||||
expect(sut.getEnabled()).to.be.true;
|
||||
|
||||
repository.set(false);
|
||||
expect(sut.getEnabled()).to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
Reference in a new issue