Add WidgetManager tests
parent
bdb2bb294d
commit
f23418020a
|
@ -17,7 +17,7 @@
|
||||||
"package:chrome": "npm run build:app && web-ext build -s ./dist/webext/ -i app/manifest.json -i app/index.html -i manifest.firefox.json -n chrome.zip",
|
"package:chrome": "npm run build:app && web-ext build -s ./dist/webext/ -i app/manifest.json -i app/index.html -i manifest.firefox.json -n chrome.zip",
|
||||||
"package:firefox": "npm run build:app && cp src/webext/manifest.firefox.json dist/webext/manifest.json && web-ext build -s ./dist/webext/ -i app/manifest.json -i manifest.firefox.json -i app/index.html -n firefox.zip",
|
"package:firefox": "npm run build:app && cp src/webext/manifest.firefox.json dist/webext/manifest.json && web-ext build -s ./dist/webext/ -i app/manifest.json -i manifest.firefox.json -i app/index.html -n firefox.zip",
|
||||||
"lint": "eslint 'src/**/*.{js,ts,tsx}' --fix",
|
"lint": "eslint 'src/**/*.{js,ts,tsx}' --fix",
|
||||||
"test": "NODE_PATH=src mocha -r ts-node/register tests/**/*.test.ts",
|
"test": "TS_NODE_FILES=1 NODE_PATH=src mocha -r ts-node/register tests/**/*.test.ts",
|
||||||
"coverage": "nyc -r lcov -e .ts -x \"*.test.ts\" npm run test",
|
"coverage": "nyc -r lcov -e .ts -x \"*.test.ts\" npm run test",
|
||||||
"trans:extract": "formatjs extract 'src/app/**/*.ts*' --out-file src/app/locale/en.json --id-interpolation-pattern '[sha512:contenthash:base64:6]' --format utils/translation-format.js && node utils/update_translations.js",
|
"trans:extract": "formatjs extract 'src/app/**/*.ts*' --out-file src/app/locale/en.json --id-interpolation-pattern '[sha512:contenthash:base64:6]' --format utils/translation-format.js && node utils/update_translations.js",
|
||||||
"trans:compile": "node utils/compile_translations.js"
|
"trans:compile": "node utils/compile_translations.js"
|
||||||
|
|
|
@ -119,7 +119,7 @@ class DelegateStorage implements IStorage {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (typeof browser === 'undefined' &&
|
if (typeof browser === 'undefined' && typeof navigator !== "undefined" &&
|
||||||
navigator.storage && navigator.storage.persist) {
|
navigator.storage && navigator.storage.persist) {
|
||||||
navigator.storage.persist()
|
navigator.storage.persist()
|
||||||
.then((isPersisted) =>
|
.then((isPersisted) =>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { storage } from "./Storage";
|
import { IStorage } from "./Storage";
|
||||||
import { Vector2 } from "./utils/Vector2";
|
import { Vector2 } from "./utils/Vector2";
|
||||||
import { WidgetTypes } from "./widgets";
|
import { WidgetTypes } from "./widgets";
|
||||||
import { getInitialTheme, Widget } from "./Widget";
|
import { getInitialTheme, Widget } from "./Widget";
|
||||||
|
@ -13,10 +13,10 @@ export class WidgetManager {
|
||||||
|
|
||||||
widgets: (Widget<any>)[] = [];
|
widgets: (Widget<any>)[] = [];
|
||||||
|
|
||||||
constructor() {}
|
constructor(private storage: IStorage) {}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
const json = await storage.get<Widget<any>[]>("widgets");
|
const json = await this.storage.get<Widget<any>[]>("widgets");
|
||||||
if (!json) {
|
if (!json) {
|
||||||
this.resetToDefault();
|
this.resetToDefault();
|
||||||
return;
|
return;
|
||||||
|
@ -43,7 +43,7 @@ export class WidgetManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
storage.set("widgets", this.widgets);
|
this.storage.set("widgets", this.widgets);
|
||||||
}
|
}
|
||||||
|
|
||||||
resetToDefault() {
|
resetToDefault() {
|
||||||
|
@ -52,11 +52,11 @@ export class WidgetManager {
|
||||||
"HelpAbout", "Weather", "Feed", "Notes"].forEach(this.createWidget.bind(this));
|
"HelpAbout", "Weather", "Feed", "Notes"].forEach(this.createWidget.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
createWidget(type: string) {
|
createWidget<T>(type: string): Widget<T> {
|
||||||
this.id_counter++;
|
this.id_counter++;
|
||||||
|
|
||||||
const widget_type = WidgetTypes[type];
|
const widget_type = WidgetTypes[type];
|
||||||
const widget = {
|
const widget: Widget<T> = {
|
||||||
id: this.id_counter,
|
id: this.id_counter,
|
||||||
type: type,
|
type: type,
|
||||||
position: undefined,
|
position: undefined,
|
||||||
|
@ -71,6 +71,8 @@ export class WidgetManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.save();
|
this.save();
|
||||||
|
|
||||||
|
return widget;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeWidget(id: number) {
|
removeWidget(id: number) {
|
||||||
|
|
|
@ -10,9 +10,10 @@ import { FormattedMessage, IntlProvider } from "react-intl";
|
||||||
import { getTranslation, getUserLocale } from "app/locale";
|
import { getTranslation, getUserLocale } from "app/locale";
|
||||||
import { applyTheme, ThemeConfig } from "./settings/ThemeSettings";
|
import { applyTheme, ThemeConfig } from "./settings/ThemeSettings";
|
||||||
import ReviewRequester from "./ReviewRequester";
|
import ReviewRequester from "./ReviewRequester";
|
||||||
|
import { storage } from "app/Storage";
|
||||||
|
|
||||||
|
|
||||||
const widgetManager = new WidgetManager();
|
const widgetManager = new WidgetManager(storage);
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [loadingRes,] = usePromise(async () => {
|
const [loadingRes,] = usePromise(async () => {
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
interface Config {
|
||||||
|
API_URL: string;
|
||||||
|
PROXY_URL: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare var config: Config;
|
||||||
|
declare const app_version: string;
|
|
@ -3,14 +3,13 @@ import { miscMessages } from "app/locale/common";
|
||||||
import { IntlShape, useIntl } from "react-intl";
|
import { IntlShape, useIntl } from "react-intl";
|
||||||
import { usePromise } from "./promises";
|
import { usePromise } from "./promises";
|
||||||
|
|
||||||
const proxy_url = config.PROXY_URL;
|
|
||||||
|
|
||||||
function makeProxy(url: string) {
|
function makeProxy(url: string) {
|
||||||
if (typeof browser !== 'undefined') {
|
if (typeof browser !== 'undefined') {
|
||||||
console.log("Detected running as webext");
|
console.log("Detected running as webext");
|
||||||
return url;
|
return url;
|
||||||
} else {
|
} else {
|
||||||
const ret = new URL(proxy_url);
|
const ret = new URL(config.PROXY_URL);
|
||||||
ret.searchParams.set("url", url);
|
ret.searchParams.set("url", url);
|
||||||
return ret.toString();
|
return ret.toString();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,3 @@
|
||||||
interface Config {
|
|
||||||
API_URL: string;
|
|
||||||
PROXY_URL: string;
|
|
||||||
PROXY_ALLOWED_HOSTS: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
const config: Config;
|
|
||||||
const app_version: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { render } from "react-dom";
|
import { render } from "react-dom";
|
||||||
import App from "./components/App";
|
import App from "./components/App";
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { IStorage } from "app/Storage";
|
||||||
|
|
||||||
|
export default class DummyStorage implements IStorage {
|
||||||
|
private values: { [key: string]: any } = {};
|
||||||
|
|
||||||
|
async getAll(): Promise<{ [key: string]: any; }> {
|
||||||
|
return { ...this.values };
|
||||||
|
}
|
||||||
|
|
||||||
|
async get<T>(key: string): Promise<T | null> {
|
||||||
|
return this.values[key] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async set<T>(key: string, value: T): Promise<void> {
|
||||||
|
this.values[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(key: string): Promise<void> {
|
||||||
|
delete this.values[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
async clear(): Promise<void> {
|
||||||
|
this.values = {}
|
||||||
|
}
|
||||||
|
}
|
|
@ -36,7 +36,10 @@ describe("WidgetLayouter", function() {
|
||||||
type: "Type",
|
type: "Type",
|
||||||
position: V(1, 1),
|
position: V(1, 1),
|
||||||
size: V(3, 3),
|
size: V(3, 3),
|
||||||
props: {}
|
props: {},
|
||||||
|
theme: {
|
||||||
|
showPanelBG: false
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
layouter.resolveAll([widget]);
|
layouter.resolveAll([widget]);
|
||||||
|
@ -52,21 +55,30 @@ describe("WidgetLayouter", function() {
|
||||||
id: 1,
|
id: 1,
|
||||||
type: "Type",
|
type: "Type",
|
||||||
size: V(3, 3),
|
size: V(3, 3),
|
||||||
props: {}
|
props: {},
|
||||||
|
theme: {
|
||||||
|
showPanelBG: false
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const widget2 : Widget<any> = {
|
const widget2 : Widget<any> = {
|
||||||
id: 1,
|
id: 1,
|
||||||
type: "Type",
|
type: "Type",
|
||||||
size: V(15, 2),
|
size: V(15, 2),
|
||||||
props: {}
|
props: {},
|
||||||
|
theme: {
|
||||||
|
showPanelBG: false
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const widget3 : Widget<any> = {
|
const widget3 : Widget<any> = {
|
||||||
id: 1,
|
id: 1,
|
||||||
type: "Type",
|
type: "Type",
|
||||||
size: V(3, 3),
|
size: V(3, 3),
|
||||||
props: {}
|
props: {},
|
||||||
|
theme: {
|
||||||
|
showPanelBG: false
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
layouter.resolveAll([widget1, widget2, widget3]);
|
layouter.resolveAll([widget1, widget2, widget3]);
|
||||||
|
@ -86,7 +98,10 @@ describe("WidgetLayouter", function() {
|
||||||
id: 1,
|
id: 1,
|
||||||
type: "Type",
|
type: "Type",
|
||||||
size: V(3, 3),
|
size: V(3, 3),
|
||||||
props: {}
|
props: {},
|
||||||
|
theme: {
|
||||||
|
showPanelBG: false
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const widget2 : Widget<any> = {
|
const widget2 : Widget<any> = {
|
||||||
|
@ -94,7 +109,10 @@ describe("WidgetLayouter", function() {
|
||||||
type: "Type",
|
type: "Type",
|
||||||
position: V(0, 0),
|
position: V(0, 0),
|
||||||
size: V(15, 2),
|
size: V(15, 2),
|
||||||
props: {}
|
props: {},
|
||||||
|
theme: {
|
||||||
|
showPanelBG: false
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const widget3 : Widget<any> = {
|
const widget3 : Widget<any> = {
|
||||||
|
@ -102,7 +120,10 @@ describe("WidgetLayouter", function() {
|
||||||
type: "Type",
|
type: "Type",
|
||||||
position: V(3, 0),
|
position: V(3, 0),
|
||||||
size: V(3, 3),
|
size: V(3, 3),
|
||||||
props: {}
|
props: {},
|
||||||
|
theme: {
|
||||||
|
showPanelBG: false
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
layouter.resolveAll([widget1, widget2, widget3]);
|
layouter.resolveAll([widget1, widget2, widget3]);
|
|
@ -0,0 +1,123 @@
|
||||||
|
import { LinkBoxProps } from "app/components/LinkBox";
|
||||||
|
import { Vector2 } from "app/utils/Vector2";
|
||||||
|
import { WidgetManager } from "app/WidgetManager";
|
||||||
|
import { expect } from "chai";
|
||||||
|
import DummyStorage from "./DummyStorage";
|
||||||
|
|
||||||
|
describe("WidgetManager::create", () => {
|
||||||
|
it("createsDefaultWidgets", async () => {
|
||||||
|
const storage = new DummyStorage();
|
||||||
|
const wm = new WidgetManager(storage);
|
||||||
|
await wm.load();
|
||||||
|
expect(wm.widgets.length).to.equal(9);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("copiesProps", async () => {
|
||||||
|
const storage = new DummyStorage();
|
||||||
|
storage.set("widgets", [
|
||||||
|
{
|
||||||
|
id: "123",
|
||||||
|
type: "Notes",
|
||||||
|
props: {},
|
||||||
|
size: new Vector2(3, 5),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const wm = new WidgetManager(storage);
|
||||||
|
expect(wm.widgets.length).to.equal(0);
|
||||||
|
await wm.load();
|
||||||
|
expect(wm.widgets.length).to.equal(1);
|
||||||
|
|
||||||
|
const widget1 = wm.createWidget<LinkBoxProps>("Links");
|
||||||
|
const widget2 = wm.createWidget<LinkBoxProps>("Links");
|
||||||
|
expect(widget1.props.links.length).to.equal(6);
|
||||||
|
expect(widget2.props.links.length).to.equal(6);
|
||||||
|
|
||||||
|
widget1.props.links.push({
|
||||||
|
id: "123",
|
||||||
|
title: "Title",
|
||||||
|
url: "url",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(widget1.props.links.length).to.equal(7);
|
||||||
|
expect(widget2.props.links.length).to.equal(6);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("copiesTheme", async () => {
|
||||||
|
const storage = new DummyStorage();
|
||||||
|
storage.set("widgets", []);
|
||||||
|
const wm = new WidgetManager(storage);
|
||||||
|
await wm.load();
|
||||||
|
expect(wm.widgets.length).to.equal(0);
|
||||||
|
|
||||||
|
const widget1 = wm.createWidget<LinkBoxProps>("Links");
|
||||||
|
const widget2 = wm.createWidget<LinkBoxProps>("Links");
|
||||||
|
expect(widget1.theme.useIconBar).is.false;
|
||||||
|
widget1.theme.useIconBar = true;
|
||||||
|
expect(widget1.theme.useIconBar).is.true;
|
||||||
|
expect(widget2.theme.useIconBar).is.false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe("WidgetManager::load", () => {
|
||||||
|
it("adds theme", async () => {
|
||||||
|
const storage = new DummyStorage();
|
||||||
|
storage.set("widgets", [
|
||||||
|
{
|
||||||
|
id: "123",
|
||||||
|
type: "Notes",
|
||||||
|
props: {},
|
||||||
|
size: new Vector2(3, 5),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "124",
|
||||||
|
type: "Clock",
|
||||||
|
props: {},
|
||||||
|
size: new Vector2(3, 5),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const wm = new WidgetManager(storage);
|
||||||
|
expect(wm.widgets.length).to.equal(0);
|
||||||
|
await wm.load();
|
||||||
|
expect(wm.widgets.length).to.equal(2);
|
||||||
|
|
||||||
|
{
|
||||||
|
const notesWidget = wm.widgets[0];
|
||||||
|
expect(notesWidget.theme).not.null;
|
||||||
|
expect(notesWidget.theme.showPanelBG).to.be.true;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const clockWidget = wm.widgets[1];
|
||||||
|
expect(clockWidget.theme).not.null;
|
||||||
|
expect(clockWidget.theme.showPanelBG).to.be.false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("runs load hook", async () => {
|
||||||
|
const storage = new DummyStorage();
|
||||||
|
storage.set("widgets", [
|
||||||
|
{
|
||||||
|
id: "123",
|
||||||
|
type: "TopSites",
|
||||||
|
props: {
|
||||||
|
useIconBar: false,
|
||||||
|
},
|
||||||
|
size: new Vector2(3, 5),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const wm = new WidgetManager(storage);
|
||||||
|
expect(wm.widgets.length).to.equal(0);
|
||||||
|
await wm.load();
|
||||||
|
expect(wm.widgets.length).to.equal(1);
|
||||||
|
|
||||||
|
const widget = wm.widgets[0];
|
||||||
|
expect(widget.props).not.to.haveOwnProperty("useIconBar");
|
||||||
|
expect(widget.theme).not.null;
|
||||||
|
expect(widget.theme.showPanelBG).to.be.true;
|
||||||
|
expect(widget.theme.useIconBar).to.be.false;
|
||||||
|
});
|
||||||
|
});
|
|
@ -4,6 +4,7 @@
|
||||||
"module": "ES2015"
|
"module": "ES2015"
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"src/server"
|
"src/server",
|
||||||
|
"tests"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"typeRoots": [
|
"typeRoots": [
|
||||||
"./node_modules/@types",
|
"./node_modules/@types",
|
||||||
"./node_modules/web-ext-types"
|
"./node_modules/web-ext-types",
|
||||||
|
"src/app/custom.d.ts"
|
||||||
],
|
],
|
||||||
"baseUrl": "src",
|
"baseUrl": "src",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
@ -15,6 +16,6 @@
|
||||||
"resolveJsonModule": true
|
"resolveJsonModule": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src"
|
"src", "tests"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
"extends": "./tsconfig.json",
|
"extends": "./tsconfig.json",
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"src/app",
|
"src/app",
|
||||||
"src/webext"
|
"src/webext",
|
||||||
|
"tests"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
|
|
Loading…
Reference in New Issue