prettier
This commit is contained in:
parent
912813fd09
commit
fb6aff76d2
@ -27,17 +27,17 @@ import { platform, setupPlatform } from "./platform/api.js";
|
|||||||
import devAPI from "./platform/dev.js";
|
import devAPI from "./platform/dev.js";
|
||||||
import { wxMain } from "./wxBackend.js";
|
import { wxMain } from "./wxBackend.js";
|
||||||
|
|
||||||
console.log("Wallet setup for Dev API")
|
console.log("Wallet setup for Dev API");
|
||||||
setupPlatform(devAPI)
|
setupPlatform(devAPI);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
platform.registerOnInstalled(() => {
|
platform.registerOnInstalled(() => {
|
||||||
platform.openWalletPage("/welcome")
|
platform.openWalletPage("/welcome");
|
||||||
})
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
platform.notifyWhenAppIsReady(() => {
|
platform.notifyWhenAppIsReady(() => {
|
||||||
wxMain();
|
wxMain();
|
||||||
})
|
});
|
||||||
|
@ -28,22 +28,24 @@ import chromeAPI from "./platform/chrome.js";
|
|||||||
import firefoxAPI from "./platform/firefox.js";
|
import firefoxAPI from "./platform/firefox.js";
|
||||||
import { wxMain } from "./wxBackend.js";
|
import { wxMain } from "./wxBackend.js";
|
||||||
|
|
||||||
const isFirefox = typeof (window as any) !== 'undefined' && typeof (window as any)['InstallTrigger'] !== 'undefined'
|
const isFirefox =
|
||||||
|
typeof (window as any) !== "undefined" &&
|
||||||
|
typeof (window as any)["InstallTrigger"] !== "undefined";
|
||||||
|
|
||||||
// FIXME: create different entry point for any platform instead of
|
// FIXME: create different entry point for any platform instead of
|
||||||
// switching in runtime
|
// switching in runtime
|
||||||
if (isFirefox) {
|
if (isFirefox) {
|
||||||
console.log("Wallet setup for Firefox API")
|
console.log("Wallet setup for Firefox API");
|
||||||
setupPlatform(firefoxAPI)
|
setupPlatform(firefoxAPI);
|
||||||
} else {
|
} else {
|
||||||
console.log("Wallet setup for Chrome API")
|
console.log("Wallet setup for Chrome API");
|
||||||
setupPlatform(chromeAPI)
|
setupPlatform(chromeAPI);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
platform.registerOnInstalled(() => {
|
platform.registerOnInstalled(() => {
|
||||||
platform.openWalletPage("/welcome")
|
platform.openWalletPage("/welcome");
|
||||||
})
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
@ -51,4 +53,4 @@ try {
|
|||||||
// setGlobalLogLevelFromString("trace")
|
// setGlobalLogLevelFromString("trace")
|
||||||
platform.notifyWhenAppIsReady(() => {
|
platform.notifyWhenAppIsReady(() => {
|
||||||
wxMain();
|
wxMain();
|
||||||
})
|
});
|
||||||
|
@ -178,7 +178,7 @@ export class BrowserHttpLib implements HttpRequestLibrary {
|
|||||||
): Promise<HttpResponse> {
|
): Promise<HttpResponse> {
|
||||||
return this.fetch(url, {
|
return this.fetch(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {"Content-Type": "application/json"},
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
...opt,
|
...opt,
|
||||||
});
|
});
|
||||||
|
@ -29,7 +29,9 @@ interface Type {
|
|||||||
}
|
}
|
||||||
const Context = createContext<Type>({
|
const Context = createContext<Type>({
|
||||||
devMode: false,
|
devMode: false,
|
||||||
toggleDevMode: async () => { return; },
|
toggleDevMode: async () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const useDevContext = (): Type => useContext(Context);
|
export const useDevContext = (): Type => useContext(Context);
|
||||||
@ -55,7 +57,8 @@ export const DevContextProviderForTesting = ({
|
|||||||
export const DevContextProvider = ({ children }: { children: any }): VNode => {
|
export const DevContextProvider = ({ children }: { children: any }): VNode => {
|
||||||
const [value, setter] = useLocalStorage("devMode");
|
const [value, setter] = useLocalStorage("devMode");
|
||||||
const devMode = value === "true";
|
const devMode = value === "true";
|
||||||
const toggleDevMode = async (): Promise<void> => setter((v) => (!v ? "true" : undefined));
|
const toggleDevMode = async (): Promise<void> =>
|
||||||
|
setter((v) => (!v ? "true" : undefined));
|
||||||
children =
|
children =
|
||||||
children.length === 1 && typeof children === "function"
|
children.length === 1 && typeof children === "function"
|
||||||
? children({ devMode })
|
? children({ devMode })
|
||||||
|
@ -32,18 +32,31 @@ const Context = createContext<Type>({
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Inversion of control Context
|
* Inversion of control Context
|
||||||
*
|
*
|
||||||
* This context act as a proxy between API that need to be replaced in
|
* This context act as a proxy between API that need to be replaced in
|
||||||
* different environments
|
* different environments
|
||||||
*
|
*
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const useIocContext = (): Type => useContext(Context);
|
export const useIocContext = (): Type => useContext(Context);
|
||||||
|
|
||||||
export const IoCProviderForTesting = ({ value, children }: { value: Type, children: any }): VNode => {
|
export const IoCProviderForTesting = ({
|
||||||
|
value,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
value: Type;
|
||||||
|
children: any;
|
||||||
|
}): VNode => {
|
||||||
return h(Context.Provider, { value, children });
|
return h(Context.Provider, { value, children });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IoCProviderForRuntime = ({ children }: { children: any }): VNode => {
|
export const IoCProviderForRuntime = ({
|
||||||
return h(Context.Provider, { value: { findTalerUriInActiveTab: platform.findTalerUriInActiveTab }, children });
|
children,
|
||||||
|
}: {
|
||||||
|
children: any;
|
||||||
|
}): VNode => {
|
||||||
|
return h(Context.Provider, {
|
||||||
|
value: { findTalerUriInActiveTab: platform.findTalerUriInActiveTab },
|
||||||
|
children,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -27,7 +27,7 @@ import { strings } from "../i18n/strings.js";
|
|||||||
|
|
||||||
interface Type {
|
interface Type {
|
||||||
lang: string;
|
lang: string;
|
||||||
supportedLang: { [id in keyof typeof supportedLang]: string }
|
supportedLang: { [id in keyof typeof supportedLang]: string };
|
||||||
changeLanguage: (l: string) => void;
|
changeLanguage: (l: string) => void;
|
||||||
i18n: typeof i18n;
|
i18n: typeof i18n;
|
||||||
isSaved: boolean;
|
isSaved: boolean;
|
||||||
@ -47,7 +47,6 @@ const supportedLang = {
|
|||||||
navigator: "Defined by navigator",
|
navigator: "Defined by navigator",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const initial = {
|
const initial = {
|
||||||
lang: "en",
|
lang: "en",
|
||||||
supportedLang,
|
supportedLang,
|
||||||
@ -84,7 +83,10 @@ export const TranslationProvider = ({
|
|||||||
} else {
|
} else {
|
||||||
setupI18n(lang, strings);
|
setupI18n(lang, strings);
|
||||||
}
|
}
|
||||||
return h(Context.Provider, { value: { lang, changeLanguage, supportedLang, i18n, isSaved }, children });
|
return h(Context.Provider, {
|
||||||
|
value: { lang, changeLanguage, supportedLang, i18n, isSaved },
|
||||||
|
children,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useTranslationContext = (): Type => useContext(Context);
|
export const useTranslationContext = (): Type => useContext(Context);
|
||||||
|
@ -26,58 +26,61 @@ import { useComponentState } from "./Deposit.jsx";
|
|||||||
|
|
||||||
describe("Deposit CTA states", () => {
|
describe("Deposit CTA states", () => {
|
||||||
it("should tell the user that the URI is missing", async () => {
|
it("should tell the user that the URI is missing", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
useComponentState(undefined, undefined, {
|
mountHook(() =>
|
||||||
prepareRefund: async () => ({}),
|
useComponentState(undefined, undefined, {
|
||||||
applyRefund: async () => ({}),
|
prepareRefund: async () => ({}),
|
||||||
onUpdateNotification: async () => ({})
|
applyRefund: async () => ({}),
|
||||||
} as any),
|
onUpdateNotification: async () => ({}),
|
||||||
);
|
} as any),
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
expect(status).equals('loading')
|
expect(status).equals("loading");
|
||||||
expect(hook).undefined;
|
expect(hook).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
|
|
||||||
expect(status).equals('loading')
|
expect(status).equals("loading");
|
||||||
if (!hook) expect.fail();
|
if (!hook) expect.fail();
|
||||||
if (!hook.hasError) expect.fail();
|
if (!hook.hasError) expect.fail();
|
||||||
if (hook.operational) expect.fail();
|
if (hook.operational) expect.fail();
|
||||||
expect(hook.message).eq("ERROR_NO-URI-FOR-DEPOSIT");
|
expect(hook.message).eq("ERROR_NO-URI-FOR-DEPOSIT");
|
||||||
}
|
}
|
||||||
|
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be ready after loading", async () => {
|
it("should be ready after loading", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
useComponentState("payto://refund/asdasdas", "EUR:1", {
|
mountHook(() =>
|
||||||
prepareDeposit: async () => ({
|
useComponentState("payto://refund/asdasdas", "EUR:1", {
|
||||||
effectiveDepositAmount: Amounts.parseOrThrow("EUR:1"),
|
prepareDeposit: async () =>
|
||||||
totalDepositCost: Amounts.parseOrThrow("EUR:1.2")
|
({
|
||||||
} as PrepareDepositResponse as any),
|
effectiveDepositAmount: Amounts.parseOrThrow("EUR:1"),
|
||||||
createDepositGroup: async () => ({}),
|
totalDepositCost: Amounts.parseOrThrow("EUR:1.2"),
|
||||||
} as any),
|
} as PrepareDepositResponse as any),
|
||||||
);
|
createDepositGroup: async () => ({}),
|
||||||
|
} as any),
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
expect(status).equals('loading')
|
expect(status).equals("loading");
|
||||||
expect(hook).undefined;
|
expect(hook).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const state = getLastResultOrThrow()
|
const state = getLastResultOrThrow();
|
||||||
|
|
||||||
if (state.status !== 'ready') expect.fail();
|
if (state.status !== "ready") expect.fail();
|
||||||
if (state.hook) expect.fail();
|
if (state.hook) expect.fail();
|
||||||
expect(state.confirm.onClick).not.undefined;
|
expect(state.confirm.onClick).not.undefined;
|
||||||
expect(state.cost).deep.eq(Amounts.parseOrThrow("EUR:1.2"));
|
expect(state.cost).deep.eq(Amounts.parseOrThrow("EUR:1.2"));
|
||||||
@ -85,8 +88,6 @@ describe("Deposit CTA states", () => {
|
|||||||
expect(state.effective).deep.eq(Amounts.parseOrThrow("EUR:1"));
|
expect(state.effective).deep.eq(Amounts.parseOrThrow("EUR:1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
});
|
|
||||||
|
@ -19,7 +19,16 @@
|
|||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AmountJson, Amounts, BalancesResponse, ConfirmPayResult, ConfirmPayResultType, NotificationType, PreparePayResult, PreparePayResultType } from "@gnu-taler/taler-util";
|
import {
|
||||||
|
AmountJson,
|
||||||
|
Amounts,
|
||||||
|
BalancesResponse,
|
||||||
|
ConfirmPayResult,
|
||||||
|
ConfirmPayResultType,
|
||||||
|
NotificationType,
|
||||||
|
PreparePayResult,
|
||||||
|
PreparePayResultType,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
import { mountHook } from "../test-utils.js";
|
import { mountHook } from "../test-utils.js";
|
||||||
import * as wxApi from "../wxApi.js";
|
import * as wxApi from "../wxApi.js";
|
||||||
@ -29,8 +38,8 @@ const nullFunction: any = () => null;
|
|||||||
type VoidFunction = () => void;
|
type VoidFunction = () => void;
|
||||||
|
|
||||||
type Subs = {
|
type Subs = {
|
||||||
[key in NotificationType]?: VoidFunction
|
[key in NotificationType]?: VoidFunction;
|
||||||
}
|
};
|
||||||
|
|
||||||
export class SubsHandler {
|
export class SubsHandler {
|
||||||
private subs: Subs = {};
|
private subs: Subs = {};
|
||||||
@ -39,311 +48,340 @@ export class SubsHandler {
|
|||||||
this.saveSubscription = this.saveSubscription.bind(this);
|
this.saveSubscription = this.saveSubscription.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
saveSubscription(messageTypes: NotificationType[], callback: VoidFunction): VoidFunction {
|
saveSubscription(
|
||||||
messageTypes.forEach(m => {
|
messageTypes: NotificationType[],
|
||||||
|
callback: VoidFunction,
|
||||||
|
): VoidFunction {
|
||||||
|
messageTypes.forEach((m) => {
|
||||||
this.subs[m] = callback;
|
this.subs[m] = callback;
|
||||||
})
|
});
|
||||||
return nullFunction;
|
return nullFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyEvent(event: NotificationType): void {
|
notifyEvent(event: NotificationType): void {
|
||||||
const cb = this.subs[event];
|
const cb = this.subs[event];
|
||||||
if (cb === undefined) expect.fail(`Expected to have a subscription for ${event}`);
|
if (cb === undefined)
|
||||||
cb()
|
expect.fail(`Expected to have a subscription for ${event}`);
|
||||||
|
cb();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
describe("Pay CTA states", () => {
|
describe("Pay CTA states", () => {
|
||||||
it("should tell the user that the URI is missing", async () => {
|
it("should tell the user that the URI is missing", async () => {
|
||||||
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState(undefined, {
|
useComponentState(undefined, {
|
||||||
onUpdateNotification: nullFunction,
|
onUpdateNotification: nullFunction,
|
||||||
} as Partial<typeof wxApi> as any)
|
} as Partial<typeof wxApi> as any),
|
||||||
);
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
expect(status).equals('loading')
|
expect(status).equals("loading");
|
||||||
expect(hook).undefined;
|
expect(hook).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
|
|
||||||
expect(status).equals('loading')
|
expect(status).equals("loading");
|
||||||
if (hook === undefined) expect.fail()
|
if (hook === undefined) expect.fail();
|
||||||
expect(hook.hasError).true;
|
expect(hook.hasError).true;
|
||||||
expect(hook.operational).false;
|
expect(hook.operational).false;
|
||||||
}
|
}
|
||||||
|
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should response with no balance", async () => {
|
it("should response with no balance", async () => {
|
||||||
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState('taller://pay', {
|
useComponentState("taller://pay", {
|
||||||
onUpdateNotification: nullFunction,
|
onUpdateNotification: nullFunction,
|
||||||
preparePay: async () => ({
|
preparePay: async () =>
|
||||||
amountRaw: 'USD:10',
|
({
|
||||||
status: PreparePayResultType.InsufficientBalance,
|
amountRaw: "USD:10",
|
||||||
} as Partial<PreparePayResult>),
|
status: PreparePayResultType.InsufficientBalance,
|
||||||
getBalance: async () => ({
|
} as Partial<PreparePayResult>),
|
||||||
balances: []
|
getBalance: async () =>
|
||||||
} as Partial<BalancesResponse>),
|
({
|
||||||
} as Partial<typeof wxApi> as any)
|
balances: [],
|
||||||
);
|
} as Partial<BalancesResponse>),
|
||||||
|
} as Partial<typeof wxApi> as any),
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
expect(status).equals('loading')
|
expect(status).equals("loading");
|
||||||
expect(hook).undefined;
|
expect(hook).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate();
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const r = getLastResultOrThrow()
|
const r = getLastResultOrThrow();
|
||||||
if (r.status !== 'ready') expect.fail()
|
if (r.status !== "ready") expect.fail();
|
||||||
expect(r.balance).undefined;
|
expect(r.balance).undefined;
|
||||||
expect(r.amount).deep.equal(Amounts.parseOrThrow('USD:10'))
|
expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:10"));
|
||||||
expect(r.payHandler.onClick).undefined;
|
expect(r.payHandler.onClick).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not be able to pay if there is no enough balance", async () => {
|
it("should not be able to pay if there is no enough balance", async () => {
|
||||||
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState('taller://pay', {
|
useComponentState("taller://pay", {
|
||||||
onUpdateNotification: nullFunction,
|
onUpdateNotification: nullFunction,
|
||||||
preparePay: async () => ({
|
preparePay: async () =>
|
||||||
amountRaw: 'USD:10',
|
({
|
||||||
status: PreparePayResultType.InsufficientBalance,
|
amountRaw: "USD:10",
|
||||||
} as Partial<PreparePayResult>),
|
status: PreparePayResultType.InsufficientBalance,
|
||||||
getBalance: async () => ({
|
} as Partial<PreparePayResult>),
|
||||||
balances: [{
|
getBalance: async () =>
|
||||||
available: 'USD:5'
|
({
|
||||||
}]
|
balances: [
|
||||||
} as Partial<BalancesResponse>),
|
{
|
||||||
} as Partial<typeof wxApi> as any)
|
available: "USD:5",
|
||||||
);
|
},
|
||||||
|
],
|
||||||
|
} as Partial<BalancesResponse>),
|
||||||
|
} as Partial<typeof wxApi> as any),
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
expect(status).equals('loading')
|
expect(status).equals("loading");
|
||||||
expect(hook).undefined;
|
expect(hook).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate();
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const r = getLastResultOrThrow()
|
const r = getLastResultOrThrow();
|
||||||
if (r.status !== 'ready') expect.fail()
|
if (r.status !== "ready") expect.fail();
|
||||||
expect(r.balance).deep.equal(Amounts.parseOrThrow('USD:5'));
|
expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:5"));
|
||||||
expect(r.amount).deep.equal(Amounts.parseOrThrow('USD:10'))
|
expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:10"));
|
||||||
expect(r.payHandler.onClick).undefined;
|
expect(r.payHandler.onClick).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be able to pay (without fee)", async () => {
|
it("should be able to pay (without fee)", async () => {
|
||||||
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState('taller://pay', {
|
useComponentState("taller://pay", {
|
||||||
onUpdateNotification: nullFunction,
|
onUpdateNotification: nullFunction,
|
||||||
preparePay: async () => ({
|
preparePay: async () =>
|
||||||
amountRaw: 'USD:10',
|
({
|
||||||
amountEffective: 'USD:10',
|
amountRaw: "USD:10",
|
||||||
status: PreparePayResultType.PaymentPossible,
|
amountEffective: "USD:10",
|
||||||
} as Partial<PreparePayResult>),
|
status: PreparePayResultType.PaymentPossible,
|
||||||
getBalance: async () => ({
|
} as Partial<PreparePayResult>),
|
||||||
balances: [{
|
getBalance: async () =>
|
||||||
available: 'USD:15'
|
({
|
||||||
}]
|
balances: [
|
||||||
} as Partial<BalancesResponse>),
|
{
|
||||||
} as Partial<typeof wxApi> as any)
|
available: "USD:15",
|
||||||
);
|
},
|
||||||
|
],
|
||||||
|
} as Partial<BalancesResponse>),
|
||||||
|
} as Partial<typeof wxApi> as any),
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
expect(status).equals('loading')
|
expect(status).equals("loading");
|
||||||
expect(hook).undefined;
|
expect(hook).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate();
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const r = getLastResultOrThrow()
|
const r = getLastResultOrThrow();
|
||||||
if (r.status !== 'ready') expect.fail()
|
if (r.status !== "ready") expect.fail();
|
||||||
expect(r.balance).deep.equal(Amounts.parseOrThrow('USD:15'));
|
expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15"));
|
||||||
expect(r.amount).deep.equal(Amounts.parseOrThrow('USD:10'))
|
expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:10"));
|
||||||
expect(r.totalFees).deep.equal(Amounts.parseOrThrow('USD:0'))
|
expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:0"));
|
||||||
expect(r.payHandler.onClick).not.undefined;
|
expect(r.payHandler.onClick).not.undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be able to pay (with fee)", async () => {
|
it("should be able to pay (with fee)", async () => {
|
||||||
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState('taller://pay', {
|
useComponentState("taller://pay", {
|
||||||
onUpdateNotification: nullFunction,
|
onUpdateNotification: nullFunction,
|
||||||
preparePay: async () => ({
|
preparePay: async () =>
|
||||||
amountRaw: 'USD:9',
|
({
|
||||||
amountEffective: 'USD:10',
|
amountRaw: "USD:9",
|
||||||
status: PreparePayResultType.PaymentPossible,
|
amountEffective: "USD:10",
|
||||||
} as Partial<PreparePayResult>),
|
status: PreparePayResultType.PaymentPossible,
|
||||||
getBalance: async () => ({
|
} as Partial<PreparePayResult>),
|
||||||
balances: [{
|
getBalance: async () =>
|
||||||
available: 'USD:15'
|
({
|
||||||
}]
|
balances: [
|
||||||
} as Partial<BalancesResponse>),
|
{
|
||||||
} as Partial<typeof wxApi> as any)
|
available: "USD:15",
|
||||||
);
|
},
|
||||||
|
],
|
||||||
|
} as Partial<BalancesResponse>),
|
||||||
|
} as Partial<typeof wxApi> as any),
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
expect(status).equals('loading')
|
expect(status).equals("loading");
|
||||||
expect(hook).undefined;
|
expect(hook).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate();
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const r = getLastResultOrThrow()
|
const r = getLastResultOrThrow();
|
||||||
if (r.status !== 'ready') expect.fail()
|
if (r.status !== "ready") expect.fail();
|
||||||
expect(r.balance).deep.equal(Amounts.parseOrThrow('USD:15'));
|
expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15"));
|
||||||
expect(r.amount).deep.equal(Amounts.parseOrThrow('USD:9'))
|
expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9"));
|
||||||
expect(r.totalFees).deep.equal(Amounts.parseOrThrow('USD:1'))
|
expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1"));
|
||||||
expect(r.payHandler.onClick).not.undefined;
|
expect(r.payHandler.onClick).not.undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should get confirmation done after pay successfully", async () => {
|
it("should get confirmation done after pay successfully", async () => {
|
||||||
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState('taller://pay', {
|
useComponentState("taller://pay", {
|
||||||
onUpdateNotification: nullFunction,
|
onUpdateNotification: nullFunction,
|
||||||
preparePay: async () => ({
|
preparePay: async () =>
|
||||||
amountRaw: 'USD:9',
|
({
|
||||||
amountEffective: 'USD:10',
|
amountRaw: "USD:9",
|
||||||
status: PreparePayResultType.PaymentPossible,
|
amountEffective: "USD:10",
|
||||||
} as Partial<PreparePayResult>),
|
status: PreparePayResultType.PaymentPossible,
|
||||||
getBalance: async () => ({
|
} as Partial<PreparePayResult>),
|
||||||
balances: [{
|
getBalance: async () =>
|
||||||
available: 'USD:15'
|
({
|
||||||
}]
|
balances: [
|
||||||
} as Partial<BalancesResponse>),
|
{
|
||||||
confirmPay: async () => ({
|
available: "USD:15",
|
||||||
type: ConfirmPayResultType.Done,
|
},
|
||||||
contractTerms: {}
|
],
|
||||||
} as Partial<ConfirmPayResult>),
|
} as Partial<BalancesResponse>),
|
||||||
} as Partial<typeof wxApi> as any)
|
confirmPay: async () =>
|
||||||
);
|
({
|
||||||
|
type: ConfirmPayResultType.Done,
|
||||||
|
contractTerms: {},
|
||||||
|
} as Partial<ConfirmPayResult>),
|
||||||
|
} as Partial<typeof wxApi> as any),
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
expect(status).equals('loading')
|
expect(status).equals("loading");
|
||||||
expect(hook).undefined;
|
expect(hook).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate();
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const r = getLastResultOrThrow()
|
const r = getLastResultOrThrow();
|
||||||
if (r.status !== 'ready') expect.fail()
|
if (r.status !== "ready") expect.fail();
|
||||||
expect(r.balance).deep.equal(Amounts.parseOrThrow('USD:15'));
|
expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15"));
|
||||||
expect(r.amount).deep.equal(Amounts.parseOrThrow('USD:9'))
|
expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9"));
|
||||||
expect(r.totalFees).deep.equal(Amounts.parseOrThrow('USD:1'))
|
expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1"));
|
||||||
if (r.payHandler.onClick === undefined) expect.fail();
|
if (r.payHandler.onClick === undefined) expect.fail();
|
||||||
r.payHandler.onClick()
|
r.payHandler.onClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate();
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const r = getLastResultOrThrow()
|
const r = getLastResultOrThrow();
|
||||||
if (r.status !== 'confirmed') expect.fail()
|
if (r.status !== "confirmed") expect.fail();
|
||||||
expect(r.balance).deep.equal(Amounts.parseOrThrow('USD:15'));
|
expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15"));
|
||||||
expect(r.amount).deep.equal(Amounts.parseOrThrow('USD:9'))
|
expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9"));
|
||||||
expect(r.totalFees).deep.equal(Amounts.parseOrThrow('USD:1'))
|
expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1"));
|
||||||
if (r.payResult.type !== ConfirmPayResultType.Done) expect.fail();
|
if (r.payResult.type !== ConfirmPayResultType.Done) expect.fail();
|
||||||
expect(r.payResult.contractTerms).not.undefined;
|
expect(r.payResult.contractTerms).not.undefined;
|
||||||
expect(r.payHandler.onClick).undefined;
|
expect(r.payHandler.onClick).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not stay in ready state after pay with error", async () => {
|
it("should not stay in ready state after pay with error", async () => {
|
||||||
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState('taller://pay', {
|
useComponentState("taller://pay", {
|
||||||
onUpdateNotification: nullFunction,
|
onUpdateNotification: nullFunction,
|
||||||
preparePay: async () => ({
|
preparePay: async () =>
|
||||||
amountRaw: 'USD:9',
|
({
|
||||||
amountEffective: 'USD:10',
|
amountRaw: "USD:9",
|
||||||
status: PreparePayResultType.PaymentPossible,
|
amountEffective: "USD:10",
|
||||||
} as Partial<PreparePayResult>),
|
status: PreparePayResultType.PaymentPossible,
|
||||||
getBalance: async () => ({
|
} as Partial<PreparePayResult>),
|
||||||
balances: [{
|
getBalance: async () =>
|
||||||
available: 'USD:15'
|
({
|
||||||
}]
|
balances: [
|
||||||
} as Partial<BalancesResponse>),
|
{
|
||||||
confirmPay: async () => ({
|
available: "USD:15",
|
||||||
type: ConfirmPayResultType.Pending,
|
},
|
||||||
lastError: { code: 1 },
|
],
|
||||||
} as Partial<ConfirmPayResult>),
|
} as Partial<BalancesResponse>),
|
||||||
} as Partial<typeof wxApi> as any)
|
confirmPay: async () =>
|
||||||
);
|
({
|
||||||
|
type: ConfirmPayResultType.Pending,
|
||||||
|
lastError: { code: 1 },
|
||||||
|
} as Partial<ConfirmPayResult>),
|
||||||
|
} as Partial<typeof wxApi> as any),
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
expect(status).equals('loading')
|
expect(status).equals("loading");
|
||||||
expect(hook).undefined;
|
expect(hook).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate();
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const r = getLastResultOrThrow()
|
const r = getLastResultOrThrow();
|
||||||
if (r.status !== 'ready') expect.fail()
|
if (r.status !== "ready") expect.fail();
|
||||||
expect(r.balance).deep.equal(Amounts.parseOrThrow('USD:15'));
|
expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15"));
|
||||||
expect(r.amount).deep.equal(Amounts.parseOrThrow('USD:9'))
|
expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9"));
|
||||||
expect(r.totalFees).deep.equal(Amounts.parseOrThrow('USD:1'))
|
expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1"));
|
||||||
if (r.payHandler.onClick === undefined) expect.fail();
|
if (r.payHandler.onClick === undefined) expect.fail();
|
||||||
r.payHandler.onClick()
|
r.payHandler.onClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate();
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const r = getLastResultOrThrow()
|
const r = getLastResultOrThrow();
|
||||||
if (r.status !== 'ready') expect.fail()
|
if (r.status !== "ready") expect.fail();
|
||||||
expect(r.balance).deep.equal(Amounts.parseOrThrow('USD:15'));
|
expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15"));
|
||||||
expect(r.amount).deep.equal(Amounts.parseOrThrow('USD:9'))
|
expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9"));
|
||||||
expect(r.totalFees).deep.equal(Amounts.parseOrThrow('USD:1'))
|
expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1"));
|
||||||
expect(r.payHandler.onClick).undefined;
|
expect(r.payHandler.onClick).undefined;
|
||||||
if (r.payHandler.error === undefined) expect.fail();
|
if (r.payHandler.error === undefined) expect.fail();
|
||||||
//FIXME: error message here is bad
|
//FIXME: error message here is bad
|
||||||
expect(r.payHandler.error.errorDetail.hint).eq("could not confirm payment")
|
expect(r.payHandler.error.errorDetail.hint).eq(
|
||||||
|
"could not confirm payment",
|
||||||
|
);
|
||||||
expect(r.payHandler.error.errorDetail.payResult).deep.equal({
|
expect(r.payHandler.error.errorDetail.payResult).deep.equal({
|
||||||
type: ConfirmPayResultType.Pending,
|
type: ConfirmPayResultType.Pending,
|
||||||
lastError: { code: 1 }
|
lastError: { code: 1 },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should update balance if a coins is withdraw", async () => {
|
it("should update balance if a coins is withdraw", async () => {
|
||||||
@ -351,40 +389,45 @@ describe("Pay CTA states", () => {
|
|||||||
let availableBalance = Amounts.parseOrThrow("USD:10");
|
let availableBalance = Amounts.parseOrThrow("USD:10");
|
||||||
|
|
||||||
function notifyCoinWithdrawn(newAmount: AmountJson): void {
|
function notifyCoinWithdrawn(newAmount: AmountJson): void {
|
||||||
availableBalance = Amounts.add(availableBalance, newAmount).amount
|
availableBalance = Amounts.add(availableBalance, newAmount).amount;
|
||||||
subscriptions.notifyEvent(NotificationType.CoinWithdrawn)
|
subscriptions.notifyEvent(NotificationType.CoinWithdrawn);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
useComponentState('taller://pay', {
|
mountHook(() =>
|
||||||
onUpdateNotification: subscriptions.saveSubscription,
|
useComponentState("taller://pay", {
|
||||||
preparePay: async () => ({
|
onUpdateNotification: subscriptions.saveSubscription,
|
||||||
amountRaw: 'USD:9',
|
preparePay: async () =>
|
||||||
amountEffective: 'USD:10',
|
({
|
||||||
status: PreparePayResultType.PaymentPossible,
|
amountRaw: "USD:9",
|
||||||
} as Partial<PreparePayResult>),
|
amountEffective: "USD:10",
|
||||||
getBalance: async () => ({
|
status: PreparePayResultType.PaymentPossible,
|
||||||
balances: [{
|
} as Partial<PreparePayResult>),
|
||||||
available: Amounts.stringify(availableBalance)
|
getBalance: async () =>
|
||||||
}]
|
({
|
||||||
} as Partial<BalancesResponse>),
|
balances: [
|
||||||
} as Partial<typeof wxApi> as any)
|
{
|
||||||
);
|
available: Amounts.stringify(availableBalance),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as Partial<BalancesResponse>),
|
||||||
|
} as Partial<typeof wxApi> as any),
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
expect(status).equals('loading')
|
expect(status).equals("loading");
|
||||||
expect(hook).undefined;
|
expect(hook).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate();
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const r = getLastResultOrThrow()
|
const r = getLastResultOrThrow();
|
||||||
if (r.status !== 'ready') expect.fail()
|
if (r.status !== "ready") expect.fail();
|
||||||
expect(r.balance).deep.equal(Amounts.parseOrThrow('USD:10'));
|
expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:10"));
|
||||||
expect(r.amount).deep.equal(Amounts.parseOrThrow('USD:9'))
|
expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9"));
|
||||||
expect(r.totalFees).deep.equal(Amounts.parseOrThrow('USD:1'))
|
expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1"));
|
||||||
expect(r.payHandler.onClick).not.undefined;
|
expect(r.payHandler.onClick).not.undefined;
|
||||||
|
|
||||||
notifyCoinWithdrawn(Amounts.parseOrThrow("USD:5"));
|
notifyCoinWithdrawn(Amounts.parseOrThrow("USD:5"));
|
||||||
@ -393,16 +436,14 @@ describe("Pay CTA states", () => {
|
|||||||
await waitNextUpdate();
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const r = getLastResultOrThrow()
|
const r = getLastResultOrThrow();
|
||||||
if (r.status !== 'ready') expect.fail()
|
if (r.status !== "ready") expect.fail();
|
||||||
expect(r.balance).deep.equal(Amounts.parseOrThrow('USD:15'));
|
expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15"));
|
||||||
expect(r.amount).deep.equal(Amounts.parseOrThrow('USD:9'))
|
expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9"));
|
||||||
expect(r.totalFees).deep.equal(Amounts.parseOrThrow('USD:1'))
|
expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1"));
|
||||||
expect(r.payHandler.onClick).not.undefined;
|
expect(r.payHandler.onClick).not.undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
|
||||||
|
@ -19,7 +19,12 @@
|
|||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AmountJson, Amounts, NotificationType, PrepareRefundResult } from "@gnu-taler/taler-util";
|
import {
|
||||||
|
AmountJson,
|
||||||
|
Amounts,
|
||||||
|
NotificationType,
|
||||||
|
PrepareRefundResult,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
import { mountHook } from "../test-utils.js";
|
import { mountHook } from "../test-utils.js";
|
||||||
import { SubsHandler } from "./Pay.test.js";
|
import { SubsHandler } from "./Pay.test.js";
|
||||||
@ -29,146 +34,151 @@ import { useComponentState } from "./Refund.jsx";
|
|||||||
|
|
||||||
describe("Refund CTA states", () => {
|
describe("Refund CTA states", () => {
|
||||||
it("should tell the user that the URI is missing", async () => {
|
it("should tell the user that the URI is missing", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
useComponentState(undefined, {
|
mountHook(() =>
|
||||||
prepareRefund: async () => ({}),
|
useComponentState(undefined, {
|
||||||
applyRefund: async () => ({}),
|
prepareRefund: async () => ({}),
|
||||||
onUpdateNotification: async () => ({})
|
applyRefund: async () => ({}),
|
||||||
} as any),
|
onUpdateNotification: async () => ({}),
|
||||||
);
|
} as any),
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
expect(status).equals('loading')
|
expect(status).equals("loading");
|
||||||
expect(hook).undefined;
|
expect(hook).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
|
|
||||||
expect(status).equals('loading')
|
expect(status).equals("loading");
|
||||||
if (!hook) expect.fail();
|
if (!hook) expect.fail();
|
||||||
if (!hook.hasError) expect.fail();
|
if (!hook.hasError) expect.fail();
|
||||||
if (hook.operational) expect.fail();
|
if (hook.operational) expect.fail();
|
||||||
expect(hook.message).eq("ERROR_NO-URI-FOR-REFUND");
|
expect(hook.message).eq("ERROR_NO-URI-FOR-REFUND");
|
||||||
}
|
}
|
||||||
|
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be ready after loading", async () => {
|
it("should be ready after loading", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
useComponentState("taler://refund/asdasdas", {
|
mountHook(() =>
|
||||||
prepareRefund: async () => ({
|
useComponentState("taler://refund/asdasdas", {
|
||||||
effectivePaid: 'EUR:2',
|
prepareRefund: async () =>
|
||||||
awaiting: 'EUR:2',
|
({
|
||||||
gone: 'EUR:0',
|
effectivePaid: "EUR:2",
|
||||||
granted: 'EUR:0',
|
awaiting: "EUR:2",
|
||||||
pending: false,
|
gone: "EUR:0",
|
||||||
proposalId: '1',
|
granted: "EUR:0",
|
||||||
info: {
|
pending: false,
|
||||||
contractTermsHash: '123',
|
proposalId: "1",
|
||||||
merchant: {
|
info: {
|
||||||
name: 'the merchant name'
|
contractTermsHash: "123",
|
||||||
},
|
merchant: {
|
||||||
orderId: 'orderId1',
|
name: "the merchant name",
|
||||||
summary: 'the sumary'
|
},
|
||||||
}
|
orderId: "orderId1",
|
||||||
} as PrepareRefundResult as any),
|
summary: "the sumary",
|
||||||
applyRefund: async () => ({}),
|
},
|
||||||
onUpdateNotification: async () => ({})
|
} as PrepareRefundResult as any),
|
||||||
} as any),
|
applyRefund: async () => ({}),
|
||||||
);
|
onUpdateNotification: async () => ({}),
|
||||||
|
} as any),
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
expect(status).equals('loading')
|
expect(status).equals("loading");
|
||||||
expect(hook).undefined;
|
expect(hook).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const state = getLastResultOrThrow()
|
const state = getLastResultOrThrow();
|
||||||
|
|
||||||
if (state.status !== 'ready') expect.fail();
|
if (state.status !== "ready") expect.fail();
|
||||||
if (state.hook) expect.fail();
|
if (state.hook) expect.fail();
|
||||||
expect(state.accept.onClick).not.undefined;
|
expect(state.accept.onClick).not.undefined;
|
||||||
expect(state.ignore.onClick).not.undefined;
|
expect(state.ignore.onClick).not.undefined;
|
||||||
expect(state.merchantName).eq('the merchant name');
|
expect(state.merchantName).eq("the merchant name");
|
||||||
expect(state.orderId).eq('orderId1');
|
expect(state.orderId).eq("orderId1");
|
||||||
expect(state.products).undefined;
|
expect(state.products).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be ignored after clicking the ignore button", async () => {
|
it("should be ignored after clicking the ignore button", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
useComponentState("taler://refund/asdasdas", {
|
mountHook(() =>
|
||||||
prepareRefund: async () => ({
|
useComponentState("taler://refund/asdasdas", {
|
||||||
effectivePaid: 'EUR:2',
|
prepareRefund: async () =>
|
||||||
awaiting: 'EUR:2',
|
({
|
||||||
gone: 'EUR:0',
|
effectivePaid: "EUR:2",
|
||||||
granted: 'EUR:0',
|
awaiting: "EUR:2",
|
||||||
pending: false,
|
gone: "EUR:0",
|
||||||
proposalId: '1',
|
granted: "EUR:0",
|
||||||
info: {
|
pending: false,
|
||||||
contractTermsHash: '123',
|
proposalId: "1",
|
||||||
merchant: {
|
info: {
|
||||||
name: 'the merchant name'
|
contractTermsHash: "123",
|
||||||
},
|
merchant: {
|
||||||
orderId: 'orderId1',
|
name: "the merchant name",
|
||||||
summary: 'the sumary'
|
},
|
||||||
}
|
orderId: "orderId1",
|
||||||
} as PrepareRefundResult as any),
|
summary: "the sumary",
|
||||||
applyRefund: async () => ({}),
|
},
|
||||||
onUpdateNotification: async () => ({})
|
} as PrepareRefundResult as any),
|
||||||
} as any),
|
applyRefund: async () => ({}),
|
||||||
);
|
onUpdateNotification: async () => ({}),
|
||||||
|
} as any),
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
expect(status).equals('loading')
|
expect(status).equals("loading");
|
||||||
expect(hook).undefined;
|
expect(hook).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const state = getLastResultOrThrow()
|
const state = getLastResultOrThrow();
|
||||||
|
|
||||||
if (state.status !== 'ready') expect.fail();
|
if (state.status !== "ready") expect.fail();
|
||||||
if (state.hook) expect.fail();
|
if (state.hook) expect.fail();
|
||||||
expect(state.accept.onClick).not.undefined;
|
expect(state.accept.onClick).not.undefined;
|
||||||
expect(state.merchantName).eq('the merchant name');
|
expect(state.merchantName).eq("the merchant name");
|
||||||
expect(state.orderId).eq('orderId1');
|
expect(state.orderId).eq("orderId1");
|
||||||
expect(state.products).undefined;
|
expect(state.products).undefined;
|
||||||
|
|
||||||
if (state.ignore.onClick === undefined) expect.fail();
|
if (state.ignore.onClick === undefined) expect.fail();
|
||||||
state.ignore.onClick()
|
state.ignore.onClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const state = getLastResultOrThrow()
|
const state = getLastResultOrThrow();
|
||||||
|
|
||||||
if (state.status !== 'ignored') expect.fail();
|
if (state.status !== "ignored") expect.fail();
|
||||||
if (state.hook) expect.fail();
|
if (state.hook) expect.fail();
|
||||||
expect(state.merchantName).eq('the merchant name');
|
expect(state.merchantName).eq("the merchant name");
|
||||||
}
|
}
|
||||||
|
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be in progress when doing refresh", async () => {
|
it("should be in progress when doing refresh", async () => {
|
||||||
let granted = Amounts.getZero('EUR')
|
let granted = Amounts.getZero("EUR");
|
||||||
const unit: AmountJson = { currency: 'EUR', value: 1, fraction: 0 }
|
const unit: AmountJson = { currency: "EUR", value: 1, fraction: 0 };
|
||||||
const refunded: AmountJson = { currency: 'EUR', value: 2, fraction: 0 }
|
const refunded: AmountJson = { currency: "EUR", value: 2, fraction: 0 };
|
||||||
let awaiting: AmountJson = refunded
|
let awaiting: AmountJson = refunded;
|
||||||
let pending = true;
|
let pending = true;
|
||||||
|
|
||||||
const subscriptions = new SubsHandler();
|
const subscriptions = new SubsHandler();
|
||||||
@ -177,80 +187,82 @@ describe("Refund CTA states", () => {
|
|||||||
granted = Amounts.add(granted, unit).amount;
|
granted = Amounts.add(granted, unit).amount;
|
||||||
pending = granted.value < refunded.value;
|
pending = granted.value < refunded.value;
|
||||||
awaiting = Amounts.sub(refunded, granted).amount;
|
awaiting = Amounts.sub(refunded, granted).amount;
|
||||||
subscriptions.notifyEvent(NotificationType.RefreshMelted)
|
subscriptions.notifyEvent(NotificationType.RefreshMelted);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
useComponentState("taler://refund/asdasdas", {
|
mountHook(() =>
|
||||||
prepareRefund: async () => ({
|
useComponentState("taler://refund/asdasdas", {
|
||||||
awaiting: Amounts.stringify(awaiting),
|
prepareRefund: async () =>
|
||||||
effectivePaid: 'EUR:2',
|
({
|
||||||
gone: 'EUR:0',
|
awaiting: Amounts.stringify(awaiting),
|
||||||
granted: Amounts.stringify(granted),
|
effectivePaid: "EUR:2",
|
||||||
pending,
|
gone: "EUR:0",
|
||||||
proposalId: '1',
|
granted: Amounts.stringify(granted),
|
||||||
info: {
|
pending,
|
||||||
contractTermsHash: '123',
|
proposalId: "1",
|
||||||
merchant: {
|
info: {
|
||||||
name: 'the merchant name'
|
contractTermsHash: "123",
|
||||||
},
|
merchant: {
|
||||||
orderId: 'orderId1',
|
name: "the merchant name",
|
||||||
summary: 'the sumary'
|
},
|
||||||
}
|
orderId: "orderId1",
|
||||||
} as PrepareRefundResult as any),
|
summary: "the sumary",
|
||||||
applyRefund: async () => ({}),
|
},
|
||||||
onUpdateNotification: subscriptions.saveSubscription,
|
} as PrepareRefundResult as any),
|
||||||
} as any),
|
applyRefund: async () => ({}),
|
||||||
);
|
onUpdateNotification: subscriptions.saveSubscription,
|
||||||
|
} as any),
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
expect(status).equals('loading')
|
expect(status).equals("loading");
|
||||||
expect(hook).undefined;
|
expect(hook).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const state = getLastResultOrThrow()
|
const state = getLastResultOrThrow();
|
||||||
|
|
||||||
if (state.status !== 'in-progress') expect.fail('1');
|
if (state.status !== "in-progress") expect.fail("1");
|
||||||
if (state.hook) expect.fail();
|
if (state.hook) expect.fail();
|
||||||
expect(state.merchantName).eq('the merchant name');
|
expect(state.merchantName).eq("the merchant name");
|
||||||
expect(state.products).undefined;
|
expect(state.products).undefined;
|
||||||
expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:2"))
|
expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:2"));
|
||||||
// expect(state.progress).closeTo(1 / 3, 0.01)
|
// expect(state.progress).closeTo(1 / 3, 0.01)
|
||||||
|
|
||||||
notifyMelt()
|
notifyMelt();
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const state = getLastResultOrThrow()
|
const state = getLastResultOrThrow();
|
||||||
|
|
||||||
if (state.status !== 'in-progress') expect.fail('2');
|
if (state.status !== "in-progress") expect.fail("2");
|
||||||
if (state.hook) expect.fail();
|
if (state.hook) expect.fail();
|
||||||
expect(state.merchantName).eq('the merchant name');
|
expect(state.merchantName).eq("the merchant name");
|
||||||
expect(state.products).undefined;
|
expect(state.products).undefined;
|
||||||
expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:2"))
|
expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:2"));
|
||||||
// expect(state.progress).closeTo(2 / 3, 0.01)
|
// expect(state.progress).closeTo(2 / 3, 0.01)
|
||||||
|
|
||||||
notifyMelt()
|
notifyMelt();
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const state = getLastResultOrThrow()
|
const state = getLastResultOrThrow();
|
||||||
|
|
||||||
if (state.status !== 'completed') expect.fail('3');
|
if (state.status !== "completed") expect.fail("3");
|
||||||
if (state.hook) expect.fail();
|
if (state.hook) expect.fail();
|
||||||
expect(state.merchantName).eq('the merchant name');
|
expect(state.merchantName).eq("the merchant name");
|
||||||
expect(state.products).undefined;
|
expect(state.products).undefined;
|
||||||
expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:2"))
|
expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:2"));
|
||||||
}
|
}
|
||||||
|
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -26,64 +26,67 @@ import { useComponentState } from "./Tip.jsx";
|
|||||||
|
|
||||||
describe("Tip CTA states", () => {
|
describe("Tip CTA states", () => {
|
||||||
it("should tell the user that the URI is missing", async () => {
|
it("should tell the user that the URI is missing", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
useComponentState(undefined, {
|
mountHook(() =>
|
||||||
prepareTip: async () => ({}),
|
useComponentState(undefined, {
|
||||||
acceptTip: async () => ({})
|
prepareTip: async () => ({}),
|
||||||
} as any),
|
acceptTip: async () => ({}),
|
||||||
);
|
} as any),
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
expect(status).equals('loading')
|
expect(status).equals("loading");
|
||||||
expect(hook).undefined;
|
expect(hook).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
|
|
||||||
expect(status).equals('loading')
|
expect(status).equals("loading");
|
||||||
if (!hook) expect.fail();
|
if (!hook) expect.fail();
|
||||||
if (!hook.hasError) expect.fail();
|
if (!hook.hasError) expect.fail();
|
||||||
if (hook.operational) expect.fail();
|
if (hook.operational) expect.fail();
|
||||||
expect(hook.message).eq("ERROR_NO-URI-FOR-TIP");
|
expect(hook.message).eq("ERROR_NO-URI-FOR-TIP");
|
||||||
}
|
}
|
||||||
|
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be ready for accepting the tip", async () => {
|
it("should be ready for accepting the tip", async () => {
|
||||||
let tipAccepted = false;
|
let tipAccepted = false;
|
||||||
|
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
useComponentState("taler://tip/asd", {
|
mountHook(() =>
|
||||||
prepareTip: async () => ({
|
useComponentState("taler://tip/asd", {
|
||||||
accepted: tipAccepted,
|
prepareTip: async () =>
|
||||||
exchangeBaseUrl: "exchange url",
|
({
|
||||||
merchantBaseUrl: "merchant url",
|
accepted: tipAccepted,
|
||||||
tipAmountEffective: "EUR:1",
|
exchangeBaseUrl: "exchange url",
|
||||||
walletTipId: "tip_id",
|
merchantBaseUrl: "merchant url",
|
||||||
} as PrepareTipResult as any),
|
tipAmountEffective: "EUR:1",
|
||||||
acceptTip: async () => {
|
walletTipId: "tip_id",
|
||||||
tipAccepted = true
|
} as PrepareTipResult as any),
|
||||||
}
|
acceptTip: async () => {
|
||||||
} as any),
|
tipAccepted = true;
|
||||||
);
|
},
|
||||||
|
} as any),
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
expect(status).equals('loading')
|
expect(status).equals("loading");
|
||||||
expect(hook).undefined;
|
expect(hook).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const state = getLastResultOrThrow()
|
const state = getLastResultOrThrow();
|
||||||
|
|
||||||
if (state.status !== "ready") expect.fail()
|
if (state.status !== "ready") expect.fail();
|
||||||
if (state.hook) expect.fail();
|
if (state.hook) expect.fail();
|
||||||
expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1"));
|
expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1"));
|
||||||
expect(state.merchantBaseUrl).eq("merchant url");
|
expect(state.merchantBaseUrl).eq("merchant url");
|
||||||
@ -93,45 +96,46 @@ describe("Tip CTA states", () => {
|
|||||||
state.accept.onClick();
|
state.accept.onClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
{
|
{
|
||||||
const state = getLastResultOrThrow()
|
const state = getLastResultOrThrow();
|
||||||
|
|
||||||
if (state.status !== "accepted") expect.fail()
|
if (state.status !== "accepted") expect.fail();
|
||||||
if (state.hook) expect.fail();
|
if (state.hook) expect.fail();
|
||||||
expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1"));
|
expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1"));
|
||||||
expect(state.merchantBaseUrl).eq("merchant url");
|
expect(state.merchantBaseUrl).eq("merchant url");
|
||||||
expect(state.exchangeBaseUrl).eq("exchange url");
|
expect(state.exchangeBaseUrl).eq("exchange url");
|
||||||
|
|
||||||
}
|
}
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be ignored after clicking the ignore button", async () => {
|
it("should be ignored after clicking the ignore button", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
useComponentState("taler://tip/asd", {
|
mountHook(() =>
|
||||||
prepareTip: async () => ({
|
useComponentState("taler://tip/asd", {
|
||||||
exchangeBaseUrl: "exchange url",
|
prepareTip: async () =>
|
||||||
merchantBaseUrl: "merchant url",
|
({
|
||||||
tipAmountEffective: "EUR:1",
|
exchangeBaseUrl: "exchange url",
|
||||||
walletTipId: "tip_id",
|
merchantBaseUrl: "merchant url",
|
||||||
} as PrepareTipResult as any),
|
tipAmountEffective: "EUR:1",
|
||||||
acceptTip: async () => ({})
|
walletTipId: "tip_id",
|
||||||
} as any),
|
} as PrepareTipResult as any),
|
||||||
);
|
acceptTip: async () => ({}),
|
||||||
|
} as any),
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
expect(status).equals('loading')
|
expect(status).equals("loading");
|
||||||
expect(hook).undefined;
|
expect(hook).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const state = getLastResultOrThrow()
|
const state = getLastResultOrThrow();
|
||||||
|
|
||||||
if (state.status !== "ready") expect.fail()
|
if (state.status !== "ready") expect.fail();
|
||||||
if (state.hook) expect.fail();
|
if (state.hook) expect.fail();
|
||||||
expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1"));
|
expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1"));
|
||||||
expect(state.merchantBaseUrl).eq("merchant url");
|
expect(state.merchantBaseUrl).eq("merchant url");
|
||||||
@ -141,52 +145,49 @@ describe("Tip CTA states", () => {
|
|||||||
state.ignore.onClick();
|
state.ignore.onClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
{
|
{
|
||||||
const state = getLastResultOrThrow()
|
const state = getLastResultOrThrow();
|
||||||
|
|
||||||
if (state.status !== "ignored") expect.fail()
|
if (state.status !== "ignored") expect.fail();
|
||||||
if (state.hook) expect.fail();
|
if (state.hook) expect.fail();
|
||||||
|
|
||||||
}
|
}
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render accepted if the tip has been used previously", async () => {
|
it("should render accepted if the tip has been used previously", async () => {
|
||||||
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState("taler://tip/asd", {
|
useComponentState("taler://tip/asd", {
|
||||||
prepareTip: async () => ({
|
prepareTip: async () =>
|
||||||
accepted: true,
|
({
|
||||||
exchangeBaseUrl: "exchange url",
|
accepted: true,
|
||||||
merchantBaseUrl: "merchant url",
|
exchangeBaseUrl: "exchange url",
|
||||||
tipAmountEffective: "EUR:1",
|
merchantBaseUrl: "merchant url",
|
||||||
walletTipId: "tip_id",
|
tipAmountEffective: "EUR:1",
|
||||||
} as PrepareTipResult as any),
|
walletTipId: "tip_id",
|
||||||
acceptTip: async () => ({})
|
} as PrepareTipResult as any),
|
||||||
} as any),
|
acceptTip: async () => ({}),
|
||||||
);
|
} as any),
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
expect(status).equals('loading')
|
expect(status).equals("loading");
|
||||||
expect(hook).undefined;
|
expect(hook).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const state = getLastResultOrThrow()
|
const state = getLastResultOrThrow();
|
||||||
|
|
||||||
if (state.status !== "accepted") expect.fail()
|
if (state.status !== "accepted") expect.fail();
|
||||||
if (state.hook) expect.fail();
|
if (state.hook) expect.fail();
|
||||||
expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1"));
|
expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1"));
|
||||||
expect(state.merchantBaseUrl).eq("merchant url");
|
expect(state.merchantBaseUrl).eq("merchant url");
|
||||||
expect(state.exchangeBaseUrl).eq("exchange url");
|
expect(state.exchangeBaseUrl).eq("exchange url");
|
||||||
|
|
||||||
}
|
}
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
|
||||||
|
@ -19,234 +19,249 @@
|
|||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Amounts, ExchangeListItem, GetExchangeTosResult } from "@gnu-taler/taler-util";
|
import {
|
||||||
|
Amounts,
|
||||||
|
ExchangeListItem,
|
||||||
|
GetExchangeTosResult,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
import { ExchangeWithdrawDetails } from "@gnu-taler/taler-wallet-core";
|
import { ExchangeWithdrawDetails } from "@gnu-taler/taler-wallet-core";
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
import { mountHook } from "../test-utils.js";
|
import { mountHook } from "../test-utils.js";
|
||||||
import { useComponentState } from "./Withdraw.js";
|
import { useComponentState } from "./Withdraw.js";
|
||||||
|
|
||||||
const exchanges: ExchangeListItem[] = [{
|
const exchanges: ExchangeListItem[] = [
|
||||||
currency: 'ARS',
|
{
|
||||||
exchangeBaseUrl: 'http://exchange.demo.taler.net',
|
currency: "ARS",
|
||||||
paytoUris: [],
|
exchangeBaseUrl: "http://exchange.demo.taler.net",
|
||||||
tos: {
|
paytoUris: [],
|
||||||
acceptedVersion: '',
|
tos: {
|
||||||
}
|
acceptedVersion: "",
|
||||||
}]
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
describe("Withdraw CTA states", () => {
|
describe("Withdraw CTA states", () => {
|
||||||
it("should tell the user that the URI is missing", async () => {
|
it("should tell the user that the URI is missing", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
useComponentState(undefined, {
|
mountHook(() =>
|
||||||
listExchanges: async () => ({ exchanges }),
|
useComponentState(undefined, {
|
||||||
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
|
listExchanges: async () => ({ exchanges }),
|
||||||
amount: 'ARS:2',
|
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
|
||||||
possibleExchanges: exchanges,
|
amount: "ARS:2",
|
||||||
})
|
possibleExchanges: exchanges,
|
||||||
} as any),
|
}),
|
||||||
);
|
} as any),
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
expect(status).equals('loading-uri')
|
expect(status).equals("loading-uri");
|
||||||
expect(hook).undefined;
|
expect(hook).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
|
|
||||||
expect(status).equals('loading-uri')
|
expect(status).equals("loading-uri");
|
||||||
if (!hook) expect.fail();
|
if (!hook) expect.fail();
|
||||||
if (!hook.hasError) expect.fail();
|
if (!hook.hasError) expect.fail();
|
||||||
if (hook.operational) expect.fail();
|
if (hook.operational) expect.fail();
|
||||||
expect(hook.message).eq("ERROR_NO-URI-FOR-WITHDRAWAL");
|
expect(hook.message).eq("ERROR_NO-URI-FOR-WITHDRAWAL");
|
||||||
}
|
}
|
||||||
|
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should tell the user that there is not known exchange", async () => {
|
it("should tell the user that there is not known exchange", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
useComponentState('taler-withdraw://', {
|
mountHook(() =>
|
||||||
listExchanges: async () => ({ exchanges }),
|
useComponentState("taler-withdraw://", {
|
||||||
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
|
listExchanges: async () => ({ exchanges }),
|
||||||
amount: 'EUR:2',
|
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
|
||||||
possibleExchanges: [],
|
amount: "EUR:2",
|
||||||
})
|
possibleExchanges: [],
|
||||||
} as any),
|
}),
|
||||||
);
|
} as any),
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
expect(status).equals('loading-uri')
|
expect(status).equals("loading-uri");
|
||||||
expect(hook).undefined;
|
expect(hook).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
|
|
||||||
expect(status).equals('loading-exchange')
|
expect(status).equals("loading-exchange");
|
||||||
|
|
||||||
expect(hook).deep.equals({ "hasError": true, "operational": false, "message": "ERROR_NO-DEFAULT-EXCHANGE" });
|
expect(hook).deep.equals({
|
||||||
|
hasError: true,
|
||||||
|
operational: false,
|
||||||
|
message: "ERROR_NO-DEFAULT-EXCHANGE",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be able to withdraw if tos are ok", async () => {
|
it("should be able to withdraw if tos are ok", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
useComponentState('taler-withdraw://', {
|
mountHook(() =>
|
||||||
listExchanges: async () => ({ exchanges }),
|
useComponentState("taler-withdraw://", {
|
||||||
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
|
listExchanges: async () => ({ exchanges }),
|
||||||
amount: 'ARS:2',
|
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
|
||||||
possibleExchanges: exchanges,
|
amount: "ARS:2",
|
||||||
}),
|
possibleExchanges: exchanges,
|
||||||
getExchangeWithdrawalInfo: async (): Promise<ExchangeWithdrawDetails> => ({
|
}),
|
||||||
withdrawalAmountRaw: 'ARS:5',
|
getExchangeWithdrawalInfo:
|
||||||
withdrawalAmountEffective: 'ARS:5',
|
async (): Promise<ExchangeWithdrawDetails> =>
|
||||||
|
({
|
||||||
|
withdrawalAmountRaw: "ARS:5",
|
||||||
|
withdrawalAmountEffective: "ARS:5",
|
||||||
|
} as any),
|
||||||
|
getExchangeTos: async (): Promise<GetExchangeTosResult> => ({
|
||||||
|
contentType: "text",
|
||||||
|
content: "just accept",
|
||||||
|
acceptedEtag: "v1",
|
||||||
|
currentEtag: "v1",
|
||||||
|
}),
|
||||||
} as any),
|
} as any),
|
||||||
getExchangeTos: async (): Promise<GetExchangeTosResult> => ({
|
);
|
||||||
contentType: 'text',
|
|
||||||
content: 'just accept',
|
|
||||||
acceptedEtag: 'v1',
|
|
||||||
currentEtag: 'v1'
|
|
||||||
})
|
|
||||||
} as any),
|
|
||||||
);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
expect(status).equals('loading-uri')
|
expect(status).equals("loading-uri");
|
||||||
expect(hook).undefined;
|
expect(hook).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
|
|
||||||
expect(status).equals('loading-info')
|
expect(status).equals("loading-info");
|
||||||
|
|
||||||
expect(hook).undefined;
|
expect(hook).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const state = getLastResultOrThrow()
|
const state = getLastResultOrThrow();
|
||||||
expect(state.status).equals("success")
|
expect(state.status).equals("success");
|
||||||
if (state.status !== "success") return;
|
if (state.status !== "success") return;
|
||||||
|
|
||||||
expect(state.exchange.isDirty).false
|
expect(state.exchange.isDirty).false;
|
||||||
expect(state.exchange.value).equal("http://exchange.demo.taler.net")
|
expect(state.exchange.value).equal("http://exchange.demo.taler.net");
|
||||||
expect(state.exchange.list).deep.equal({
|
expect(state.exchange.list).deep.equal({
|
||||||
"http://exchange.demo.taler.net": "http://exchange.demo.taler.net"
|
"http://exchange.demo.taler.net": "http://exchange.demo.taler.net",
|
||||||
})
|
});
|
||||||
expect(state.showExchangeSelection).false
|
expect(state.showExchangeSelection).false;
|
||||||
|
|
||||||
expect(state.toBeReceived).deep.equal(Amounts.parseOrThrow("ARS:2"))
|
expect(state.toBeReceived).deep.equal(Amounts.parseOrThrow("ARS:2"));
|
||||||
expect(state.withdrawalFee).deep.equal(Amounts.parseOrThrow("ARS:0"))
|
expect(state.withdrawalFee).deep.equal(Amounts.parseOrThrow("ARS:0"));
|
||||||
expect(state.chosenAmount).deep.equal(Amounts.parseOrThrow("ARS:2"))
|
expect(state.chosenAmount).deep.equal(Amounts.parseOrThrow("ARS:2"));
|
||||||
|
|
||||||
expect(state.doWithdrawal.onClick).not.undefined
|
|
||||||
expect(state.mustAcceptFirst).false
|
|
||||||
|
|
||||||
|
expect(state.doWithdrawal.onClick).not.undefined;
|
||||||
|
expect(state.mustAcceptFirst).false;
|
||||||
}
|
}
|
||||||
|
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be accept the tos before withdraw", async () => {
|
it("should be accept the tos before withdraw", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
useComponentState('taler-withdraw://', {
|
mountHook(() =>
|
||||||
listExchanges: async () => ({ exchanges }),
|
useComponentState("taler-withdraw://", {
|
||||||
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
|
listExchanges: async () => ({ exchanges }),
|
||||||
amount: 'ARS:2',
|
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
|
||||||
possibleExchanges: exchanges,
|
amount: "ARS:2",
|
||||||
}),
|
possibleExchanges: exchanges,
|
||||||
getExchangeWithdrawalInfo: async (): Promise<ExchangeWithdrawDetails> => ({
|
}),
|
||||||
withdrawalAmountRaw: 'ARS:5',
|
getExchangeWithdrawalInfo:
|
||||||
withdrawalAmountEffective: 'ARS:5',
|
async (): Promise<ExchangeWithdrawDetails> =>
|
||||||
|
({
|
||||||
|
withdrawalAmountRaw: "ARS:5",
|
||||||
|
withdrawalAmountEffective: "ARS:5",
|
||||||
|
} as any),
|
||||||
|
getExchangeTos: async (): Promise<GetExchangeTosResult> => ({
|
||||||
|
contentType: "text",
|
||||||
|
content: "just accept",
|
||||||
|
acceptedEtag: "v1",
|
||||||
|
currentEtag: "v2",
|
||||||
|
}),
|
||||||
|
setExchangeTosAccepted: async () => ({}),
|
||||||
} as any),
|
} as any),
|
||||||
getExchangeTos: async (): Promise<GetExchangeTosResult> => ({
|
);
|
||||||
contentType: 'text',
|
|
||||||
content: 'just accept',
|
|
||||||
acceptedEtag: 'v1',
|
|
||||||
currentEtag: 'v2'
|
|
||||||
}),
|
|
||||||
setExchangeTosAccepted: async () => ({})
|
|
||||||
} as any),
|
|
||||||
);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
expect(status).equals('loading-uri')
|
expect(status).equals("loading-uri");
|
||||||
expect(hook).undefined;
|
expect(hook).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status, hook } = getLastResultOrThrow()
|
const { status, hook } = getLastResultOrThrow();
|
||||||
|
|
||||||
expect(status).equals('loading-info')
|
expect(status).equals("loading-info");
|
||||||
|
|
||||||
expect(hook).undefined;
|
expect(hook).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const state = getLastResultOrThrow()
|
const state = getLastResultOrThrow();
|
||||||
expect(state.status).equals("success")
|
expect(state.status).equals("success");
|
||||||
if (state.status !== "success") return;
|
if (state.status !== "success") return;
|
||||||
|
|
||||||
expect(state.exchange.isDirty).false
|
expect(state.exchange.isDirty).false;
|
||||||
expect(state.exchange.value).equal("http://exchange.demo.taler.net")
|
expect(state.exchange.value).equal("http://exchange.demo.taler.net");
|
||||||
expect(state.exchange.list).deep.equal({
|
expect(state.exchange.list).deep.equal({
|
||||||
"http://exchange.demo.taler.net": "http://exchange.demo.taler.net"
|
"http://exchange.demo.taler.net": "http://exchange.demo.taler.net",
|
||||||
})
|
});
|
||||||
expect(state.showExchangeSelection).false
|
expect(state.showExchangeSelection).false;
|
||||||
|
|
||||||
expect(state.toBeReceived).deep.equal(Amounts.parseOrThrow("ARS:2"))
|
expect(state.toBeReceived).deep.equal(Amounts.parseOrThrow("ARS:2"));
|
||||||
expect(state.withdrawalFee).deep.equal(Amounts.parseOrThrow("ARS:0"))
|
expect(state.withdrawalFee).deep.equal(Amounts.parseOrThrow("ARS:0"));
|
||||||
expect(state.chosenAmount).deep.equal(Amounts.parseOrThrow("ARS:2"))
|
expect(state.chosenAmount).deep.equal(Amounts.parseOrThrow("ARS:2"));
|
||||||
|
|
||||||
expect(state.doWithdrawal.onClick).undefined
|
expect(state.doWithdrawal.onClick).undefined;
|
||||||
expect(state.mustAcceptFirst).true
|
expect(state.mustAcceptFirst).true;
|
||||||
|
|
||||||
// accept TOS
|
// accept TOS
|
||||||
state.tosProps?.onAccept(true)
|
state.tosProps?.onAccept(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const state = getLastResultOrThrow()
|
const state = getLastResultOrThrow();
|
||||||
expect(state.status).equals("success")
|
expect(state.status).equals("success");
|
||||||
if (state.status !== "success") return;
|
if (state.status !== "success") return;
|
||||||
|
|
||||||
expect(state.exchange.isDirty).false
|
expect(state.exchange.isDirty).false;
|
||||||
expect(state.exchange.value).equal("http://exchange.demo.taler.net")
|
expect(state.exchange.value).equal("http://exchange.demo.taler.net");
|
||||||
expect(state.exchange.list).deep.equal({
|
expect(state.exchange.list).deep.equal({
|
||||||
"http://exchange.demo.taler.net": "http://exchange.demo.taler.net"
|
"http://exchange.demo.taler.net": "http://exchange.demo.taler.net",
|
||||||
})
|
});
|
||||||
expect(state.showExchangeSelection).false
|
expect(state.showExchangeSelection).false;
|
||||||
|
|
||||||
expect(state.toBeReceived).deep.equal(Amounts.parseOrThrow("ARS:2"))
|
expect(state.toBeReceived).deep.equal(Amounts.parseOrThrow("ARS:2"));
|
||||||
expect(state.withdrawalFee).deep.equal(Amounts.parseOrThrow("ARS:0"))
|
expect(state.withdrawalFee).deep.equal(Amounts.parseOrThrow("ARS:0"));
|
||||||
expect(state.chosenAmount).deep.equal(Amounts.parseOrThrow("ARS:2"))
|
expect(state.chosenAmount).deep.equal(Amounts.parseOrThrow("ARS:2"));
|
||||||
|
|
||||||
expect(state.doWithdrawal.onClick).not.undefined
|
|
||||||
expect(state.mustAcceptFirst).true
|
|
||||||
|
|
||||||
|
expect(state.doWithdrawal.onClick).not.undefined;
|
||||||
|
expect(state.mustAcceptFirst).true;
|
||||||
}
|
}
|
||||||
|
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
});
|
|
||||||
|
@ -779,4 +779,3 @@ trailer
|
|||||||
<< /Root 3 0 R >>
|
<< /Root 3 0 R >>
|
||||||
%%EOF
|
%%EOF
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
You should have received a copy of the GNU General Public License along with
|
You should have received a copy of the GNU General Public License along with
|
||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
declare module "*.jpeg" {
|
declare module "*.jpeg" {
|
||||||
const content: any;
|
const content: any;
|
||||||
export default content;
|
export default content;
|
||||||
}
|
}
|
||||||
|
@ -13,9 +13,7 @@
|
|||||||
You should have received a copy of the GNU General Public License along with
|
You should have received a copy of the GNU General Public License along with
|
||||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
import {
|
import { NotificationType, TalerErrorDetail } from "@gnu-taler/taler-util";
|
||||||
NotificationType, TalerErrorDetail
|
|
||||||
} from "@gnu-taler/taler-util";
|
|
||||||
import { TalerError } from "@gnu-taler/taler-wallet-core";
|
import { TalerError } from "@gnu-taler/taler-wallet-core";
|
||||||
import { useEffect, useMemo, useState } from "preact/hooks";
|
import { useEffect, useMemo, useState } from "preact/hooks";
|
||||||
import * as wxApi from "../wxApi.js";
|
import * as wxApi from "../wxApi.js";
|
||||||
@ -44,19 +42,23 @@ interface WithRetry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type HookResponse<T> = HookOk<T> | HookError | undefined;
|
export type HookResponse<T> = HookOk<T> | HookError | undefined;
|
||||||
export type HookResponseWithRetry<T> = ((HookOk<T> | HookError) & WithRetry) | undefined;
|
export type HookResponseWithRetry<T> =
|
||||||
|
| ((HookOk<T> | HookError) & WithRetry)
|
||||||
|
| undefined;
|
||||||
|
|
||||||
export function useAsyncAsHook<T>(
|
export function useAsyncAsHook<T>(
|
||||||
fn: () => Promise<T | false>,
|
fn: () => Promise<T | false>,
|
||||||
deps?: any[],
|
deps?: any[],
|
||||||
): HookResponseWithRetry<T> {
|
): HookResponseWithRetry<T> {
|
||||||
|
|
||||||
const [result, setHookResponse] = useState<HookResponse<T>>(undefined);
|
const [result, setHookResponse] = useState<HookResponse<T>>(undefined);
|
||||||
|
|
||||||
const args = useMemo(() => ({
|
const args = useMemo(
|
||||||
fn
|
() => ({
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
fn,
|
||||||
}), deps || [])
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}),
|
||||||
|
deps || [],
|
||||||
|
);
|
||||||
|
|
||||||
async function doAsync(): Promise<void> {
|
async function doAsync(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
|
@ -24,9 +24,9 @@ export function useExtendedPermissions(): ToggleHandler {
|
|||||||
const [enabled, setEnabled] = useState(false);
|
const [enabled, setEnabled] = useState(false);
|
||||||
const [error, setError] = useState<TalerError | undefined>();
|
const [error, setError] = useState<TalerError | undefined>();
|
||||||
const toggle = async (): Promise<void> => {
|
const toggle = async (): Promise<void> => {
|
||||||
return handleExtendedPerm(enabled, setEnabled).catch(e => {
|
return handleExtendedPerm(enabled, setEnabled).catch((e) => {
|
||||||
setError(TalerError.fromException(e))
|
setError(TalerError.fromException(e));
|
||||||
})
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -40,12 +40,15 @@ export function useExtendedPermissions(): ToggleHandler {
|
|||||||
value: enabled,
|
value: enabled,
|
||||||
button: {
|
button: {
|
||||||
onClick: toggle,
|
onClick: toggle,
|
||||||
error
|
error,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleExtendedPerm(isEnabled: boolean, onChange: (value: boolean) => void): Promise<void> {
|
async function handleExtendedPerm(
|
||||||
|
isEnabled: boolean,
|
||||||
|
onChange: (value: boolean) => void,
|
||||||
|
): Promise<void> {
|
||||||
if (!isEnabled) {
|
if (!isEnabled) {
|
||||||
// We set permissions here, since apparently FF wants this to be done
|
// We set permissions here, since apparently FF wants this to be done
|
||||||
// as the result of an input event ...
|
// as the result of an input event ...
|
||||||
@ -60,11 +63,10 @@ async function handleExtendedPerm(isEnabled: boolean, onChange: (value: boolean)
|
|||||||
onChange(res.newValue);
|
onChange(res.newValue);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
await wxApi.toggleHeaderListener(false).then(r => onChange(r.newValue));
|
await wxApi.toggleHeaderListener(false).then((r) => onChange(r.newValue));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e)
|
console.log(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
@ -17,12 +17,14 @@
|
|||||||
import { useNotNullLocalStorage } from "./useLocalStorage.js";
|
import { useNotNullLocalStorage } from "./useLocalStorage.js";
|
||||||
|
|
||||||
function getBrowserLang(): string | undefined {
|
function getBrowserLang(): string | undefined {
|
||||||
if (window.navigator.languages) return window.navigator.languages[0]
|
if (window.navigator.languages) return window.navigator.languages[0];
|
||||||
if (window.navigator.language) return window.navigator.language
|
if (window.navigator.language) return window.navigator.language;
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useLang(initial?: string): [string, (s: string) => void, boolean] {
|
export function useLang(
|
||||||
|
initial?: string,
|
||||||
|
): [string, (s: string) => void, boolean] {
|
||||||
const defaultLang = (getBrowserLang() || initial || "en").substring(0, 2);
|
const defaultLang = (getBrowserLang() || initial || "en").substring(0, 2);
|
||||||
return useNotNullLocalStorage("lang-preference", defaultLang);
|
return useNotNullLocalStorage("lang-preference", defaultLang);
|
||||||
}
|
}
|
||||||
|
@ -25,13 +25,13 @@ export function useLocalStorage(
|
|||||||
key: string,
|
key: string,
|
||||||
initialValue?: string,
|
initialValue?: string,
|
||||||
): [string | undefined, StateUpdater<string | undefined>] {
|
): [string | undefined, StateUpdater<string | undefined>] {
|
||||||
const [storedValue, setStoredValue] = useState<string | undefined>(():
|
const [storedValue, setStoredValue] = useState<string | undefined>(
|
||||||
| string
|
(): string | undefined => {
|
||||||
| undefined => {
|
return typeof window !== "undefined"
|
||||||
return typeof window !== "undefined"
|
? window.localStorage.getItem(key) || initialValue
|
||||||
? window.localStorage.getItem(key) || initialValue
|
: initialValue;
|
||||||
: initialValue;
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
const setValue = (
|
const setValue = (
|
||||||
value?: string | ((val?: string) => string | undefined),
|
value?: string | ((val?: string) => string | undefined),
|
||||||
|
@ -14,48 +14,47 @@
|
|||||||
You should have received a copy of the GNU General Public License along with
|
You should have received a copy of the GNU General Public License along with
|
||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
import { useTalerActionURL } from "./useTalerActionURL.js"
|
import { useTalerActionURL } from "./useTalerActionURL.js";
|
||||||
import { mountHook } from "../test-utils.js";
|
import { mountHook } from "../test-utils.js";
|
||||||
import { IoCProviderForTesting } from "../context/iocContext.js";
|
import { IoCProviderForTesting } from "../context/iocContext.js";
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
|
|
||||||
describe('useTalerActionURL hook', () => {
|
describe("useTalerActionURL hook", () => {
|
||||||
|
it("should be set url to undefined when dismiss", async () => {
|
||||||
it('should be set url to undefined when dismiss', async () => {
|
|
||||||
|
|
||||||
const ctx = ({ children }: { children: any }): VNode => {
|
const ctx = ({ children }: { children: any }): VNode => {
|
||||||
return h(IoCProviderForTesting, {
|
return h(IoCProviderForTesting, {
|
||||||
value: {
|
value: {
|
||||||
findTalerUriInActiveTab: async () => "asd",
|
findTalerUriInActiveTab: async () => "asd",
|
||||||
}, children
|
},
|
||||||
})
|
children,
|
||||||
}
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(useTalerActionURL, ctx)
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
|
mountHook(useTalerActionURL, ctx);
|
||||||
|
|
||||||
{
|
{
|
||||||
const [url] = getLastResultOrThrow()
|
const [url] = getLastResultOrThrow();
|
||||||
expect(url).undefined;
|
expect(url).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await waitNextUpdate("waiting for useEffect");
|
||||||
await waitNextUpdate("waiting for useEffect")
|
|
||||||
|
|
||||||
{
|
{
|
||||||
const [url, setDismissed] = getLastResultOrThrow()
|
const [url, setDismissed] = getLastResultOrThrow();
|
||||||
expect(url).equals("asd");
|
expect(url).equals("asd");
|
||||||
setDismissed(true)
|
setDismissed(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate("after dismiss")
|
await waitNextUpdate("after dismiss");
|
||||||
|
|
||||||
{
|
{
|
||||||
const [url] = getLastResultOrThrow()
|
const [url] = getLastResultOrThrow();
|
||||||
if (url !== undefined) throw Error('invalid')
|
if (url !== undefined) throw Error("invalid");
|
||||||
expect(url).undefined;
|
expect(url).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
@ -25,7 +25,7 @@ export function useTalerActionURL(): [
|
|||||||
undefined,
|
undefined,
|
||||||
);
|
);
|
||||||
const [dismissed, setDismissed] = useState(false);
|
const [dismissed, setDismissed] = useState(false);
|
||||||
const { findTalerUriInActiveTab } = useIocContext()
|
const { findTalerUriInActiveTab } = useIocContext();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function check(): Promise<void> {
|
async function check(): Promise<void> {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,348 +1,327 @@
|
|||||||
export const amber = {
|
export const amber = {
|
||||||
50: '#fff8e1',
|
50: "#fff8e1",
|
||||||
100: '#ffecb3',
|
100: "#ffecb3",
|
||||||
200: '#ffe082',
|
200: "#ffe082",
|
||||||
300: '#ffd54f',
|
300: "#ffd54f",
|
||||||
400: '#ffca28',
|
400: "#ffca28",
|
||||||
500: '#ffc107',
|
500: "#ffc107",
|
||||||
600: '#ffb300',
|
600: "#ffb300",
|
||||||
700: '#ffa000',
|
700: "#ffa000",
|
||||||
800: '#ff8f00',
|
800: "#ff8f00",
|
||||||
900: '#ff6f00',
|
900: "#ff6f00",
|
||||||
A100: '#ffe57f',
|
A100: "#ffe57f",
|
||||||
A200: '#ffd740',
|
A200: "#ffd740",
|
||||||
A400: '#ffc400',
|
A400: "#ffc400",
|
||||||
A700: '#ffab00',
|
A700: "#ffab00",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const blueGrey = {
|
export const blueGrey = {
|
||||||
50: '#eceff1',
|
50: "#eceff1",
|
||||||
100: '#cfd8dc',
|
100: "#cfd8dc",
|
||||||
200: '#b0bec5',
|
200: "#b0bec5",
|
||||||
300: '#90a4ae',
|
300: "#90a4ae",
|
||||||
400: '#78909c',
|
400: "#78909c",
|
||||||
500: '#607d8b',
|
500: "#607d8b",
|
||||||
600: '#546e7a',
|
600: "#546e7a",
|
||||||
700: '#455a64',
|
700: "#455a64",
|
||||||
800: '#37474f',
|
800: "#37474f",
|
||||||
900: '#263238',
|
900: "#263238",
|
||||||
A100: '#cfd8dc',
|
A100: "#cfd8dc",
|
||||||
A200: '#b0bec5',
|
A200: "#b0bec5",
|
||||||
A400: '#78909c',
|
A400: "#78909c",
|
||||||
A700: '#455a64',
|
A700: "#455a64",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const blue = {
|
export const blue = {
|
||||||
50: '#e3f2fd',
|
50: "#e3f2fd",
|
||||||
100: '#bbdefb',
|
100: "#bbdefb",
|
||||||
200: '#90caf9',
|
200: "#90caf9",
|
||||||
300: '#64b5f6',
|
300: "#64b5f6",
|
||||||
400: '#42a5f5',
|
400: "#42a5f5",
|
||||||
500: '#2196f3',
|
500: "#2196f3",
|
||||||
600: '#1e88e5',
|
600: "#1e88e5",
|
||||||
700: '#1976d2',
|
700: "#1976d2",
|
||||||
800: '#1565c0',
|
800: "#1565c0",
|
||||||
900: '#0d47a1',
|
900: "#0d47a1",
|
||||||
A100: '#82b1ff',
|
A100: "#82b1ff",
|
||||||
A200: '#448aff',
|
A200: "#448aff",
|
||||||
A400: '#2979ff',
|
A400: "#2979ff",
|
||||||
A700: '#2962ff',
|
A700: "#2962ff",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const brown = {
|
export const brown = {
|
||||||
50: '#efebe9',
|
50: "#efebe9",
|
||||||
100: '#d7ccc8',
|
100: "#d7ccc8",
|
||||||
200: '#bcaaa4',
|
200: "#bcaaa4",
|
||||||
300: '#a1887f',
|
300: "#a1887f",
|
||||||
400: '#8d6e63',
|
400: "#8d6e63",
|
||||||
500: '#795548',
|
500: "#795548",
|
||||||
600: '#6d4c41',
|
600: "#6d4c41",
|
||||||
700: '#5d4037',
|
700: "#5d4037",
|
||||||
800: '#4e342e',
|
800: "#4e342e",
|
||||||
900: '#3e2723',
|
900: "#3e2723",
|
||||||
A100: '#d7ccc8',
|
A100: "#d7ccc8",
|
||||||
A200: '#bcaaa4',
|
A200: "#bcaaa4",
|
||||||
A400: '#8d6e63',
|
A400: "#8d6e63",
|
||||||
A700: '#5d4037',
|
A700: "#5d4037",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const common = {
|
export const common = {
|
||||||
black: '#000',
|
black: "#000",
|
||||||
white: '#fff',
|
white: "#fff",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const cyan = {
|
export const cyan = {
|
||||||
50: '#e0f7fa',
|
50: "#e0f7fa",
|
||||||
100: '#b2ebf2',
|
100: "#b2ebf2",
|
||||||
200: '#80deea',
|
200: "#80deea",
|
||||||
300: '#4dd0e1',
|
300: "#4dd0e1",
|
||||||
400: '#26c6da',
|
400: "#26c6da",
|
||||||
500: '#00bcd4',
|
500: "#00bcd4",
|
||||||
600: '#00acc1',
|
600: "#00acc1",
|
||||||
700: '#0097a7',
|
700: "#0097a7",
|
||||||
800: '#00838f',
|
800: "#00838f",
|
||||||
900: '#006064',
|
900: "#006064",
|
||||||
A100: '#84ffff',
|
A100: "#84ffff",
|
||||||
A200: '#18ffff',
|
A200: "#18ffff",
|
||||||
A400: '#00e5ff',
|
A400: "#00e5ff",
|
||||||
A700: '#00b8d4',
|
A700: "#00b8d4",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const deepOrange = {
|
export const deepOrange = {
|
||||||
50: '#fbe9e7',
|
50: "#fbe9e7",
|
||||||
100: '#ffccbc',
|
100: "#ffccbc",
|
||||||
200: '#ffab91',
|
200: "#ffab91",
|
||||||
300: '#ff8a65',
|
300: "#ff8a65",
|
||||||
400: '#ff7043',
|
400: "#ff7043",
|
||||||
500: '#ff5722',
|
500: "#ff5722",
|
||||||
600: '#f4511e',
|
600: "#f4511e",
|
||||||
700: '#e64a19',
|
700: "#e64a19",
|
||||||
800: '#d84315',
|
800: "#d84315",
|
||||||
900: '#bf360c',
|
900: "#bf360c",
|
||||||
A100: '#ff9e80',
|
A100: "#ff9e80",
|
||||||
A200: '#ff6e40',
|
A200: "#ff6e40",
|
||||||
A400: '#ff3d00',
|
A400: "#ff3d00",
|
||||||
A700: '#dd2c00',
|
A700: "#dd2c00",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const deepPurple = {
|
export const deepPurple = {
|
||||||
50: '#ede7f6',
|
50: "#ede7f6",
|
||||||
100: '#d1c4e9',
|
100: "#d1c4e9",
|
||||||
200: '#b39ddb',
|
200: "#b39ddb",
|
||||||
300: '#9575cd',
|
300: "#9575cd",
|
||||||
400: '#7e57c2',
|
400: "#7e57c2",
|
||||||
500: '#673ab7',
|
500: "#673ab7",
|
||||||
600: '#5e35b1',
|
600: "#5e35b1",
|
||||||
700: '#512da8',
|
700: "#512da8",
|
||||||
800: '#4527a0',
|
800: "#4527a0",
|
||||||
900: '#311b92',
|
900: "#311b92",
|
||||||
A100: '#b388ff',
|
A100: "#b388ff",
|
||||||
A200: '#7c4dff',
|
A200: "#7c4dff",
|
||||||
A400: '#651fff',
|
A400: "#651fff",
|
||||||
A700: '#6200ea',
|
A700: "#6200ea",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const green = {
|
export const green = {
|
||||||
50: '#e8f5e9',
|
50: "#e8f5e9",
|
||||||
100: '#c8e6c9',
|
100: "#c8e6c9",
|
||||||
200: '#a5d6a7',
|
200: "#a5d6a7",
|
||||||
300: '#81c784',
|
300: "#81c784",
|
||||||
400: '#66bb6a',
|
400: "#66bb6a",
|
||||||
500: '#4caf50',
|
500: "#4caf50",
|
||||||
600: '#43a047',
|
600: "#43a047",
|
||||||
700: '#388e3c',
|
700: "#388e3c",
|
||||||
800: '#2e7d32',
|
800: "#2e7d32",
|
||||||
900: '#1b5e20',
|
900: "#1b5e20",
|
||||||
A100: '#b9f6ca',
|
A100: "#b9f6ca",
|
||||||
A200: '#69f0ae',
|
A200: "#69f0ae",
|
||||||
A400: '#00e676',
|
A400: "#00e676",
|
||||||
A700: '#00c853',
|
A700: "#00c853",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const grey = {
|
export const grey = {
|
||||||
50: '#fafafa',
|
50: "#fafafa",
|
||||||
100: '#f5f5f5',
|
100: "#f5f5f5",
|
||||||
200: '#eeeeee',
|
200: "#eeeeee",
|
||||||
300: '#e0e0e0',
|
300: "#e0e0e0",
|
||||||
400: '#bdbdbd',
|
400: "#bdbdbd",
|
||||||
500: '#9e9e9e',
|
500: "#9e9e9e",
|
||||||
600: '#757575',
|
600: "#757575",
|
||||||
700: '#616161',
|
700: "#616161",
|
||||||
800: '#424242',
|
800: "#424242",
|
||||||
900: '#212121',
|
900: "#212121",
|
||||||
A100: '#f5f5f5',
|
A100: "#f5f5f5",
|
||||||
A200: '#eeeeee',
|
A200: "#eeeeee",
|
||||||
A400: '#bdbdbd',
|
A400: "#bdbdbd",
|
||||||
A700: '#616161',
|
A700: "#616161",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const indigo = {
|
export const indigo = {
|
||||||
50: '#e8eaf6',
|
50: "#e8eaf6",
|
||||||
100: '#c5cae9',
|
100: "#c5cae9",
|
||||||
200: '#9fa8da',
|
200: "#9fa8da",
|
||||||
300: '#7986cb',
|
300: "#7986cb",
|
||||||
400: '#5c6bc0',
|
400: "#5c6bc0",
|
||||||
500: '#3f51b5',
|
500: "#3f51b5",
|
||||||
600: '#3949ab',
|
600: "#3949ab",
|
||||||
700: '#303f9f',
|
700: "#303f9f",
|
||||||
800: '#283593',
|
800: "#283593",
|
||||||
900: '#1a237e',
|
900: "#1a237e",
|
||||||
A100: '#8c9eff',
|
A100: "#8c9eff",
|
||||||
A200: '#536dfe',
|
A200: "#536dfe",
|
||||||
A400: '#3d5afe',
|
A400: "#3d5afe",
|
||||||
A700: '#304ffe',
|
A700: "#304ffe",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const lightBlue = {
|
export const lightBlue = {
|
||||||
50: '#e1f5fe',
|
50: "#e1f5fe",
|
||||||
100: '#b3e5fc',
|
100: "#b3e5fc",
|
||||||
200: '#81d4fa',
|
200: "#81d4fa",
|
||||||
300: '#4fc3f7',
|
300: "#4fc3f7",
|
||||||
400: '#29b6f6',
|
400: "#29b6f6",
|
||||||
500: '#03a9f4',
|
500: "#03a9f4",
|
||||||
600: '#039be5',
|
600: "#039be5",
|
||||||
700: '#0288d1',
|
700: "#0288d1",
|
||||||
800: '#0277bd',
|
800: "#0277bd",
|
||||||
900: '#01579b',
|
900: "#01579b",
|
||||||
A100: '#80d8ff',
|
A100: "#80d8ff",
|
||||||
A200: '#40c4ff',
|
A200: "#40c4ff",
|
||||||
A400: '#00b0ff',
|
A400: "#00b0ff",
|
||||||
A700: '#0091ea',
|
A700: "#0091ea",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const lightGreen = {
|
export const lightGreen = {
|
||||||
50: '#f1f8e9',
|
50: "#f1f8e9",
|
||||||
100: '#dcedc8',
|
100: "#dcedc8",
|
||||||
200: '#c5e1a5',
|
200: "#c5e1a5",
|
||||||
300: '#aed581',
|
300: "#aed581",
|
||||||
400: '#9ccc65',
|
400: "#9ccc65",
|
||||||
500: '#8bc34a',
|
500: "#8bc34a",
|
||||||
600: '#7cb342',
|
600: "#7cb342",
|
||||||
700: '#689f38',
|
700: "#689f38",
|
||||||
800: '#558b2f',
|
800: "#558b2f",
|
||||||
900: '#33691e',
|
900: "#33691e",
|
||||||
A100: '#ccff90',
|
A100: "#ccff90",
|
||||||
A200: '#b2ff59',
|
A200: "#b2ff59",
|
||||||
A400: '#76ff03',
|
A400: "#76ff03",
|
||||||
A700: '#64dd17',
|
A700: "#64dd17",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const lime = {
|
export const lime = {
|
||||||
50: '#f9fbe7',
|
50: "#f9fbe7",
|
||||||
100: '#f0f4c3',
|
100: "#f0f4c3",
|
||||||
200: '#e6ee9c',
|
200: "#e6ee9c",
|
||||||
300: '#dce775',
|
300: "#dce775",
|
||||||
400: '#d4e157',
|
400: "#d4e157",
|
||||||
500: '#cddc39',
|
500: "#cddc39",
|
||||||
600: '#c0ca33',
|
600: "#c0ca33",
|
||||||
700: '#afb42b',
|
700: "#afb42b",
|
||||||
800: '#9e9d24',
|
800: "#9e9d24",
|
||||||
900: '#827717',
|
900: "#827717",
|
||||||
A100: '#f4ff81',
|
A100: "#f4ff81",
|
||||||
A200: '#eeff41',
|
A200: "#eeff41",
|
||||||
A400: '#c6ff00',
|
A400: "#c6ff00",
|
||||||
A700: '#aeea00',
|
A700: "#aeea00",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const orange = {
|
export const orange = {
|
||||||
50: '#fff3e0',
|
50: "#fff3e0",
|
||||||
100: '#ffe0b2',
|
100: "#ffe0b2",
|
||||||
200: '#ffcc80',
|
200: "#ffcc80",
|
||||||
300: '#ffb74d',
|
300: "#ffb74d",
|
||||||
400: '#ffa726',
|
400: "#ffa726",
|
||||||
500: '#ff9800',
|
500: "#ff9800",
|
||||||
600: '#fb8c00',
|
600: "#fb8c00",
|
||||||
700: '#f57c00',
|
700: "#f57c00",
|
||||||
800: '#ef6c00',
|
800: "#ef6c00",
|
||||||
900: '#e65100',
|
900: "#e65100",
|
||||||
A100: '#ffd180',
|
A100: "#ffd180",
|
||||||
A200: '#ffab40',
|
A200: "#ffab40",
|
||||||
A400: '#ff9100',
|
A400: "#ff9100",
|
||||||
A700: '#ff6d00',
|
A700: "#ff6d00",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const pink = {
|
export const pink = {
|
||||||
50: '#fce4ec',
|
50: "#fce4ec",
|
||||||
100: '#f8bbd0',
|
100: "#f8bbd0",
|
||||||
200: '#f48fb1',
|
200: "#f48fb1",
|
||||||
300: '#f06292',
|
300: "#f06292",
|
||||||
400: '#ec407a',
|
400: "#ec407a",
|
||||||
500: '#e91e63',
|
500: "#e91e63",
|
||||||
600: '#d81b60',
|
600: "#d81b60",
|
||||||
700: '#c2185b',
|
700: "#c2185b",
|
||||||
800: '#ad1457',
|
800: "#ad1457",
|
||||||
900: '#880e4f',
|
900: "#880e4f",
|
||||||
A100: '#ff80ab',
|
A100: "#ff80ab",
|
||||||
A200: '#ff4081',
|
A200: "#ff4081",
|
||||||
A400: '#f50057',
|
A400: "#f50057",
|
||||||
A700: '#c51162',
|
A700: "#c51162",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const purple = {
|
export const purple = {
|
||||||
50: '#f3e5f5',
|
50: "#f3e5f5",
|
||||||
100: '#e1bee7',
|
100: "#e1bee7",
|
||||||
200: '#ce93d8',
|
200: "#ce93d8",
|
||||||
300: '#ba68c8',
|
300: "#ba68c8",
|
||||||
400: '#ab47bc',
|
400: "#ab47bc",
|
||||||
500: '#9c27b0',
|
500: "#9c27b0",
|
||||||
600: '#8e24aa',
|
600: "#8e24aa",
|
||||||
700: '#7b1fa2',
|
700: "#7b1fa2",
|
||||||
800: '#6a1b9a',
|
800: "#6a1b9a",
|
||||||
900: '#4a148c',
|
900: "#4a148c",
|
||||||
A100: '#ea80fc',
|
A100: "#ea80fc",
|
||||||
A200: '#e040fb',
|
A200: "#e040fb",
|
||||||
A400: '#d500f9',
|
A400: "#d500f9",
|
||||||
A700: '#aa00ff',
|
A700: "#aa00ff",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const red = {
|
export const red = {
|
||||||
50: '#ffebee',
|
50: "#ffebee",
|
||||||
100: '#ffcdd2',
|
100: "#ffcdd2",
|
||||||
200: '#ef9a9a',
|
200: "#ef9a9a",
|
||||||
300: '#e57373',
|
300: "#e57373",
|
||||||
400: '#ef5350',
|
400: "#ef5350",
|
||||||
500: '#f44336',
|
500: "#f44336",
|
||||||
600: '#e53935',
|
600: "#e53935",
|
||||||
700: '#d32f2f',
|
700: "#d32f2f",
|
||||||
800: '#c62828',
|
800: "#c62828",
|
||||||
900: '#b71c1c',
|
900: "#b71c1c",
|
||||||
A100: '#ff8a80',
|
A100: "#ff8a80",
|
||||||
A200: '#ff5252',
|
A200: "#ff5252",
|
||||||
A400: '#ff1744',
|
A400: "#ff1744",
|
||||||
A700: '#d50000',
|
A700: "#d50000",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const teal = {
|
export const teal = {
|
||||||
50: '#e0f2f1',
|
50: "#e0f2f1",
|
||||||
100: '#b2dfdb',
|
100: "#b2dfdb",
|
||||||
200: '#80cbc4',
|
200: "#80cbc4",
|
||||||
300: '#4db6ac',
|
300: "#4db6ac",
|
||||||
400: '#26a69a',
|
400: "#26a69a",
|
||||||
500: '#009688',
|
500: "#009688",
|
||||||
600: '#00897b',
|
600: "#00897b",
|
||||||
700: '#00796b',
|
700: "#00796b",
|
||||||
800: '#00695c',
|
800: "#00695c",
|
||||||
900: '#004d40',
|
900: "#004d40",
|
||||||
A100: '#a7ffeb',
|
A100: "#a7ffeb",
|
||||||
A200: '#64ffda',
|
A200: "#64ffda",
|
||||||
A400: '#1de9b6',
|
A400: "#1de9b6",
|
||||||
A700: '#00bfa5',
|
A700: "#00bfa5",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const yellow = {
|
export const yellow = {
|
||||||
50: '#fffde7',
|
50: "#fffde7",
|
||||||
100: '#fff9c4',
|
100: "#fff9c4",
|
||||||
200: '#fff59d',
|
200: "#fff59d",
|
||||||
300: '#fff176',
|
300: "#fff176",
|
||||||
400: '#ffee58',
|
400: "#ffee58",
|
||||||
500: '#ffeb3b',
|
500: "#ffeb3b",
|
||||||
600: '#fdd835',
|
600: "#fdd835",
|
||||||
700: '#fbc02d',
|
700: "#fbc02d",
|
||||||
800: '#f9a825',
|
800: "#f9a825",
|
||||||
900: '#f57f17',
|
900: "#f57f17",
|
||||||
A100: '#ffff8d',
|
A100: "#ffff8d",
|
||||||
A200: '#ffff00',
|
A200: "#ffff00",
|
||||||
A400: '#ffea00',
|
A400: "#ffea00",
|
||||||
A700: '#ffd600',
|
A700: "#ffd600",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { expect } from 'chai';
|
import { expect } from "chai";
|
||||||
import {
|
import {
|
||||||
recomposeColor,
|
recomposeColor,
|
||||||
hexToRgb,
|
hexToRgb,
|
||||||
@ -11,295 +11,308 @@ import {
|
|||||||
getContrastRatio,
|
getContrastRatio,
|
||||||
getLuminance,
|
getLuminance,
|
||||||
lighten,
|
lighten,
|
||||||
} from './manipulation.js';
|
} from "./manipulation.js";
|
||||||
|
|
||||||
describe('utils/colorManipulator', () => {
|
describe("utils/colorManipulator", () => {
|
||||||
describe('recomposeColor', () => {
|
describe("recomposeColor", () => {
|
||||||
it('converts a decomposed rgb color object to a string` ', () => {
|
it("converts a decomposed rgb color object to a string` ", () => {
|
||||||
expect(
|
expect(
|
||||||
recomposeColor({
|
recomposeColor({
|
||||||
type: 'rgb',
|
type: "rgb",
|
||||||
values: [255, 255, 255],
|
values: [255, 255, 255],
|
||||||
}),
|
}),
|
||||||
).to.equal('rgb(255, 255, 255)');
|
).to.equal("rgb(255, 255, 255)");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('converts a decomposed rgba color object to a string` ', () => {
|
it("converts a decomposed rgba color object to a string` ", () => {
|
||||||
expect(
|
expect(
|
||||||
recomposeColor({
|
recomposeColor({
|
||||||
type: 'rgba',
|
type: "rgba",
|
||||||
values: [255, 255, 255, 0.5],
|
values: [255, 255, 255, 0.5],
|
||||||
}),
|
}),
|
||||||
).to.equal('rgba(255, 255, 255, 0.5)');
|
).to.equal("rgba(255, 255, 255, 0.5)");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('converts a decomposed hsl color object to a string` ', () => {
|
it("converts a decomposed hsl color object to a string` ", () => {
|
||||||
expect(
|
expect(
|
||||||
recomposeColor({
|
recomposeColor({
|
||||||
type: 'hsl',
|
type: "hsl",
|
||||||
values: [100, 50, 25],
|
values: [100, 50, 25],
|
||||||
}),
|
}),
|
||||||
).to.equal('hsl(100, 50%, 25%)');
|
).to.equal("hsl(100, 50%, 25%)");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('converts a decomposed hsla color object to a string` ', () => {
|
it("converts a decomposed hsla color object to a string` ", () => {
|
||||||
expect(
|
expect(
|
||||||
recomposeColor({
|
recomposeColor({
|
||||||
type: 'hsla',
|
type: "hsla",
|
||||||
values: [100, 50, 25, 0.5],
|
values: [100, 50, 25, 0.5],
|
||||||
}),
|
}),
|
||||||
).to.equal('hsla(100, 50%, 25%, 0.5)');
|
).to.equal("hsla(100, 50%, 25%, 0.5)");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('hexToRgb', () => {
|
describe("hexToRgb", () => {
|
||||||
it('converts a short hex color to an rgb color` ', () => {
|
it("converts a short hex color to an rgb color` ", () => {
|
||||||
expect(hexToRgb('#9f3')).to.equal('rgb(153, 255, 51)');
|
expect(hexToRgb("#9f3")).to.equal("rgb(153, 255, 51)");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('converts a long hex color to an rgb color` ', () => {
|
it("converts a long hex color to an rgb color` ", () => {
|
||||||
expect(hexToRgb('#a94fd3')).to.equal('rgb(169, 79, 211)');
|
expect(hexToRgb("#a94fd3")).to.equal("rgb(169, 79, 211)");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('converts a long alpha hex color to an argb color` ', () => {
|
it("converts a long alpha hex color to an argb color` ", () => {
|
||||||
expect(hexToRgb('#111111f8')).to.equal('rgba(17, 17, 17, 0.973)');
|
expect(hexToRgb("#111111f8")).to.equal("rgba(17, 17, 17, 0.973)");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('rgbToHex', () => {
|
describe("rgbToHex", () => {
|
||||||
it('converts an rgb color to a hex color` ', () => {
|
it("converts an rgb color to a hex color` ", () => {
|
||||||
expect(rgbToHex('rgb(169, 79, 211)')).to.equal('#a94fd3');
|
expect(rgbToHex("rgb(169, 79, 211)")).to.equal("#a94fd3");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('converts an rgba color to a hex color` ', () => {
|
it("converts an rgba color to a hex color` ", () => {
|
||||||
expect(rgbToHex('rgba(169, 79, 211, 1)')).to.equal('#a94fd3ff');
|
expect(rgbToHex("rgba(169, 79, 211, 1)")).to.equal("#a94fd3ff");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('idempotent', () => {
|
it("idempotent", () => {
|
||||||
expect(rgbToHex('#A94FD3')).to.equal('#A94FD3');
|
expect(rgbToHex("#A94FD3")).to.equal("#A94FD3");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('hslToRgb', () => {
|
describe("hslToRgb", () => {
|
||||||
it('converts an hsl color to an rgb color` ', () => {
|
it("converts an hsl color to an rgb color` ", () => {
|
||||||
expect(hslToRgb('hsl(281, 60%, 57%)')).to.equal('rgb(169, 80, 211)');
|
expect(hslToRgb("hsl(281, 60%, 57%)")).to.equal("rgb(169, 80, 211)");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('converts an hsla color to an rgba color` ', () => {
|
it("converts an hsla color to an rgba color` ", () => {
|
||||||
expect(hslToRgb('hsla(281, 60%, 57%, 0.5)')).to.equal('rgba(169, 80, 211, 0.5)');
|
expect(hslToRgb("hsla(281, 60%, 57%, 0.5)")).to.equal(
|
||||||
|
"rgba(169, 80, 211, 0.5)",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('allow to convert values only', () => {
|
it("allow to convert values only", () => {
|
||||||
expect(hslToRgb('hsl(281, 60%, 57%)')).to.equal('rgb(169, 80, 211)');
|
expect(hslToRgb("hsl(281, 60%, 57%)")).to.equal("rgb(169, 80, 211)");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('decomposeColor', () => {
|
describe("decomposeColor", () => {
|
||||||
it('converts an rgb color string to an object with `type` and `value` keys', () => {
|
it("converts an rgb color string to an object with `type` and `value` keys", () => {
|
||||||
const { type, values } = decomposeColor('rgb(255, 255, 255)');
|
const { type, values } = decomposeColor("rgb(255, 255, 255)");
|
||||||
expect(type).to.equal('rgb');
|
expect(type).to.equal("rgb");
|
||||||
expect(values).to.deep.equal([255, 255, 255]);
|
expect(values).to.deep.equal([255, 255, 255]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('converts an rgba color string to an object with `type` and `value` keys', () => {
|
it("converts an rgba color string to an object with `type` and `value` keys", () => {
|
||||||
const { type, values } = decomposeColor('rgba(255, 255, 255, 0.5)');
|
const { type, values } = decomposeColor("rgba(255, 255, 255, 0.5)");
|
||||||
expect(type).to.equal('rgba');
|
expect(type).to.equal("rgba");
|
||||||
expect(values).to.deep.equal([255, 255, 255, 0.5]);
|
expect(values).to.deep.equal([255, 255, 255, 0.5]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('converts an hsl color string to an object with `type` and `value` keys', () => {
|
it("converts an hsl color string to an object with `type` and `value` keys", () => {
|
||||||
const { type, values } = decomposeColor('hsl(100, 50%, 25%)');
|
const { type, values } = decomposeColor("hsl(100, 50%, 25%)");
|
||||||
expect(type).to.equal('hsl');
|
expect(type).to.equal("hsl");
|
||||||
expect(values).to.deep.equal([100, 50, 25]);
|
expect(values).to.deep.equal([100, 50, 25]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('converts an hsla color string to an object with `type` and `value` keys', () => {
|
it("converts an hsla color string to an object with `type` and `value` keys", () => {
|
||||||
const { type, values } = decomposeColor('hsla(100, 50%, 25%, 0.5)');
|
const { type, values } = decomposeColor("hsla(100, 50%, 25%, 0.5)");
|
||||||
expect(type).to.equal('hsla');
|
expect(type).to.equal("hsla");
|
||||||
expect(values).to.deep.equal([100, 50, 25, 0.5]);
|
expect(values).to.deep.equal([100, 50, 25, 0.5]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('converts rgba hex', () => {
|
it("converts rgba hex", () => {
|
||||||
const decomposed = decomposeColor('#111111f8');
|
const decomposed = decomposeColor("#111111f8");
|
||||||
expect(decomposed).to.deep.equal({
|
expect(decomposed).to.deep.equal({
|
||||||
type: 'rgba',
|
type: "rgba",
|
||||||
colorSpace: undefined,
|
colorSpace: undefined,
|
||||||
values: [17, 17, 17, 0.973],
|
values: [17, 17, 17, 0.973],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getContrastRatio', () => {
|
describe("getContrastRatio", () => {
|
||||||
it('returns a ratio for black : white', () => {
|
it("returns a ratio for black : white", () => {
|
||||||
expect(getContrastRatio('#000', '#FFF')).to.equal(21);
|
expect(getContrastRatio("#000", "#FFF")).to.equal(21);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns a ratio for black : black', () => {
|
it("returns a ratio for black : black", () => {
|
||||||
expect(getContrastRatio('#000', '#000')).to.equal(1);
|
expect(getContrastRatio("#000", "#000")).to.equal(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns a ratio for white : white', () => {
|
it("returns a ratio for white : white", () => {
|
||||||
expect(getContrastRatio('#FFF', '#FFF')).to.equal(1);
|
expect(getContrastRatio("#FFF", "#FFF")).to.equal(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns a ratio for dark-grey : light-grey', () => {
|
it("returns a ratio for dark-grey : light-grey", () => {
|
||||||
expect(getContrastRatio('#707070', '#E5E5E5')).to.be.approximately(3.93, 0.01);
|
expect(getContrastRatio("#707070", "#E5E5E5")).to.be.approximately(
|
||||||
|
3.93,
|
||||||
|
0.01,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns a ratio for black : light-grey', () => {
|
it("returns a ratio for black : light-grey", () => {
|
||||||
expect(getContrastRatio('#000', '#888')).to.be.approximately(5.92, 0.01);
|
expect(getContrastRatio("#000", "#888")).to.be.approximately(5.92, 0.01);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getLuminance', () => {
|
describe("getLuminance", () => {
|
||||||
|
it("returns a valid luminance for rgb white ", () => {
|
||||||
it('returns a valid luminance for rgb white ', () => {
|
expect(getLuminance("rgba(255, 255, 255)")).to.equal(1);
|
||||||
expect(getLuminance('rgba(255, 255, 255)')).to.equal(1);
|
expect(getLuminance("rgb(255, 255, 255)")).to.equal(1);
|
||||||
expect(getLuminance('rgb(255, 255, 255)')).to.equal(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns a valid luminance for rgb mid-grey', () => {
|
it("returns a valid luminance for rgb mid-grey", () => {
|
||||||
expect(getLuminance('rgba(127, 127, 127)')).to.equal(0.212);
|
expect(getLuminance("rgba(127, 127, 127)")).to.equal(0.212);
|
||||||
expect(getLuminance('rgb(127, 127, 127)')).to.equal(0.212);
|
expect(getLuminance("rgb(127, 127, 127)")).to.equal(0.212);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns a valid luminance for an rgb color', () => {
|
it("returns a valid luminance for an rgb color", () => {
|
||||||
expect(getLuminance('rgb(255, 127, 0)')).to.equal(0.364);
|
expect(getLuminance("rgb(255, 127, 0)")).to.equal(0.364);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns a valid luminance from an hsl color', () => {
|
it("returns a valid luminance from an hsl color", () => {
|
||||||
expect(getLuminance('hsl(100, 100%, 50%)')).to.equal(0.735);
|
expect(getLuminance("hsl(100, 100%, 50%)")).to.equal(0.735);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns an equal luminance for the same color in different formats', () => {
|
it("returns an equal luminance for the same color in different formats", () => {
|
||||||
const hsl = 'hsl(100, 100%, 50%)';
|
const hsl = "hsl(100, 100%, 50%)";
|
||||||
const rgb = 'rgb(85, 255, 0)';
|
const rgb = "rgb(85, 255, 0)";
|
||||||
expect(getLuminance(hsl)).to.equal(getLuminance(rgb));
|
expect(getLuminance(hsl)).to.equal(getLuminance(rgb));
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('emphasize', () => {
|
describe("emphasize", () => {
|
||||||
it('lightens a dark rgb color with the coefficient provided', () => {
|
it("lightens a dark rgb color with the coefficient provided", () => {
|
||||||
expect(emphasize('rgb(1, 2, 3)', 0.4)).to.equal(lighten('rgb(1, 2, 3)', 0.4));
|
expect(emphasize("rgb(1, 2, 3)", 0.4)).to.equal(
|
||||||
|
lighten("rgb(1, 2, 3)", 0.4),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('darkens a light rgb color with the coefficient provided', () => {
|
it("darkens a light rgb color with the coefficient provided", () => {
|
||||||
expect(emphasize('rgb(250, 240, 230)', 0.3)).to.equal(darken('rgb(250, 240, 230)', 0.3));
|
expect(emphasize("rgb(250, 240, 230)", 0.3)).to.equal(
|
||||||
|
darken("rgb(250, 240, 230)", 0.3),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('lightens a dark rgb color with the coefficient 0.15 by default', () => {
|
it("lightens a dark rgb color with the coefficient 0.15 by default", () => {
|
||||||
expect(emphasize('rgb(1, 2, 3)')).to.equal(lighten('rgb(1, 2, 3)', 0.15));
|
expect(emphasize("rgb(1, 2, 3)")).to.equal(lighten("rgb(1, 2, 3)", 0.15));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('darkens a light rgb color with the coefficient 0.15 by default', () => {
|
it("darkens a light rgb color with the coefficient 0.15 by default", () => {
|
||||||
expect(emphasize('rgb(250, 240, 230)')).to.equal(darken('rgb(250, 240, 230)', 0.15));
|
expect(emphasize("rgb(250, 240, 230)")).to.equal(
|
||||||
|
darken("rgb(250, 240, 230)", 0.15),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('alpha', () => {
|
describe("alpha", () => {
|
||||||
it('converts an rgb color to an rgba color with the value provided', () => {
|
it("converts an rgb color to an rgba color with the value provided", () => {
|
||||||
expect(alpha('rgb(1, 2, 3)', 0.4)).to.equal('rgba(1, 2, 3, 0.4)');
|
expect(alpha("rgb(1, 2, 3)", 0.4)).to.equal("rgba(1, 2, 3, 0.4)");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates an rgba color with the alpha value provided', () => {
|
it("updates an rgba color with the alpha value provided", () => {
|
||||||
expect(alpha('rgba(255, 0, 0, 0.2)', 0.5)).to.equal('rgba(255, 0, 0, 0.5)');
|
expect(alpha("rgba(255, 0, 0, 0.2)", 0.5)).to.equal(
|
||||||
|
"rgba(255, 0, 0, 0.5)",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('converts an hsl color to an hsla color with the value provided', () => {
|
it("converts an hsl color to an hsla color with the value provided", () => {
|
||||||
expect(alpha('hsl(0, 100%, 50%)', 0.1)).to.equal('hsla(0, 100%, 50%, 0.1)');
|
expect(alpha("hsl(0, 100%, 50%)", 0.1)).to.equal(
|
||||||
|
"hsla(0, 100%, 50%, 0.1)",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates an hsla color with the alpha value provided', () => {
|
it("updates an hsla color with the alpha value provided", () => {
|
||||||
expect(alpha('hsla(0, 100%, 50%, 0.2)', 0.5)).to.equal('hsla(0, 100%, 50%, 0.5)');
|
expect(alpha("hsla(0, 100%, 50%, 0.2)", 0.5)).to.equal(
|
||||||
|
"hsla(0, 100%, 50%, 0.5)",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('darken', () => {
|
describe("darken", () => {
|
||||||
it("doesn't modify rgb black", () => {
|
it("doesn't modify rgb black", () => {
|
||||||
expect(darken('rgb(0, 0, 0)', 0.1)).to.equal('rgb(0, 0, 0)');
|
expect(darken("rgb(0, 0, 0)", 0.1)).to.equal("rgb(0, 0, 0)");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('darkens rgb white to black when coefficient is 1', () => {
|
it("darkens rgb white to black when coefficient is 1", () => {
|
||||||
expect(darken('rgb(255, 255, 255)', 1)).to.equal('rgb(0, 0, 0)');
|
expect(darken("rgb(255, 255, 255)", 1)).to.equal("rgb(0, 0, 0)");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('retains the alpha value in an rgba color', () => {
|
it("retains the alpha value in an rgba color", () => {
|
||||||
expect(darken('rgba(0, 0, 0, 0.5)', 0.1)).to.equal('rgba(0, 0, 0, 0.5)');
|
expect(darken("rgba(0, 0, 0, 0.5)", 0.1)).to.equal("rgba(0, 0, 0, 0.5)");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('darkens rgb white by 10% when coefficient is 0.1', () => {
|
it("darkens rgb white by 10% when coefficient is 0.1", () => {
|
||||||
expect(darken('rgb(255, 255, 255)', 0.1)).to.equal('rgb(229, 229, 229)');
|
expect(darken("rgb(255, 255, 255)", 0.1)).to.equal("rgb(229, 229, 229)");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('darkens rgb red by 50% when coefficient is 0.5', () => {
|
it("darkens rgb red by 50% when coefficient is 0.5", () => {
|
||||||
expect(darken('rgb(255, 0, 0)', 0.5)).to.equal('rgb(127, 0, 0)');
|
expect(darken("rgb(255, 0, 0)", 0.5)).to.equal("rgb(127, 0, 0)");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('darkens rgb grey by 50% when coefficient is 0.5', () => {
|
it("darkens rgb grey by 50% when coefficient is 0.5", () => {
|
||||||
expect(darken('rgb(127, 127, 127)', 0.5)).to.equal('rgb(63, 63, 63)');
|
expect(darken("rgb(127, 127, 127)", 0.5)).to.equal("rgb(63, 63, 63)");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("doesn't modify rgb colors when coefficient is 0", () => {
|
it("doesn't modify rgb colors when coefficient is 0", () => {
|
||||||
expect(darken('rgb(255, 255, 255)', 0)).to.equal('rgb(255, 255, 255)');
|
expect(darken("rgb(255, 255, 255)", 0)).to.equal("rgb(255, 255, 255)");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('darkens hsl red by 50% when coefficient is 0.5', () => {
|
it("darkens hsl red by 50% when coefficient is 0.5", () => {
|
||||||
expect(darken('hsl(0, 100%, 50%)', 0.5)).to.equal('hsl(0, 100%, 25%)');
|
expect(darken("hsl(0, 100%, 50%)", 0.5)).to.equal("hsl(0, 100%, 25%)");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("doesn't modify hsl colors when coefficient is 0", () => {
|
it("doesn't modify hsl colors when coefficient is 0", () => {
|
||||||
expect(darken('hsl(0, 100%, 50%)', 0)).to.equal('hsl(0, 100%, 50%)');
|
expect(darken("hsl(0, 100%, 50%)", 0)).to.equal("hsl(0, 100%, 50%)");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("doesn't modify hsl colors when l is 0%", () => {
|
it("doesn't modify hsl colors when l is 0%", () => {
|
||||||
expect(darken('hsl(0, 50%, 0%)', 0.5)).to.equal('hsl(0, 50%, 0%)');
|
expect(darken("hsl(0, 50%, 0%)", 0.5)).to.equal("hsl(0, 50%, 0%)");
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('lighten', () => {
|
describe("lighten", () => {
|
||||||
it("doesn't modify rgb white", () => {
|
it("doesn't modify rgb white", () => {
|
||||||
expect(lighten('rgb(255, 255, 255)', 0.1)).to.equal('rgb(255, 255, 255)');
|
expect(lighten("rgb(255, 255, 255)", 0.1)).to.equal("rgb(255, 255, 255)");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('lightens rgb black to white when coefficient is 1', () => {
|
it("lightens rgb black to white when coefficient is 1", () => {
|
||||||
expect(lighten('rgb(0, 0, 0)', 1)).to.equal('rgb(255, 255, 255)');
|
expect(lighten("rgb(0, 0, 0)", 1)).to.equal("rgb(255, 255, 255)");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('retains the alpha value in an rgba color', () => {
|
it("retains the alpha value in an rgba color", () => {
|
||||||
expect(lighten('rgba(255, 255, 255, 0.5)', 0.1)).to.equal('rgba(255, 255, 255, 0.5)');
|
expect(lighten("rgba(255, 255, 255, 0.5)", 0.1)).to.equal(
|
||||||
|
"rgba(255, 255, 255, 0.5)",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('lightens rgb black by 10% when coefficient is 0.1', () => {
|
it("lightens rgb black by 10% when coefficient is 0.1", () => {
|
||||||
expect(lighten('rgb(0, 0, 0)', 0.1)).to.equal('rgb(25, 25, 25)');
|
expect(lighten("rgb(0, 0, 0)", 0.1)).to.equal("rgb(25, 25, 25)");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('lightens rgb red by 50% when coefficient is 0.5', () => {
|
it("lightens rgb red by 50% when coefficient is 0.5", () => {
|
||||||
expect(lighten('rgb(255, 0, 0)', 0.5)).to.equal('rgb(255, 127, 127)');
|
expect(lighten("rgb(255, 0, 0)", 0.5)).to.equal("rgb(255, 127, 127)");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('lightens rgb grey by 50% when coefficient is 0.5', () => {
|
it("lightens rgb grey by 50% when coefficient is 0.5", () => {
|
||||||
expect(lighten('rgb(127, 127, 127)', 0.5)).to.equal('rgb(191, 191, 191)');
|
expect(lighten("rgb(127, 127, 127)", 0.5)).to.equal("rgb(191, 191, 191)");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("doesn't modify rgb colors when coefficient is 0", () => {
|
it("doesn't modify rgb colors when coefficient is 0", () => {
|
||||||
expect(lighten('rgb(127, 127, 127)', 0)).to.equal('rgb(127, 127, 127)');
|
expect(lighten("rgb(127, 127, 127)", 0)).to.equal("rgb(127, 127, 127)");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('lightens hsl red by 50% when coefficient is 0.5', () => {
|
it("lightens hsl red by 50% when coefficient is 0.5", () => {
|
||||||
expect(lighten('hsl(0, 100%, 50%)', 0.5)).to.equal('hsl(0, 100%, 75%)');
|
expect(lighten("hsl(0, 100%, 50%)", 0.5)).to.equal("hsl(0, 100%, 75%)");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("doesn't modify hsl colors when coefficient is 0", () => {
|
it("doesn't modify hsl colors when coefficient is 0", () => {
|
||||||
expect(lighten('hsl(0, 100%, 50%)', 0)).to.equal('hsl(0, 100%, 50%)');
|
expect(lighten("hsl(0, 100%, 50%)", 0)).to.equal("hsl(0, 100%, 50%)");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("doesn't modify hsl colors when `l` is 100%", () => {
|
it("doesn't modify hsl colors when `l` is 100%", () => {
|
||||||
expect(lighten('hsl(0, 50%, 100%)', 0.5)).to.equal('hsl(0, 50%, 100%)');
|
expect(lighten("hsl(0, 50%, 100%)", 0.5)).to.equal("hsl(0, 50%, 100%)");
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,20 +1,18 @@
|
|||||||
|
export type ColorFormat = ColorFormatWithAlpha | ColorFormatWithoutAlpha;
|
||||||
export type ColorFormat = ColorFormatWithAlpha | ColorFormatWithoutAlpha
|
export type ColorFormatWithAlpha = "rgb" | "hsl";
|
||||||
export type ColorFormatWithAlpha = 'rgb' | 'hsl';
|
export type ColorFormatWithoutAlpha = "rgba" | "hsla";
|
||||||
export type ColorFormatWithoutAlpha = 'rgba' | 'hsla';
|
export type ColorObject = ColorObjectWithAlpha | ColorObjectWithoutAlpha;
|
||||||
export type ColorObject = ColorObjectWithAlpha | ColorObjectWithoutAlpha
|
|
||||||
export interface ColorObjectWithAlpha {
|
export interface ColorObjectWithAlpha {
|
||||||
type: ColorFormatWithAlpha;
|
type: ColorFormatWithAlpha;
|
||||||
values: [number, number, number];
|
values: [number, number, number];
|
||||||
colorSpace?: 'srgb' | 'display-p3' | 'a98-rgb' | 'prophoto-rgb' | 'rec-2020';
|
colorSpace?: "srgb" | "display-p3" | "a98-rgb" | "prophoto-rgb" | "rec-2020";
|
||||||
}
|
}
|
||||||
export interface ColorObjectWithoutAlpha {
|
export interface ColorObjectWithoutAlpha {
|
||||||
type: ColorFormatWithoutAlpha;
|
type: ColorFormatWithoutAlpha;
|
||||||
values: [number, number, number, number];
|
values: [number, number, number, number];
|
||||||
colorSpace?: 'srgb' | 'display-p3' | 'a98-rgb' | 'prophoto-rgb' | 'rec-2020';
|
colorSpace?: "srgb" | "display-p3" | "a98-rgb" | "prophoto-rgb" | "rec-2020";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a number whose value is limited to the given range.
|
* Returns a number whose value is limited to the given range.
|
||||||
* @param {number} value The value to be clamped
|
* @param {number} value The value to be clamped
|
||||||
@ -40,7 +38,7 @@ function clamp(value: number, min = 0, max = 1): number {
|
|||||||
export function hexToRgb(color: string): string {
|
export function hexToRgb(color: string): string {
|
||||||
color = color.substr(1);
|
color = color.substr(1);
|
||||||
|
|
||||||
const re = new RegExp(`.{1,${color.length >= 6 ? 2 : 1}}`, 'g');
|
const re = new RegExp(`.{1,${color.length >= 6 ? 2 : 1}}`, "g");
|
||||||
let colors = color.match(re);
|
let colors = color.match(re);
|
||||||
|
|
||||||
if (colors && colors[0].length === 1) {
|
if (colors && colors[0].length === 1) {
|
||||||
@ -48,12 +46,14 @@ export function hexToRgb(color: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return colors
|
return colors
|
||||||
? `rgb${colors.length === 4 ? 'a' : ''}(${colors
|
? `rgb${colors.length === 4 ? "a" : ""}(${colors
|
||||||
.map((n, index) => {
|
.map((n, index) => {
|
||||||
return index < 3 ? parseInt(n, 16) : Math.round((parseInt(n, 16) / 255) * 1000) / 1000;
|
return index < 3
|
||||||
})
|
? parseInt(n, 16)
|
||||||
.join(', ')})`
|
: Math.round((parseInt(n, 16) / 255) * 1000) / 1000;
|
||||||
: '';
|
})
|
||||||
|
.join(", ")})`
|
||||||
|
: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function intToHex(int: number): string {
|
function intToHex(int: number): string {
|
||||||
@ -70,23 +70,42 @@ function intToHex(int: number): string {
|
|||||||
*/
|
*/
|
||||||
export function decomposeColor(color: string): ColorObject {
|
export function decomposeColor(color: string): ColorObject {
|
||||||
const colorSpace = undefined;
|
const colorSpace = undefined;
|
||||||
if (color.charAt(0) === '#') {
|
if (color.charAt(0) === "#") {
|
||||||
return decomposeColor(hexToRgb(color));
|
return decomposeColor(hexToRgb(color));
|
||||||
}
|
}
|
||||||
|
|
||||||
const marker = color.indexOf('(');
|
const marker = color.indexOf("(");
|
||||||
const type = color.substring(0, marker);
|
const type = color.substring(0, marker);
|
||||||
// if (type != 'rgba' && type != 'hsla' && type != 'rgb' && type != 'hsl') {
|
// if (type != 'rgba' && type != 'hsla' && type != 'rgb' && type != 'hsl') {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const values = color.substring(marker + 1, color.length - 1).split(',')
|
const values = color.substring(marker + 1, color.length - 1).split(",");
|
||||||
if (type == 'rgb' || type == 'hsl') {
|
if (type == "rgb" || type == "hsl") {
|
||||||
return { type, colorSpace, values: [parseFloat(values[0]), parseFloat(values[1]), parseFloat(values[2])] }
|
return {
|
||||||
|
type,
|
||||||
|
colorSpace,
|
||||||
|
values: [
|
||||||
|
parseFloat(values[0]),
|
||||||
|
parseFloat(values[1]),
|
||||||
|
parseFloat(values[2]),
|
||||||
|
],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if (type == 'rgba' || type == 'hsla') {
|
if (type == "rgba" || type == "hsla") {
|
||||||
return { type, colorSpace, values: [parseFloat(values[0]), parseFloat(values[1]), parseFloat(values[2]), parseFloat(values[3])] }
|
return {
|
||||||
|
type,
|
||||||
|
colorSpace,
|
||||||
|
values: [
|
||||||
|
parseFloat(values[0]),
|
||||||
|
parseFloat(values[1]),
|
||||||
|
parseFloat(values[2]),
|
||||||
|
parseFloat(values[3]),
|
||||||
|
],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
throw new Error(`Unsupported '${color}' color. The following formats are supported: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()`)
|
throw new Error(
|
||||||
|
`Unsupported '${color}' color. The following formats are supported: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -100,19 +119,21 @@ export function recomposeColor(color: ColorObject): string {
|
|||||||
const { type, values: valuesNum } = color;
|
const { type, values: valuesNum } = color;
|
||||||
|
|
||||||
const valuesStr: string[] = [];
|
const valuesStr: string[] = [];
|
||||||
if (type.indexOf('rgb') !== -1) {
|
if (type.indexOf("rgb") !== -1) {
|
||||||
// Only convert the first 3 values to int (i.e. not alpha)
|
// Only convert the first 3 values to int (i.e. not alpha)
|
||||||
valuesNum.map((n, i) => (i < 3 ? parseInt(String(n), 10) : n)).forEach((n, i) => valuesStr[i] = String(n));
|
valuesNum
|
||||||
} else if (type.indexOf('hsl') !== -1) {
|
.map((n, i) => (i < 3 ? parseInt(String(n), 10) : n))
|
||||||
valuesStr[0] = String(valuesNum[0])
|
.forEach((n, i) => (valuesStr[i] = String(n)));
|
||||||
|
} else if (type.indexOf("hsl") !== -1) {
|
||||||
|
valuesStr[0] = String(valuesNum[0]);
|
||||||
valuesStr[1] = `${valuesNum[1]}%`;
|
valuesStr[1] = `${valuesNum[1]}%`;
|
||||||
valuesStr[2] = `${valuesNum[2]}%`;
|
valuesStr[2] = `${valuesNum[2]}%`;
|
||||||
if (type === 'hsla') {
|
if (type === "hsla") {
|
||||||
valuesStr[3] = String(valuesNum[3])
|
valuesStr[3] = String(valuesNum[3]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${type}(${valuesStr.join(', ')})`;
|
return `${type}(${valuesStr.join(", ")})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -122,12 +143,14 @@ export function recomposeColor(color: ColorObject): string {
|
|||||||
*/
|
*/
|
||||||
export function rgbToHex(color: string): string {
|
export function rgbToHex(color: string): string {
|
||||||
// Idempotent
|
// Idempotent
|
||||||
if (color.indexOf('#') === 0) {
|
if (color.indexOf("#") === 0) {
|
||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { values } = decomposeColor(color);
|
const { values } = decomposeColor(color);
|
||||||
return `#${values.map((n, i) => intToHex(i === 3 ? Math.round(255 * n) : n)).join('')}`;
|
return `#${values
|
||||||
|
.map((n, i) => intToHex(i === 3 ? Math.round(255 * n) : n))
|
||||||
|
.join("")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -142,24 +165,28 @@ export function hslToRgb(color: string): string {
|
|||||||
const s = values[1] / 100;
|
const s = values[1] / 100;
|
||||||
const l = values[2] / 100;
|
const l = values[2] / 100;
|
||||||
const a = s * Math.min(l, 1 - l);
|
const a = s * Math.min(l, 1 - l);
|
||||||
const f = (n: number, k = (n + h / 30) % 12): number => l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
const f = (n: number, k = (n + h / 30) % 12): number =>
|
||||||
|
l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
||||||
|
|
||||||
if (colorObj.type === 'hsla') {
|
if (colorObj.type === "hsla") {
|
||||||
return recomposeColor({
|
return recomposeColor({
|
||||||
type: 'rgba', values: [
|
type: "rgba",
|
||||||
|
values: [
|
||||||
Math.round(f(0) * 255),
|
Math.round(f(0) * 255),
|
||||||
Math.round(f(8) * 255),
|
Math.round(f(8) * 255),
|
||||||
Math.round(f(4) * 255),
|
Math.round(f(4) * 255),
|
||||||
colorObj.values[3]
|
colorObj.values[3],
|
||||||
]
|
],
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return recomposeColor({
|
return recomposeColor({
|
||||||
type: 'rgb', values: [
|
type: "rgb",
|
||||||
|
values: [
|
||||||
Math.round(f(0) * 255),
|
Math.round(f(0) * 255),
|
||||||
Math.round(f(8) * 255),
|
Math.round(f(8) * 255),
|
||||||
Math.round(f(4) * 255)]
|
Math.round(f(4) * 255),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -173,14 +200,19 @@ export function hslToRgb(color: string): string {
|
|||||||
export function getLuminance(color: string): number {
|
export function getLuminance(color: string): number {
|
||||||
const colorObj = decomposeColor(color);
|
const colorObj = decomposeColor(color);
|
||||||
|
|
||||||
const rgb2 = colorObj.type === 'hsl' ? decomposeColor(hslToRgb(color)).values : colorObj.values;
|
const rgb2 =
|
||||||
|
colorObj.type === "hsl"
|
||||||
|
? decomposeColor(hslToRgb(color)).values
|
||||||
|
: colorObj.values;
|
||||||
const rgb = rgb2.map((val) => {
|
const rgb = rgb2.map((val) => {
|
||||||
val /= 255; // normalized
|
val /= 255; // normalized
|
||||||
return val <= 0.03928 ? val / 12.92 : ((val + 0.055) / 1.055) ** 2.4;
|
return val <= 0.03928 ? val / 12.92 : ((val + 0.055) / 1.055) ** 2.4;
|
||||||
}) as typeof rgb2;
|
}) as typeof rgb2;
|
||||||
|
|
||||||
// Truncate at 3 digits
|
// Truncate at 3 digits
|
||||||
return Number((0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2]).toFixed(3));
|
return Number(
|
||||||
|
(0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2]).toFixed(3),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -191,7 +223,10 @@ export function getLuminance(color: string): number {
|
|||||||
* @param {string} background - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
|
* @param {string} background - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
|
||||||
* @returns {number} A contrast ratio value in the range 0 - 21.
|
* @returns {number} A contrast ratio value in the range 0 - 21.
|
||||||
*/
|
*/
|
||||||
export function getContrastRatio(foreground: string, background: string): number {
|
export function getContrastRatio(
|
||||||
|
foreground: string,
|
||||||
|
background: string,
|
||||||
|
): number {
|
||||||
const lumA = getLuminance(foreground);
|
const lumA = getLuminance(foreground);
|
||||||
const lumB = getLuminance(background);
|
const lumB = getLuminance(background);
|
||||||
return (Math.max(lumA, lumB) + 0.05) / (Math.min(lumA, lumB) + 0.05);
|
return (Math.max(lumA, lumB) + 0.05) / (Math.min(lumA, lumB) + 0.05);
|
||||||
@ -208,8 +243,8 @@ export function alpha(color: string, value: number): string {
|
|||||||
const colorObj = decomposeColor(color);
|
const colorObj = decomposeColor(color);
|
||||||
value = clamp(value);
|
value = clamp(value);
|
||||||
|
|
||||||
if (colorObj.type === 'rgb' || colorObj.type === 'hsl') {
|
if (colorObj.type === "rgb" || colorObj.type === "hsl") {
|
||||||
colorObj.type += 'a';
|
colorObj.type += "a";
|
||||||
}
|
}
|
||||||
colorObj.values[3] = value;
|
colorObj.values[3] = value;
|
||||||
|
|
||||||
@ -226,9 +261,12 @@ export function darken(color: string, coefficient: number): string {
|
|||||||
const colorObj = decomposeColor(color);
|
const colorObj = decomposeColor(color);
|
||||||
coefficient = clamp(coefficient);
|
coefficient = clamp(coefficient);
|
||||||
|
|
||||||
if (colorObj.type.indexOf('hsl') !== -1) {
|
if (colorObj.type.indexOf("hsl") !== -1) {
|
||||||
colorObj.values[2] *= 1 - coefficient;
|
colorObj.values[2] *= 1 - coefficient;
|
||||||
} else if (colorObj.type.indexOf('rgb') !== -1 || colorObj.type.indexOf('color') !== -1) {
|
} else if (
|
||||||
|
colorObj.type.indexOf("rgb") !== -1 ||
|
||||||
|
colorObj.type.indexOf("color") !== -1
|
||||||
|
) {
|
||||||
for (let i = 0; i < 3; i += 1) {
|
for (let i = 0; i < 3; i += 1) {
|
||||||
colorObj.values[i] *= 1 - coefficient;
|
colorObj.values[i] *= 1 - coefficient;
|
||||||
}
|
}
|
||||||
@ -246,13 +284,13 @@ export function lighten(color: string, coefficient: number): string {
|
|||||||
const colorObj = decomposeColor(color);
|
const colorObj = decomposeColor(color);
|
||||||
coefficient = clamp(coefficient);
|
coefficient = clamp(coefficient);
|
||||||
|
|
||||||
if (colorObj.type.indexOf('hsl') !== -1) {
|
if (colorObj.type.indexOf("hsl") !== -1) {
|
||||||
colorObj.values[2] += (100 - colorObj.values[2]) * coefficient;
|
colorObj.values[2] += (100 - colorObj.values[2]) * coefficient;
|
||||||
} else if (colorObj.type.indexOf('rgb') !== -1) {
|
} else if (colorObj.type.indexOf("rgb") !== -1) {
|
||||||
for (let i = 0; i < 3; i += 1) {
|
for (let i = 0; i < 3; i += 1) {
|
||||||
colorObj.values[i] += (255 - colorObj.values[i]) * coefficient;
|
colorObj.values[i] += (255 - colorObj.values[i]) * coefficient;
|
||||||
}
|
}
|
||||||
} else if (colorObj.type.indexOf('color') !== -1) {
|
} else if (colorObj.type.indexOf("color") !== -1) {
|
||||||
for (let i = 0; i < 3; i += 1) {
|
for (let i = 0; i < 3; i += 1) {
|
||||||
colorObj.values[i] += (1 - colorObj.values[i]) * coefficient;
|
colorObj.values[i] += (1 - colorObj.values[i]) * coefficient;
|
||||||
}
|
}
|
||||||
@ -269,5 +307,7 @@ export function lighten(color: string, coefficient: number): string {
|
|||||||
* @returns {string} A CSS color string. Hex input values are returned as rgb
|
* @returns {string} A CSS color string. Hex input values are returned as rgb
|
||||||
*/
|
*/
|
||||||
export function emphasize(color: string, coefficient = 0.15): string {
|
export function emphasize(color: string, coefficient = 0.15): string {
|
||||||
return getLuminance(color) > 0.5 ? darken(color, coefficient) : lighten(color, coefficient);
|
return getLuminance(color) > 0.5
|
||||||
|
? darken(color, coefficient)
|
||||||
|
: lighten(color, coefficient);
|
||||||
}
|
}
|
||||||
|
@ -23,4 +23,3 @@ export interface SelectFieldHandler {
|
|||||||
isDirty?: boolean;
|
isDirty?: boolean;
|
||||||
list: Record<string, string>;
|
list: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,12 +22,11 @@ export interface Permissions {
|
|||||||
*/
|
*/
|
||||||
permissions?: string[] | undefined;
|
permissions?: string[] | undefined;
|
||||||
/**
|
/**
|
||||||
* List of origin permissions. Anything listed here must be a subset of a
|
* List of origin permissions. Anything listed here must be a subset of a
|
||||||
* host that appears in the optional_permissions list in the manifest.
|
* host that appears in the optional_permissions list in the manifest.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
origins?: string[] | undefined;
|
origins?: string[] | undefined;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -38,8 +37,9 @@ export interface CrossBrowserPermissionsApi {
|
|||||||
requestHostPermissions(): Promise<boolean>;
|
requestHostPermissions(): Promise<boolean>;
|
||||||
removeHostPermissions(): Promise<boolean>;
|
removeHostPermissions(): Promise<boolean>;
|
||||||
|
|
||||||
addPermissionsListener(callback: (p: Permissions, lastError?: string) => void): void;
|
addPermissionsListener(
|
||||||
|
callback: (p: Permissions, lastError?: string) => void,
|
||||||
|
): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MessageFromBackend = {
|
export type MessageFromBackend = {
|
||||||
@ -57,12 +57,12 @@ export interface WalletVersion {
|
|||||||
*/
|
*/
|
||||||
export interface PlatformAPI {
|
export interface PlatformAPI {
|
||||||
/**
|
/**
|
||||||
* Garantee that the
|
* Garantee that the
|
||||||
*/
|
*/
|
||||||
keepAlive(cb: VoidFunction): void;
|
keepAlive(cb: VoidFunction): void;
|
||||||
/**
|
/**
|
||||||
* FIXME: should not be needed
|
* FIXME: should not be needed
|
||||||
*
|
*
|
||||||
* check if the platform is firefox
|
* check if the platform is firefox
|
||||||
*/
|
*/
|
||||||
isFirefox(): boolean;
|
isFirefox(): boolean;
|
||||||
@ -74,46 +74,46 @@ export interface PlatformAPI {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Backend API
|
* Backend API
|
||||||
*
|
*
|
||||||
* Register a callback to be called when the wallet is ready to start
|
* Register a callback to be called when the wallet is ready to start
|
||||||
* @param callback
|
* @param callback
|
||||||
*/
|
*/
|
||||||
notifyWhenAppIsReady(callback: () => void): void;
|
notifyWhenAppIsReady(callback: () => void): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Popup API
|
* Popup API
|
||||||
*
|
*
|
||||||
* Used when an TalerURI is found and open up from the popup UI.
|
* Used when an TalerURI is found and open up from the popup UI.
|
||||||
* Closes the popup and open the URI into the wallet UI.
|
* Closes the popup and open the URI into the wallet UI.
|
||||||
*
|
*
|
||||||
* @param talerUri
|
* @param talerUri
|
||||||
*/
|
*/
|
||||||
openWalletURIFromPopup(talerUri: string): void;
|
openWalletURIFromPopup(talerUri: string): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Backend API
|
* Backend API
|
||||||
*
|
*
|
||||||
* Open a page into the wallet UI
|
* Open a page into the wallet UI
|
||||||
* @param page
|
* @param page
|
||||||
*/
|
*/
|
||||||
openWalletPage(page: string): void;
|
openWalletPage(page: string): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Popup API
|
* Popup API
|
||||||
*
|
*
|
||||||
* Open a page into the wallet UI and closed the popup
|
* Open a page into the wallet UI and closed the popup
|
||||||
* @param page
|
* @param page
|
||||||
*/
|
*/
|
||||||
openWalletPageFromPopup(page: string): void;
|
openWalletPageFromPopup(page: string): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Backend API
|
* Backend API
|
||||||
*
|
*
|
||||||
* When a tab has been detected to have a Taler action the background process
|
* When a tab has been detected to have a Taler action the background process
|
||||||
* can use this function to redirect the tab to the wallet UI
|
* can use this function to redirect the tab to the wallet UI
|
||||||
*
|
*
|
||||||
* @param tabId
|
* @param tabId
|
||||||
* @param page
|
* @param page
|
||||||
*/
|
*/
|
||||||
redirectTabToWalletPage(tabId: number, page: string): void;
|
redirectTabToWalletPage(tabId: number, page: string): void;
|
||||||
|
|
||||||
@ -122,7 +122,6 @@ export interface PlatformAPI {
|
|||||||
*/
|
*/
|
||||||
getWalletVersion(): WalletVersion;
|
getWalletVersion(): WalletVersion;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Backend API
|
* Backend API
|
||||||
*/
|
*/
|
||||||
@ -134,7 +133,9 @@ export interface PlatformAPI {
|
|||||||
/**
|
/**
|
||||||
* Backend API
|
* Backend API
|
||||||
*/
|
*/
|
||||||
registerTalerHeaderListener(onHeader: (tabId: number, url: string) => void): void;
|
registerTalerHeaderListener(
|
||||||
|
onHeader: (tabId: number, url: string) => void,
|
||||||
|
): void;
|
||||||
/**
|
/**
|
||||||
* Frontend API
|
* Frontend API
|
||||||
*/
|
*/
|
||||||
@ -146,49 +147,60 @@ export interface PlatformAPI {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Backend API
|
* Backend API
|
||||||
*
|
*
|
||||||
* Check if background process run as service worker. This is used from the
|
* Check if background process run as service worker. This is used from the
|
||||||
* wallet use different http api and crypto worker.
|
* wallet use different http api and crypto worker.
|
||||||
*/
|
*/
|
||||||
useServiceWorkerAsBackgroundProcess(): boolean;
|
useServiceWorkerAsBackgroundProcess(): boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Popup API
|
* Popup API
|
||||||
*
|
*
|
||||||
* Read the current tab html and try to find any Taler URI or QR code present.
|
* Read the current tab html and try to find any Taler URI or QR code present.
|
||||||
*
|
*
|
||||||
* @return Taler URI if found
|
* @return Taler URI if found
|
||||||
*/
|
*/
|
||||||
findTalerUriInActiveTab(): Promise<string | undefined>;
|
findTalerUriInActiveTab(): Promise<string | undefined>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used from the frontend to send commands to the wallet
|
* Used from the frontend to send commands to the wallet
|
||||||
*
|
*
|
||||||
* @param operation
|
* @param operation
|
||||||
* @param payload
|
* @param payload
|
||||||
*
|
*
|
||||||
* @return response from the backend
|
* @return response from the backend
|
||||||
*/
|
*/
|
||||||
sendMessageToWalletBackground(operation: string, payload: any): Promise<CoreApiResponse>;
|
sendMessageToWalletBackground(
|
||||||
|
operation: string,
|
||||||
|
payload: any,
|
||||||
|
): Promise<CoreApiResponse>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used from the frontend to receive notifications about new information
|
* Used from the frontend to receive notifications about new information
|
||||||
* @param listener
|
* @param listener
|
||||||
* @return function to unsubscribe the listener
|
* @return function to unsubscribe the listener
|
||||||
*/
|
*/
|
||||||
listenToWalletBackground(listener: (message: MessageFromBackend) => void): () => void;
|
listenToWalletBackground(
|
||||||
|
listener: (message: MessageFromBackend) => void,
|
||||||
|
): () => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use by the wallet backend to receive operations from frontend (popup & wallet)
|
* Use by the wallet backend to receive operations from frontend (popup & wallet)
|
||||||
* and send a response back.
|
* and send a response back.
|
||||||
*
|
*
|
||||||
* @param onNewMessage
|
* @param onNewMessage
|
||||||
*/
|
*/
|
||||||
listenToAllChannels(onNewMessage: (message: any, sender: any, sendResponse: (r: CoreApiResponse) => void) => void): void;
|
listenToAllChannels(
|
||||||
|
onNewMessage: (
|
||||||
|
message: any,
|
||||||
|
sender: any,
|
||||||
|
sendResponse: (r: CoreApiResponse) => void,
|
||||||
|
) => void,
|
||||||
|
): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used by the wallet backend to send notification about new information
|
* Used by the wallet backend to send notification about new information
|
||||||
* @param message
|
* @param message
|
||||||
*/
|
*/
|
||||||
sendMessageToAllChannels(message: MessageFromBackend): void;
|
sendMessageToAllChannels(message: MessageFromBackend): void;
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,18 @@
|
|||||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { classifyTalerUri, CoreApiResponse, Logger, TalerUriType } from "@gnu-taler/taler-util";
|
import {
|
||||||
import { CrossBrowserPermissionsApi, MessageFromBackend, Permissions, PlatformAPI } from "./api.js";
|
classifyTalerUri,
|
||||||
|
CoreApiResponse,
|
||||||
|
Logger,
|
||||||
|
TalerUriType,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
|
import {
|
||||||
|
CrossBrowserPermissionsApi,
|
||||||
|
MessageFromBackend,
|
||||||
|
Permissions,
|
||||||
|
PlatformAPI,
|
||||||
|
} from "./api.js";
|
||||||
|
|
||||||
const api: PlatformAPI = {
|
const api: PlatformAPI = {
|
||||||
isFirefox,
|
isFirefox,
|
||||||
@ -39,7 +49,7 @@ const api: PlatformAPI = {
|
|||||||
useServiceWorkerAsBackgroundProcess,
|
useServiceWorkerAsBackgroundProcess,
|
||||||
containsTalerHeaderListener,
|
containsTalerHeaderListener,
|
||||||
keepAlive,
|
keepAlive,
|
||||||
}
|
};
|
||||||
|
|
||||||
export default api;
|
export default api;
|
||||||
|
|
||||||
@ -47,16 +57,15 @@ const logger = new Logger("chrome.ts");
|
|||||||
|
|
||||||
function keepAlive(callback: any): void {
|
function keepAlive(callback: any): void {
|
||||||
if (extensionIsManifestV3()) {
|
if (extensionIsManifestV3()) {
|
||||||
chrome.alarms.create("wallet-worker", { periodInMinutes: 1 })
|
chrome.alarms.create("wallet-worker", { periodInMinutes: 1 });
|
||||||
|
|
||||||
chrome.alarms.onAlarm.addListener((a) => {
|
chrome.alarms.onAlarm.addListener((a) => {
|
||||||
logger.trace(`kee p alive alarm: ${a.name}`)
|
logger.trace(`kee p alive alarm: ${a.name}`);
|
||||||
// callback()
|
// callback()
|
||||||
})
|
});
|
||||||
// } else {
|
// } else {
|
||||||
}
|
}
|
||||||
callback();
|
callback();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isFirefox(): boolean {
|
function isFirefox(): boolean {
|
||||||
@ -66,34 +75,35 @@ function isFirefox(): boolean {
|
|||||||
const hostPermissions = {
|
const hostPermissions = {
|
||||||
permissions: ["webRequest"],
|
permissions: ["webRequest"],
|
||||||
origins: ["http://*/*", "https://*/*"],
|
origins: ["http://*/*", "https://*/*"],
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
export function containsHostPermissions(): Promise<boolean> {
|
export function containsHostPermissions(): Promise<boolean> {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
chrome.permissions.contains(hostPermissions, (resp) => {
|
chrome.permissions.contains(hostPermissions, (resp) => {
|
||||||
const le = chrome.runtime.lastError?.message
|
const le = chrome.runtime.lastError?.message;
|
||||||
if (le) {
|
if (le) {
|
||||||
rej(le)
|
rej(le);
|
||||||
}
|
}
|
||||||
res(resp)
|
res(resp);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function requestHostPermissions(): Promise<boolean> {
|
export async function requestHostPermissions(): Promise<boolean> {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
chrome.permissions.request(hostPermissions, (resp) => {
|
chrome.permissions.request(hostPermissions, (resp) => {
|
||||||
const le = chrome.runtime.lastError?.message
|
const le = chrome.runtime.lastError?.message;
|
||||||
if (le) {
|
if (le) {
|
||||||
rej(le)
|
rej(le);
|
||||||
}
|
}
|
||||||
res(resp)
|
res(resp);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
type HeaderListenerFunc = (details: chrome.webRequest.WebResponseHeadersDetails) => void
|
type HeaderListenerFunc = (
|
||||||
|
details: chrome.webRequest.WebResponseHeadersDetails,
|
||||||
|
) => void;
|
||||||
let currentHeaderListener: HeaderListenerFunc | undefined = undefined;
|
let currentHeaderListener: HeaderListenerFunc | undefined = undefined;
|
||||||
|
|
||||||
export function containsTalerHeaderListener(): boolean {
|
export function containsTalerHeaderListener(): boolean {
|
||||||
@ -128,57 +138,69 @@ export async function removeHostPermissions(): Promise<boolean> {
|
|||||||
}
|
}
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
chrome.permissions.remove(hostPermissions, (resp) => {
|
chrome.permissions.remove(hostPermissions, (resp) => {
|
||||||
const le = chrome.runtime.lastError?.message
|
const le = chrome.runtime.lastError?.message;
|
||||||
if (le) {
|
if (le) {
|
||||||
rej(le)
|
rej(le);
|
||||||
}
|
}
|
||||||
res(resp)
|
res(resp);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function addPermissionsListener(callback: (p: Permissions, lastError?: string) => void): void {
|
function addPermissionsListener(
|
||||||
|
callback: (p: Permissions, lastError?: string) => void,
|
||||||
|
): void {
|
||||||
chrome.permissions.onAdded.addListener((perm: Permissions) => {
|
chrome.permissions.onAdded.addListener((perm: Permissions) => {
|
||||||
const lastError = chrome.runtime.lastError?.message;
|
const lastError = chrome.runtime.lastError?.message;
|
||||||
callback(perm, lastError)
|
callback(perm, lastError);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPermissionsApi(): CrossBrowserPermissionsApi {
|
function getPermissionsApi(): CrossBrowserPermissionsApi {
|
||||||
return {
|
return {
|
||||||
addPermissionsListener, containsHostPermissions, requestHostPermissions, removeHostPermissions
|
addPermissionsListener,
|
||||||
}
|
containsHostPermissions,
|
||||||
|
requestHostPermissions,
|
||||||
|
removeHostPermissions,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param callback function to be called
|
* @param callback function to be called
|
||||||
*/
|
*/
|
||||||
function notifyWhenAppIsReady(callback: () => void): void {
|
function notifyWhenAppIsReady(callback: () => void): void {
|
||||||
if (extensionIsManifestV3()) {
|
if (extensionIsManifestV3()) {
|
||||||
callback()
|
callback();
|
||||||
} else {
|
} else {
|
||||||
window.addEventListener("load", callback);
|
window.addEventListener("load", callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function openWalletURIFromPopup(talerUri: string): void {
|
function openWalletURIFromPopup(talerUri: string): void {
|
||||||
const uriType = classifyTalerUri(talerUri);
|
const uriType = classifyTalerUri(talerUri);
|
||||||
|
|
||||||
let url: string | undefined = undefined;
|
let url: string | undefined = undefined;
|
||||||
switch (uriType) {
|
switch (uriType) {
|
||||||
case TalerUriType.TalerWithdraw:
|
case TalerUriType.TalerWithdraw:
|
||||||
url = chrome.runtime.getURL(`static/wallet.html#/cta/withdraw?talerWithdrawUri=${talerUri}`);
|
url = chrome.runtime.getURL(
|
||||||
|
`static/wallet.html#/cta/withdraw?talerWithdrawUri=${talerUri}`,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case TalerUriType.TalerPay:
|
case TalerUriType.TalerPay:
|
||||||
url = chrome.runtime.getURL(`static/wallet.html#/cta/pay?talerPayUri=${talerUri}`);
|
url = chrome.runtime.getURL(
|
||||||
|
`static/wallet.html#/cta/pay?talerPayUri=${talerUri}`,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case TalerUriType.TalerTip:
|
case TalerUriType.TalerTip:
|
||||||
url = chrome.runtime.getURL(`static/wallet.html#/cta/tip?talerTipUri=${talerUri}`);
|
url = chrome.runtime.getURL(
|
||||||
|
`static/wallet.html#/cta/tip?talerTipUri=${talerUri}`,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case TalerUriType.TalerRefund:
|
case TalerUriType.TalerRefund:
|
||||||
url = chrome.runtime.getURL(`static/wallet.html#/cta/refund?talerRefundUri=${talerUri}`);
|
url = chrome.runtime.getURL(
|
||||||
|
`static/wallet.html#/cta/refund?talerRefundUri=${talerUri}`,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
logger.warn(
|
logger.warn(
|
||||||
@ -187,56 +209,54 @@ function openWalletURIFromPopup(talerUri: string): void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
chrome.tabs.create(
|
chrome.tabs.create({ active: true, url }, () => {
|
||||||
{ active: true, url, },
|
window.close();
|
||||||
() => { window.close(); },
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function openWalletPage(page: string): void {
|
function openWalletPage(page: string): void {
|
||||||
const url = chrome.runtime.getURL(`/static/wallet.html#${page}`)
|
const url = chrome.runtime.getURL(`/static/wallet.html#${page}`);
|
||||||
chrome.tabs.create(
|
chrome.tabs.create({ active: true, url });
|
||||||
{ active: true, url, },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function openWalletPageFromPopup(page: string): void {
|
function openWalletPageFromPopup(page: string): void {
|
||||||
const url = chrome.runtime.getURL(`/static/wallet.html#${page}`)
|
const url = chrome.runtime.getURL(`/static/wallet.html#${page}`);
|
||||||
chrome.tabs.create(
|
chrome.tabs.create({ active: true, url }, () => {
|
||||||
{ active: true, url, },
|
window.close();
|
||||||
() => { window.close(); },
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendMessageToWalletBackground(operation: string, payload: any): Promise<any> {
|
async function sendMessageToWalletBackground(
|
||||||
|
operation: string,
|
||||||
|
payload: any,
|
||||||
|
): Promise<any> {
|
||||||
return new Promise<any>((resolve, reject) => {
|
return new Promise<any>((resolve, reject) => {
|
||||||
logger.trace("send operation to the wallet background", operation)
|
logger.trace("send operation to the wallet background", operation);
|
||||||
chrome.runtime.sendMessage({ operation, payload, id: "(none)" }, (resp) => {
|
chrome.runtime.sendMessage({ operation, payload, id: "(none)" }, (resp) => {
|
||||||
if (chrome.runtime.lastError) {
|
if (chrome.runtime.lastError) {
|
||||||
reject(chrome.runtime.lastError.message)
|
reject(chrome.runtime.lastError.message);
|
||||||
}
|
}
|
||||||
resolve(resp)
|
resolve(resp);
|
||||||
// return true to keep the channel open
|
// return true to keep the channel open
|
||||||
return true;
|
return true;
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let notificationPort: chrome.runtime.Port | undefined;
|
let notificationPort: chrome.runtime.Port | undefined;
|
||||||
function listenToWalletBackground(listener: (m: any) => void): () => void {
|
function listenToWalletBackground(listener: (m: any) => void): () => void {
|
||||||
if (notificationPort === undefined) {
|
if (notificationPort === undefined) {
|
||||||
notificationPort = chrome.runtime.connect({ name: "notifications" })
|
notificationPort = chrome.runtime.connect({ name: "notifications" });
|
||||||
}
|
}
|
||||||
notificationPort.onMessage.addListener(listener)
|
notificationPort.onMessage.addListener(listener);
|
||||||
function removeListener(): void {
|
function removeListener(): void {
|
||||||
if (notificationPort !== undefined) {
|
if (notificationPort !== undefined) {
|
||||||
notificationPort.onMessage.removeListener(listener)
|
notificationPort.onMessage.removeListener(listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return removeListener
|
return removeListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const allPorts: chrome.runtime.Port[] = [];
|
const allPorts: chrome.runtime.Port[] = [];
|
||||||
|
|
||||||
function sendMessageToAllChannels(message: MessageFromBackend): void {
|
function sendMessageToAllChannels(message: MessageFromBackend): void {
|
||||||
@ -262,9 +282,15 @@ function registerAllIncomingConnections(): void {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function listenToAllChannels(cb: (message: any, sender: any, callback: (r: CoreApiResponse) => void) => void): void {
|
function listenToAllChannels(
|
||||||
|
cb: (
|
||||||
|
message: any,
|
||||||
|
sender: any,
|
||||||
|
callback: (r: CoreApiResponse) => void,
|
||||||
|
) => void,
|
||||||
|
): void {
|
||||||
chrome.runtime.onMessage.addListener((m, s, c) => {
|
chrome.runtime.onMessage.addListener((m, s, c) => {
|
||||||
cb(m, s, c)
|
cb(m, s, c);
|
||||||
|
|
||||||
// keep the connection open
|
// keep the connection open
|
||||||
return true;
|
return true;
|
||||||
@ -278,13 +304,9 @@ function registerReloadOnNewVersion(): void {
|
|||||||
logger.info("update available:", details);
|
logger.info("update available:", details);
|
||||||
chrome.runtime.reload();
|
chrome.runtime.reload();
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function redirectTabToWalletPage(
|
function redirectTabToWalletPage(tabId: number, page: string): void {
|
||||||
tabId: number,
|
|
||||||
page: string,
|
|
||||||
): void {
|
|
||||||
const url = chrome.runtime.getURL(`/static/wallet.html#${page}`);
|
const url = chrome.runtime.getURL(`/static/wallet.html#${page}`);
|
||||||
logger.trace("redirecting tabId: ", tabId, " to: ", url);
|
logger.trace("redirecting tabId: ", tabId, " to: ", url);
|
||||||
chrome.tabs.update(tabId, { url });
|
chrome.tabs.update(tabId, { url });
|
||||||
@ -300,7 +322,9 @@ function getWalletVersion(): WalletVersion {
|
|||||||
return manifestData;
|
return manifestData;
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerTalerHeaderListener(callback: (tabId: number, url: string) => void): void {
|
function registerTalerHeaderListener(
|
||||||
|
callback: (tabId: number, url: string) => void,
|
||||||
|
): void {
|
||||||
logger.trace("setting up header listener");
|
logger.trace("setting up header listener");
|
||||||
|
|
||||||
function headerListener(
|
function headerListener(
|
||||||
@ -316,44 +340,45 @@ function registerTalerHeaderListener(callback: (tabId: number, url: string) => v
|
|||||||
details.statusCode === 200
|
details.statusCode === 200
|
||||||
) {
|
) {
|
||||||
const values = (details.responseHeaders || [])
|
const values = (details.responseHeaders || [])
|
||||||
.filter(h => h.name.toLowerCase() === 'taler')
|
.filter((h) => h.name.toLowerCase() === "taler")
|
||||||
.map(h => h.value)
|
.map((h) => h.value)
|
||||||
.filter((value): value is string => !!value)
|
.filter((value): value is string => !!value);
|
||||||
if (values.length > 0) {
|
if (values.length > 0) {
|
||||||
callback(details.tabId, values[0])
|
callback(details.tabId, values[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const prevHeaderListener = currentHeaderListener;
|
const prevHeaderListener = currentHeaderListener;
|
||||||
|
|
||||||
getPermissionsApi().containsHostPermissions().then(result => {
|
getPermissionsApi()
|
||||||
//if there is a handler already, remove it
|
.containsHostPermissions()
|
||||||
if (
|
.then((result) => {
|
||||||
prevHeaderListener &&
|
//if there is a handler already, remove it
|
||||||
chrome?.webRequest?.onHeadersReceived?.hasListener(prevHeaderListener)
|
if (
|
||||||
) {
|
prevHeaderListener &&
|
||||||
chrome.webRequest.onHeadersReceived.removeListener(prevHeaderListener);
|
chrome?.webRequest?.onHeadersReceived?.hasListener(prevHeaderListener)
|
||||||
}
|
) {
|
||||||
//if the result was positive, add the headerListener
|
chrome.webRequest.onHeadersReceived.removeListener(prevHeaderListener);
|
||||||
if (result) {
|
|
||||||
const listener: chrome.webRequest.WebResponseHeadersEvent | undefined = chrome?.webRequest?.onHeadersReceived;
|
|
||||||
if (listener) {
|
|
||||||
listener.addListener(
|
|
||||||
headerListener,
|
|
||||||
{ urls: ["<all_urls>"] },
|
|
||||||
["responseHeaders"],
|
|
||||||
);
|
|
||||||
currentHeaderListener = headerListener;
|
|
||||||
}
|
}
|
||||||
}
|
//if the result was positive, add the headerListener
|
||||||
//notify the browser about this change, this operation is expensive
|
if (result) {
|
||||||
chrome?.webRequest?.handlerBehaviorChanged(() => {
|
const listener: chrome.webRequest.WebResponseHeadersEvent | undefined =
|
||||||
if (chrome.runtime.lastError) {
|
chrome?.webRequest?.onHeadersReceived;
|
||||||
logger.error(JSON.stringify(chrome.runtime.lastError));
|
if (listener) {
|
||||||
|
listener.addListener(headerListener, { urls: ["<all_urls>"] }, [
|
||||||
|
"responseHeaders",
|
||||||
|
]);
|
||||||
|
currentHeaderListener = headerListener;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
//notify the browser about this change, this operation is expensive
|
||||||
|
chrome?.webRequest?.handlerBehaviorChanged(() => {
|
||||||
|
if (chrome.runtime.lastError) {
|
||||||
|
logger.error(JSON.stringify(chrome.runtime.lastError));
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const alertIcons = {
|
const alertIcons = {
|
||||||
@ -365,8 +390,8 @@ const alertIcons = {
|
|||||||
"64": "/static/img/taler-alert-64.png",
|
"64": "/static/img/taler-alert-64.png",
|
||||||
"128": "/static/img/taler-alert-128.png",
|
"128": "/static/img/taler-alert-128.png",
|
||||||
"256": "/static/img/taler-alert-256.png",
|
"256": "/static/img/taler-alert-256.png",
|
||||||
"512": "/static/img/taler-alert-512.png"
|
"512": "/static/img/taler-alert-512.png",
|
||||||
}
|
};
|
||||||
const normalIcons = {
|
const normalIcons = {
|
||||||
"16": "/static/img/taler-logo-16.png",
|
"16": "/static/img/taler-logo-16.png",
|
||||||
"19": "/static/img/taler-logo-19.png",
|
"19": "/static/img/taler-logo-19.png",
|
||||||
@ -376,70 +401,99 @@ const normalIcons = {
|
|||||||
"64": "/static/img/taler-logo-64.png",
|
"64": "/static/img/taler-logo-64.png",
|
||||||
"128": "/static/img/taler-logo-128.png",
|
"128": "/static/img/taler-logo-128.png",
|
||||||
"256": "/static/img/taler-logo-256.png",
|
"256": "/static/img/taler-logo-256.png",
|
||||||
"512": "/static/img/taler-logo-512.png"
|
"512": "/static/img/taler-logo-512.png",
|
||||||
}
|
};
|
||||||
function setNormalIcon(): void {
|
function setNormalIcon(): void {
|
||||||
if (extensionIsManifestV3()) {
|
if (extensionIsManifestV3()) {
|
||||||
chrome.action.setIcon({ path: normalIcons })
|
chrome.action.setIcon({ path: normalIcons });
|
||||||
} else {
|
} else {
|
||||||
chrome.browserAction.setIcon({ path: normalIcons })
|
chrome.browserAction.setIcon({ path: normalIcons });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setAlertedIcon(): void {
|
function setAlertedIcon(): void {
|
||||||
if (extensionIsManifestV3()) {
|
if (extensionIsManifestV3()) {
|
||||||
chrome.action.setIcon({ path: alertIcons })
|
chrome.action.setIcon({ path: alertIcons });
|
||||||
} else {
|
} else {
|
||||||
chrome.browserAction.setIcon({ path: alertIcons })
|
chrome.browserAction.setIcon({ path: alertIcons });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface OffscreenCanvasRenderingContext2D
|
||||||
interface OffscreenCanvasRenderingContext2D extends CanvasState, CanvasTransform, CanvasCompositing, CanvasImageSmoothing, CanvasFillStrokeStyles, CanvasShadowStyles, CanvasFilters, CanvasRect, CanvasDrawPath, CanvasUserInterface, CanvasText, CanvasDrawImage, CanvasImageData, CanvasPathDrawingStyles, CanvasTextDrawingStyles, CanvasPath {
|
extends CanvasState,
|
||||||
|
CanvasTransform,
|
||||||
|
CanvasCompositing,
|
||||||
|
CanvasImageSmoothing,
|
||||||
|
CanvasFillStrokeStyles,
|
||||||
|
CanvasShadowStyles,
|
||||||
|
CanvasFilters,
|
||||||
|
CanvasRect,
|
||||||
|
CanvasDrawPath,
|
||||||
|
CanvasUserInterface,
|
||||||
|
CanvasText,
|
||||||
|
CanvasDrawImage,
|
||||||
|
CanvasImageData,
|
||||||
|
CanvasPathDrawingStyles,
|
||||||
|
CanvasTextDrawingStyles,
|
||||||
|
CanvasPath {
|
||||||
readonly canvas: OffscreenCanvas;
|
readonly canvas: OffscreenCanvas;
|
||||||
}
|
}
|
||||||
declare const OffscreenCanvasRenderingContext2D: {
|
declare const OffscreenCanvasRenderingContext2D: {
|
||||||
prototype: OffscreenCanvasRenderingContext2D;
|
prototype: OffscreenCanvasRenderingContext2D;
|
||||||
new(): OffscreenCanvasRenderingContext2D;
|
new (): OffscreenCanvasRenderingContext2D;
|
||||||
}
|
};
|
||||||
|
|
||||||
interface OffscreenCanvas extends EventTarget {
|
interface OffscreenCanvas extends EventTarget {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
getContext(contextId: "2d", contextAttributes?: CanvasRenderingContext2DSettings): OffscreenCanvasRenderingContext2D | null;
|
getContext(
|
||||||
|
contextId: "2d",
|
||||||
|
contextAttributes?: CanvasRenderingContext2DSettings,
|
||||||
|
): OffscreenCanvasRenderingContext2D | null;
|
||||||
}
|
}
|
||||||
declare const OffscreenCanvas: {
|
declare const OffscreenCanvas: {
|
||||||
prototype: OffscreenCanvas;
|
prototype: OffscreenCanvas;
|
||||||
new(width: number, height: number): OffscreenCanvas;
|
new (width: number, height: number): OffscreenCanvas;
|
||||||
}
|
};
|
||||||
|
|
||||||
function createCanvas(size: number): OffscreenCanvas {
|
function createCanvas(size: number): OffscreenCanvas {
|
||||||
if (extensionIsManifestV3()) {
|
if (extensionIsManifestV3()) {
|
||||||
return new OffscreenCanvas(size, size)
|
return new OffscreenCanvas(size, size);
|
||||||
} else {
|
} else {
|
||||||
const c = document.createElement("canvas")
|
const c = document.createElement("canvas");
|
||||||
c.height = size;
|
c.height = size;
|
||||||
c.width = size;
|
c.width = size;
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function createImage(size: number, file: string): Promise<ImageData> {
|
async function createImage(size: number, file: string): Promise<ImageData> {
|
||||||
const r = await fetch(file)
|
const r = await fetch(file);
|
||||||
const b = await r.blob()
|
const b = await r.blob();
|
||||||
const image = await createImageBitmap(b)
|
const image = await createImageBitmap(b);
|
||||||
const canvas = createCanvas(size);
|
const canvas = createCanvas(size);
|
||||||
const canvasContext = canvas.getContext('2d')!;
|
const canvasContext = canvas.getContext("2d")!;
|
||||||
canvasContext.clearRect(0, 0, canvas.width, canvas.height);
|
canvasContext.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
canvasContext.drawImage(image, 0, 0, canvas.width, canvas.height);
|
canvasContext.drawImage(image, 0, 0, canvas.width, canvas.height);
|
||||||
const imageData = canvasContext.getImageData(0, 0, canvas.width, canvas.height);
|
const imageData = canvasContext.getImageData(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
canvas.width,
|
||||||
|
canvas.height,
|
||||||
|
);
|
||||||
return imageData;
|
return imageData;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function registerIconChangeOnTalerContent(): Promise<void> {
|
async function registerIconChangeOnTalerContent(): Promise<void> {
|
||||||
const imgs = await Promise.all(Object.entries(alertIcons).map(([key, value]) => createImage(parseInt(key, 10), value)))
|
const imgs = await Promise.all(
|
||||||
const imageData = imgs.reduce((prev, cur) => ({ ...prev, [cur.width]: cur }), {} as { [size: string]: ImageData })
|
Object.entries(alertIcons).map(([key, value]) =>
|
||||||
|
createImage(parseInt(key, 10), value),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const imageData = imgs.reduce(
|
||||||
|
(prev, cur) => ({ ...prev, [cur.width]: cur }),
|
||||||
|
{} as { [size: string]: ImageData },
|
||||||
|
);
|
||||||
|
|
||||||
if (chrome.declarativeContent) {
|
if (chrome.declarativeContent) {
|
||||||
// using declarative content does not need host permission
|
// using declarative content does not need host permission
|
||||||
@ -447,49 +501,54 @@ async function registerIconChangeOnTalerContent(): Promise<void> {
|
|||||||
const secureTalerUrlLookup = {
|
const secureTalerUrlLookup = {
|
||||||
conditions: [
|
conditions: [
|
||||||
new chrome.declarativeContent.PageStateMatcher({
|
new chrome.declarativeContent.PageStateMatcher({
|
||||||
css: ["a[href^='taler://'"]
|
css: ["a[href^='taler://'"],
|
||||||
})
|
}),
|
||||||
],
|
],
|
||||||
actions: [new chrome.declarativeContent.SetIcon({ imageData })]
|
actions: [new chrome.declarativeContent.SetIcon({ imageData })],
|
||||||
};
|
};
|
||||||
const inSecureTalerUrlLookup = {
|
const inSecureTalerUrlLookup = {
|
||||||
conditions: [
|
conditions: [
|
||||||
new chrome.declarativeContent.PageStateMatcher({
|
new chrome.declarativeContent.PageStateMatcher({
|
||||||
css: ["a[href^='taler+http://'"]
|
css: ["a[href^='taler+http://'"],
|
||||||
})
|
}),
|
||||||
],
|
],
|
||||||
actions: [new chrome.declarativeContent.SetIcon({ imageData })]
|
actions: [new chrome.declarativeContent.SetIcon({ imageData })],
|
||||||
};
|
};
|
||||||
chrome.declarativeContent.onPageChanged.removeRules(undefined, function () {
|
chrome.declarativeContent.onPageChanged.removeRules(undefined, function () {
|
||||||
chrome.declarativeContent.onPageChanged.addRules([secureTalerUrlLookup, inSecureTalerUrlLookup]);
|
chrome.declarativeContent.onPageChanged.addRules([
|
||||||
|
secureTalerUrlLookup,
|
||||||
|
inSecureTalerUrlLookup,
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//this browser doesn't have declarativeContent
|
//this browser doesn't have declarativeContent
|
||||||
//we need host_permission and we will check the content for changing the icon
|
//we need host_permission and we will check the content for changing the icon
|
||||||
chrome.tabs.onUpdated.addListener(async (tabId, info: chrome.tabs.TabChangeInfo) => {
|
chrome.tabs.onUpdated.addListener(
|
||||||
if (tabId < 0) return;
|
async (tabId, info: chrome.tabs.TabChangeInfo) => {
|
||||||
logger.info("tab updated", tabId, info);
|
if (tabId < 0) return;
|
||||||
if (info.status !== "complete") return;
|
logger.info("tab updated", tabId, info);
|
||||||
const uri = await findTalerUriInTab(tabId);
|
if (info.status !== "complete") return;
|
||||||
if (uri) {
|
const uri = await findTalerUriInTab(tabId);
|
||||||
setAlertedIcon()
|
if (uri) {
|
||||||
} else {
|
setAlertedIcon();
|
||||||
setNormalIcon()
|
} else {
|
||||||
}
|
setNormalIcon();
|
||||||
|
}
|
||||||
});
|
},
|
||||||
chrome.tabs.onActivated.addListener(async ({ tabId }: chrome.tabs.TabActiveInfo) => {
|
);
|
||||||
if (tabId < 0) return;
|
chrome.tabs.onActivated.addListener(
|
||||||
const uri = await findTalerUriInTab(tabId);
|
async ({ tabId }: chrome.tabs.TabActiveInfo) => {
|
||||||
if (uri) {
|
if (tabId < 0) return;
|
||||||
setAlertedIcon()
|
const uri = await findTalerUriInTab(tabId);
|
||||||
} else {
|
if (uri) {
|
||||||
setNormalIcon()
|
setAlertedIcon();
|
||||||
}
|
} else {
|
||||||
})
|
setNormalIcon();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerOnInstalled(callback: () => void): void {
|
function registerOnInstalled(callback: () => void): void {
|
||||||
@ -498,27 +557,27 @@ function registerOnInstalled(callback: () => void): void {
|
|||||||
chrome.runtime.onInstalled.addListener(async (details) => {
|
chrome.runtime.onInstalled.addListener(async (details) => {
|
||||||
logger.info(`onInstalled with reason: "${details.reason}"`);
|
logger.info(`onInstalled with reason: "${details.reason}"`);
|
||||||
if (details.reason === chrome.runtime.OnInstalledReason.INSTALL) {
|
if (details.reason === chrome.runtime.OnInstalledReason.INSTALL) {
|
||||||
callback()
|
callback();
|
||||||
}
|
}
|
||||||
registerIconChangeOnTalerContent()
|
registerIconChangeOnTalerContent();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function extensionIsManifestV3(): boolean {
|
function extensionIsManifestV3(): boolean {
|
||||||
return chrome.runtime.getManifest().manifest_version === 3
|
return chrome.runtime.getManifest().manifest_version === 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
function useServiceWorkerAsBackgroundProcess(): boolean {
|
function useServiceWorkerAsBackgroundProcess(): boolean {
|
||||||
return extensionIsManifestV3()
|
return extensionIsManifestV3();
|
||||||
}
|
}
|
||||||
|
|
||||||
function searchForTalerLinks(): string | undefined {
|
function searchForTalerLinks(): string | undefined {
|
||||||
let found;
|
let found;
|
||||||
found = document.querySelector("a[href^='taler://'")
|
found = document.querySelector("a[href^='taler://'");
|
||||||
if (found) return found.toString()
|
if (found) return found.toString();
|
||||||
found = document.querySelector("a[href^='taler+http://'")
|
found = document.querySelector("a[href^='taler+http://'");
|
||||||
if (found) return found.toString()
|
if (found) return found.toString();
|
||||||
return undefined
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getCurrentTab(): Promise<chrome.tabs.Tab> {
|
async function getCurrentTab(): Promise<chrome.tabs.Tab> {
|
||||||
@ -526,12 +585,12 @@ async function getCurrentTab(): Promise<chrome.tabs.Tab> {
|
|||||||
return new Promise<chrome.tabs.Tab>((resolve, reject) => {
|
return new Promise<chrome.tabs.Tab>((resolve, reject) => {
|
||||||
chrome.tabs.query(queryOptions, (tabs) => {
|
chrome.tabs.query(queryOptions, (tabs) => {
|
||||||
if (chrome.runtime.lastError) {
|
if (chrome.runtime.lastError) {
|
||||||
reject(chrome.runtime.lastError)
|
reject(chrome.runtime.lastError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
resolve(tabs[0])
|
resolve(tabs[0]);
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function findTalerUriInTab(tabId: number): Promise<string | undefined> {
|
async function findTalerUriInTab(tabId: number): Promise<string | undefined> {
|
||||||
@ -541,16 +600,17 @@ async function findTalerUriInTab(tabId: number): Promise<string | undefined> {
|
|||||||
const res = await chrome.scripting.executeScript({
|
const res = await chrome.scripting.executeScript({
|
||||||
target: { tabId, allFrames: true },
|
target: { tabId, allFrames: true },
|
||||||
func: searchForTalerLinks,
|
func: searchForTalerLinks,
|
||||||
args: []
|
args: [],
|
||||||
})
|
});
|
||||||
return res[0].result
|
return res[0].result;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
//manifest v2
|
//manifest v2
|
||||||
chrome.tabs.executeScript(tabId,
|
chrome.tabs.executeScript(
|
||||||
|
tabId,
|
||||||
{
|
{
|
||||||
code: `
|
code: `
|
||||||
(() => {
|
(() => {
|
||||||
@ -576,6 +636,5 @@ async function findTalerUriInTab(tabId: number): Promise<string | undefined> {
|
|||||||
async function findTalerUriInActiveTab(): Promise<string | undefined> {
|
async function findTalerUriInActiveTab(): Promise<string | undefined> {
|
||||||
const tab = await getCurrentTab();
|
const tab = await getCurrentTab();
|
||||||
if (!tab || tab.id === undefined) return;
|
if (!tab || tab.id === undefined) return;
|
||||||
return findTalerUriInTab(tab.id)
|
return findTalerUriInTab(tab.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,50 +17,55 @@
|
|||||||
import { CoreApiResponse } from "@gnu-taler/taler-util";
|
import { CoreApiResponse } from "@gnu-taler/taler-util";
|
||||||
import { MessageFromBackend, PlatformAPI } from "./api.js";
|
import { MessageFromBackend, PlatformAPI } from "./api.js";
|
||||||
|
|
||||||
const frames = ["popup", "wallet"]
|
const frames = ["popup", "wallet"];
|
||||||
|
|
||||||
const api: PlatformAPI = ({
|
const api: PlatformAPI = {
|
||||||
isFirefox: () => false,
|
isFirefox: () => false,
|
||||||
keepAlive: (cb: VoidFunction) => cb(),
|
keepAlive: (cb: VoidFunction) => cb(),
|
||||||
findTalerUriInActiveTab: async () => undefined,
|
findTalerUriInActiveTab: async () => undefined,
|
||||||
containsTalerHeaderListener: () => { return true },
|
containsTalerHeaderListener: () => {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
getPermissionsApi: () => ({
|
getPermissionsApi: () => ({
|
||||||
addPermissionsListener: () => undefined, containsHostPermissions: async () => true, removeHostPermissions: async () => false, requestHostPermissions: async () => false
|
addPermissionsListener: () => undefined,
|
||||||
|
containsHostPermissions: async () => true,
|
||||||
|
removeHostPermissions: async () => false,
|
||||||
|
requestHostPermissions: async () => false,
|
||||||
}),
|
}),
|
||||||
getWalletVersion: () => ({
|
getWalletVersion: () => ({
|
||||||
version: 'none'
|
version: "none",
|
||||||
}),
|
}),
|
||||||
notifyWhenAppIsReady: (fn: () => void) => {
|
notifyWhenAppIsReady: (fn: () => void) => {
|
||||||
let total = frames.length
|
let total = frames.length;
|
||||||
function waitAndNotify(): void {
|
function waitAndNotify(): void {
|
||||||
total--
|
total--;
|
||||||
if (total < 1) {
|
if (total < 1) {
|
||||||
console.log('done')
|
console.log("done");
|
||||||
fn()
|
fn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
frames.forEach(f => {
|
frames.forEach((f) => {
|
||||||
const theFrame = window.frames[f as any]
|
const theFrame = window.frames[f as any];
|
||||||
if (theFrame.location.href === 'about:blank') {
|
if (theFrame.location.href === "about:blank") {
|
||||||
waitAndNotify()
|
waitAndNotify();
|
||||||
} else {
|
} else {
|
||||||
theFrame.addEventListener("load", waitAndNotify)
|
theFrame.addEventListener("load", waitAndNotify);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
openWalletPage: (page: string) => {
|
openWalletPage: (page: string) => {
|
||||||
window.frames['wallet' as any].location = `/wallet.html#${page}`
|
window.frames["wallet" as any].location = `/wallet.html#${page}`;
|
||||||
},
|
},
|
||||||
openWalletPageFromPopup: (page: string) => {
|
openWalletPageFromPopup: (page: string) => {
|
||||||
window.parent.frames['wallet' as any].location = `/wallet.html#${page}`
|
window.parent.frames["wallet" as any].location = `/wallet.html#${page}`;
|
||||||
window.location.href = "about:blank"
|
window.location.href = "about:blank";
|
||||||
},
|
},
|
||||||
openWalletURIFromPopup: (page: string) => {
|
openWalletURIFromPopup: (page: string) => {
|
||||||
alert('openWalletURIFromPopup not implemented yet')
|
alert("openWalletURIFromPopup not implemented yet");
|
||||||
},
|
},
|
||||||
redirectTabToWalletPage: (tabId: number, page: string) => {
|
redirectTabToWalletPage: (tabId: number, page: string) => {
|
||||||
alert('redirectTabToWalletPage not implemented yet')
|
alert("redirectTabToWalletPage not implemented yet");
|
||||||
},
|
},
|
||||||
|
|
||||||
registerAllIncomingConnections: () => undefined,
|
registerAllIncomingConnections: () => undefined,
|
||||||
@ -70,91 +75,101 @@ const api: PlatformAPI = ({
|
|||||||
|
|
||||||
useServiceWorkerAsBackgroundProcess: () => false,
|
useServiceWorkerAsBackgroundProcess: () => false,
|
||||||
|
|
||||||
listenToAllChannels: (fn: (m: any, s: any, c: (r: CoreApiResponse) => void) => void) => {
|
listenToAllChannels: (
|
||||||
window.addEventListener("message", (event: MessageEvent<IframeMessageType>) => {
|
fn: (m: any, s: any, c: (r: CoreApiResponse) => void) => void,
|
||||||
if (event.data.type !== 'command') return
|
) => {
|
||||||
const sender = event.data.header.replyMe
|
window.addEventListener(
|
||||||
fn(event.data.body, sender, (resp: CoreApiResponse) => {
|
"message",
|
||||||
if (event.source) {
|
(event: MessageEvent<IframeMessageType>) => {
|
||||||
const msg: IframeMessageResponse = {
|
if (event.data.type !== "command") return;
|
||||||
type: "response",
|
const sender = event.data.header.replyMe;
|
||||||
header: { responseId: sender },
|
fn(event.data.body, sender, (resp: CoreApiResponse) => {
|
||||||
body: resp
|
if (event.source) {
|
||||||
|
const msg: IframeMessageResponse = {
|
||||||
|
type: "response",
|
||||||
|
header: { responseId: sender },
|
||||||
|
body: resp,
|
||||||
|
};
|
||||||
|
window.parent.postMessage(msg);
|
||||||
}
|
}
|
||||||
window.parent.postMessage(msg)
|
});
|
||||||
}
|
},
|
||||||
})
|
);
|
||||||
})
|
|
||||||
},
|
},
|
||||||
sendMessageToAllChannels: (message: MessageFromBackend) => {
|
sendMessageToAllChannels: (message: MessageFromBackend) => {
|
||||||
Array.from(window.frames).forEach(w => {
|
Array.from(window.frames).forEach((w) => {
|
||||||
try {
|
try {
|
||||||
w.postMessage({
|
w.postMessage({
|
||||||
header: {}, body: message
|
header: {},
|
||||||
|
body: message,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
listenToWalletBackground: (onNewMessage: (m: MessageFromBackend) => void) => {
|
listenToWalletBackground: (onNewMessage: (m: MessageFromBackend) => void) => {
|
||||||
function listener(event: MessageEvent<IframeMessageType>): void {
|
function listener(event: MessageEvent<IframeMessageType>): void {
|
||||||
if (event.data.type !== 'notification') return
|
if (event.data.type !== "notification") return;
|
||||||
onNewMessage(event.data.body)
|
onNewMessage(event.data.body);
|
||||||
}
|
}
|
||||||
window.parent.addEventListener("message", listener)
|
window.parent.addEventListener("message", listener);
|
||||||
return () => {
|
return () => {
|
||||||
window.parent.removeEventListener("message", listener)
|
window.parent.removeEventListener("message", listener);
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
sendMessageToWalletBackground: async (operation: string, payload: any) => {
|
sendMessageToWalletBackground: async (operation: string, payload: any) => {
|
||||||
const replyMe = `reply-${Math.floor(Math.random() * 100000)}`
|
const replyMe = `reply-${Math.floor(Math.random() * 100000)}`;
|
||||||
const message: IframeMessageCommand = {
|
const message: IframeMessageCommand = {
|
||||||
type: 'command',
|
type: "command",
|
||||||
header: { replyMe },
|
header: { replyMe },
|
||||||
body: { operation, payload, id: "(none)" }
|
body: { operation, payload, id: "(none)" },
|
||||||
}
|
};
|
||||||
window.parent.postMessage(message)
|
window.parent.postMessage(message);
|
||||||
|
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
function listener(event: MessageEvent<IframeMessageType>): void {
|
function listener(event: MessageEvent<IframeMessageType>): void {
|
||||||
if (event.data.type !== "response" || event.data.header.responseId !== replyMe) {
|
if (
|
||||||
return
|
event.data.type !== "response" ||
|
||||||
|
event.data.header.responseId !== replyMe
|
||||||
|
) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
res(event.data.body)
|
res(event.data.body);
|
||||||
window.parent.removeEventListener("message", listener)
|
window.parent.removeEventListener("message", listener);
|
||||||
}
|
}
|
||||||
window.parent.addEventListener("message", listener, {
|
window.parent.addEventListener("message", listener, {});
|
||||||
|
});
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
|
|
||||||
type IframeMessageType = IframeMessageNotification | IframeMessageResponse | IframeMessageCommand;
|
type IframeMessageType =
|
||||||
|
| IframeMessageNotification
|
||||||
|
| IframeMessageResponse
|
||||||
|
| IframeMessageCommand;
|
||||||
interface IframeMessageNotification {
|
interface IframeMessageNotification {
|
||||||
type: "notification";
|
type: "notification";
|
||||||
header: Record<string, never>,
|
header: Record<string, never>;
|
||||||
body: MessageFromBackend
|
body: MessageFromBackend;
|
||||||
}
|
}
|
||||||
interface IframeMessageResponse {
|
interface IframeMessageResponse {
|
||||||
type: "response";
|
type: "response";
|
||||||
header: {
|
header: {
|
||||||
responseId: string;
|
responseId: string;
|
||||||
},
|
};
|
||||||
body: CoreApiResponse
|
body: CoreApiResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IframeMessageCommand {
|
interface IframeMessageCommand {
|
||||||
type: "command";
|
type: "command";
|
||||||
header: {
|
header: {
|
||||||
replyMe: string;
|
replyMe: string;
|
||||||
},
|
};
|
||||||
body: {
|
body: {
|
||||||
operation: any, id: string, payload: any
|
operation: any;
|
||||||
}
|
id: string;
|
||||||
|
payload: any;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default api;
|
export default api;
|
||||||
|
|
||||||
|
@ -15,7 +15,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { CrossBrowserPermissionsApi, Permissions, PlatformAPI } from "./api.js";
|
import { CrossBrowserPermissionsApi, Permissions, PlatformAPI } from "./api.js";
|
||||||
import chromePlatform, { containsHostPermissions as chromeContains, removeHostPermissions as chromeRemove, requestHostPermissions as chromeRequest } from "./chrome.js";
|
import chromePlatform, {
|
||||||
|
containsHostPermissions as chromeContains,
|
||||||
|
removeHostPermissions as chromeRemove,
|
||||||
|
requestHostPermissions as chromeRequest,
|
||||||
|
} from "./chrome.js";
|
||||||
|
|
||||||
const api: PlatformAPI = {
|
const api: PlatformAPI = {
|
||||||
...chromePlatform,
|
...chromePlatform,
|
||||||
@ -23,18 +27,17 @@ const api: PlatformAPI = {
|
|||||||
getPermissionsApi,
|
getPermissionsApi,
|
||||||
notifyWhenAppIsReady,
|
notifyWhenAppIsReady,
|
||||||
redirectTabToWalletPage,
|
redirectTabToWalletPage,
|
||||||
useServiceWorkerAsBackgroundProcess
|
useServiceWorkerAsBackgroundProcess,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default api;
|
export default api;
|
||||||
|
|
||||||
function isFirefox(): boolean {
|
function isFirefox(): boolean {
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function addPermissionsListener(callback: (p: Permissions) => void): void {
|
function addPermissionsListener(callback: (p: Permissions) => void): void {
|
||||||
console.log("addPermissionListener is not supported for Firefox")
|
console.log("addPermissionListener is not supported for Firefox");
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPermissionsApi(): CrossBrowserPermissionsApi {
|
function getPermissionsApi(): CrossBrowserPermissionsApi {
|
||||||
@ -42,33 +45,28 @@ function getPermissionsApi(): CrossBrowserPermissionsApi {
|
|||||||
addPermissionsListener,
|
addPermissionsListener,
|
||||||
containsHostPermissions: chromeContains,
|
containsHostPermissions: chromeContains,
|
||||||
requestHostPermissions: chromeRequest,
|
requestHostPermissions: chromeRequest,
|
||||||
removeHostPermissions: chromeRemove
|
removeHostPermissions: chromeRemove,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param callback function to be called
|
* @param callback function to be called
|
||||||
*/
|
*/
|
||||||
function notifyWhenAppIsReady(callback: () => void): void {
|
function notifyWhenAppIsReady(callback: () => void): void {
|
||||||
if (chrome.runtime && chrome.runtime.getManifest().manifest_version === 3) {
|
if (chrome.runtime && chrome.runtime.getManifest().manifest_version === 3) {
|
||||||
callback()
|
callback();
|
||||||
} else {
|
} else {
|
||||||
window.addEventListener("load", callback);
|
window.addEventListener("load", callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function redirectTabToWalletPage(tabId: number, page: string): void {
|
||||||
function redirectTabToWalletPage(
|
|
||||||
tabId: number,
|
|
||||||
page: string,
|
|
||||||
): void {
|
|
||||||
const url = chrome.runtime.getURL(`/static/wallet.html#${page}`);
|
const url = chrome.runtime.getURL(`/static/wallet.html#${page}`);
|
||||||
console.log("redirecting tabId: ", tabId, " to: ", url);
|
console.log("redirecting tabId: ", tabId, " to: ", url);
|
||||||
chrome.tabs.update(tabId, { url, loadReplace: true } as any);
|
chrome.tabs.update(tabId, { url, loadReplace: true } as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function useServiceWorkerAsBackgroundProcess(): false {
|
function useServiceWorkerAsBackgroundProcess(): false {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ export class ServiceWorkerHttpLib implements HttpRequestLibrary {
|
|||||||
): Promise<HttpResponse> {
|
): Promise<HttpResponse> {
|
||||||
return this.fetch(url, {
|
return this.fetch(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {"Content-Type": "application/json"},
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
...opt,
|
...opt,
|
||||||
});
|
});
|
||||||
|
@ -47,9 +47,9 @@ function testThisStory(st: any): any {
|
|||||||
describe("render every storybook example", () => {
|
describe("render every storybook example", () => {
|
||||||
[popup, wallet, cta, mui, components].forEach(function testAll(st: any) {
|
[popup, wallet, cta, mui, components].forEach(function testAll(st: any) {
|
||||||
if (Array.isArray(st.default)) {
|
if (Array.isArray(st.default)) {
|
||||||
st.default.forEach(testAll)
|
st.default.forEach(testAll);
|
||||||
} else {
|
} else {
|
||||||
testThisStory(st)
|
testThisStory(st);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -14,15 +14,23 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ComponentChildren, Fragment, FunctionalComponent, h as create, options, render as renderIntoDom, VNode } from "preact";
|
import {
|
||||||
|
ComponentChildren,
|
||||||
|
Fragment,
|
||||||
|
FunctionalComponent,
|
||||||
|
h as create,
|
||||||
|
options,
|
||||||
|
render as renderIntoDom,
|
||||||
|
VNode,
|
||||||
|
} from "preact";
|
||||||
import { render as renderToString } from "preact-render-to-string";
|
import { render as renderToString } from "preact-render-to-string";
|
||||||
|
|
||||||
// When doing tests we want the requestAnimationFrame to be as fast as possible.
|
// When doing tests we want the requestAnimationFrame to be as fast as possible.
|
||||||
// without this option the RAF will timeout after 100ms making the tests slower
|
// without this option the RAF will timeout after 100ms making the tests slower
|
||||||
options.requestAnimationFrame = (fn: () => void) => {
|
options.requestAnimationFrame = (fn: () => void) => {
|
||||||
// console.log("RAF called")
|
// console.log("RAF called")
|
||||||
return fn()
|
return fn();
|
||||||
}
|
};
|
||||||
|
|
||||||
export function createExample<Props>(
|
export function createExample<Props>(
|
||||||
Component: FunctionalComponent<Props>,
|
Component: FunctionalComponent<Props>,
|
||||||
@ -30,8 +38,8 @@ export function createExample<Props>(
|
|||||||
): ComponentChildren {
|
): ComponentChildren {
|
||||||
//FIXME: props are evaluated on build time
|
//FIXME: props are evaluated on build time
|
||||||
// in some cases we want to evaluated the props on render time so we can get some relative timestamp
|
// in some cases we want to evaluated the props on render time so we can get some relative timestamp
|
||||||
// check how we can build evaluatedProps in render time
|
// check how we can build evaluatedProps in render time
|
||||||
const evaluatedProps = typeof props === "function" ? props() : props
|
const evaluatedProps = typeof props === "function" ? props() : props;
|
||||||
const Render = (args: any): VNode => create(Component, args);
|
const Render = (args: any): VNode => create(Component, args);
|
||||||
Render.args = evaluatedProps;
|
Render.args = evaluatedProps;
|
||||||
return Render;
|
return Render;
|
||||||
@ -43,14 +51,22 @@ export function createExampleWithCustomContext<Props, ContextProps>(
|
|||||||
ContextProvider: FunctionalComponent<ContextProps>,
|
ContextProvider: FunctionalComponent<ContextProps>,
|
||||||
contextProps: Partial<ContextProps>,
|
contextProps: Partial<ContextProps>,
|
||||||
): ComponentChildren {
|
): ComponentChildren {
|
||||||
const evaluatedProps = typeof props === "function" ? props() : props
|
const evaluatedProps = typeof props === "function" ? props() : props;
|
||||||
const Render = (args: any): VNode => create(Component, args);
|
const Render = (args: any): VNode => create(Component, args);
|
||||||
const WithContext = (args: any): VNode => create(ContextProvider, { ...contextProps, children: [Render(args)] } as any);
|
const WithContext = (args: any): VNode =>
|
||||||
WithContext.args = evaluatedProps
|
create(ContextProvider, {
|
||||||
return WithContext
|
...contextProps,
|
||||||
|
children: [Render(args)],
|
||||||
|
} as any);
|
||||||
|
WithContext.args = evaluatedProps;
|
||||||
|
return WithContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NullLink({ children }: { children?: ComponentChildren }): VNode {
|
export function NullLink({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children?: ComponentChildren;
|
||||||
|
}): VNode {
|
||||||
return create("a", { children, href: "javascript:void(0);" });
|
return create("a", { children, href: "javascript:void(0);" });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,53 +90,59 @@ interface Mounted<T> {
|
|||||||
waitNextUpdate: (s?: string) => Promise<void>;
|
waitNextUpdate: (s?: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isNode = typeof window === "undefined"
|
const isNode = typeof window === "undefined";
|
||||||
|
|
||||||
export function mountHook<T>(callback: () => T, Context?: ({ children }: { children: any }) => VNode): Mounted<T> {
|
export function mountHook<T>(
|
||||||
|
callback: () => T,
|
||||||
|
Context?: ({ children }: { children: any }) => VNode,
|
||||||
|
): Mounted<T> {
|
||||||
// const result: { current: T | null } = {
|
// const result: { current: T | null } = {
|
||||||
// current: null
|
// current: null
|
||||||
// }
|
// }
|
||||||
let lastResult: T | Error | null = null;
|
let lastResult: T | Error | null = null;
|
||||||
|
|
||||||
const listener: Array<() => void> = []
|
const listener: Array<() => void> = [];
|
||||||
|
|
||||||
// component that's going to hold the hook
|
// component that's going to hold the hook
|
||||||
function Component(): VNode {
|
function Component(): VNode {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
lastResult = callback()
|
lastResult = callback();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof Error) {
|
if (e instanceof Error) {
|
||||||
lastResult = e
|
lastResult = e;
|
||||||
} else {
|
} else {
|
||||||
lastResult = new Error(`mounting the hook throw an exception: ${e}`)
|
lastResult = new Error(`mounting the hook throw an exception: ${e}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// notify to everyone waiting for an update and clean the queue
|
// notify to everyone waiting for an update and clean the queue
|
||||||
listener.splice(0, listener.length).forEach(cb => cb())
|
listener.splice(0, listener.length).forEach((cb) => cb());
|
||||||
return create(Fragment, {})
|
return create(Fragment, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// create the vdom with context if required
|
// create the vdom with context if required
|
||||||
const vdom = !Context ? create(Component, {}) : create(Context, { children: [create(Component, {})] },);
|
const vdom = !Context
|
||||||
|
? create(Component, {})
|
||||||
|
: create(Context, { children: [create(Component, {})] });
|
||||||
|
|
||||||
// waiter callback
|
// waiter callback
|
||||||
async function waitNextUpdate(_label = ""): Promise<void> {
|
async function waitNextUpdate(_label = ""): Promise<void> {
|
||||||
if (_label) _label = `. label: "${_label}"`
|
if (_label) _label = `. label: "${_label}"`;
|
||||||
await new Promise((res, rej) => {
|
await new Promise((res, rej) => {
|
||||||
const tid = setTimeout(() => {
|
const tid = setTimeout(() => {
|
||||||
rej(Error(`waiting for an update but the hook didn't make one${_label}`))
|
rej(
|
||||||
}, 100)
|
Error(`waiting for an update but the hook didn't make one${_label}`),
|
||||||
|
);
|
||||||
|
}, 100);
|
||||||
|
|
||||||
listener.push(() => {
|
listener.push(() => {
|
||||||
clearTimeout(tid)
|
clearTimeout(tid);
|
||||||
res(undefined)
|
res(undefined);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const customElement = {} as Element
|
const customElement = {} as Element;
|
||||||
const parentElement = isNode ? customElement : document.createElement("div");
|
const parentElement = isNode ? customElement : document.createElement("div");
|
||||||
if (!isNode) {
|
if (!isNode) {
|
||||||
document.body.appendChild(parentElement);
|
document.body.appendChild(parentElement);
|
||||||
@ -136,38 +158,44 @@ export function mountHook<T>(callback: () => T, Context?: ({ children }: { child
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getLastResult(): T | Error | null {
|
function getLastResult(): T | Error | null {
|
||||||
const copy = lastResult
|
const copy = lastResult;
|
||||||
lastResult = null
|
lastResult = null;
|
||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLastResultOrThrow(): T {
|
function getLastResultOrThrow(): T {
|
||||||
const r = getLastResult()
|
const r = getLastResult();
|
||||||
if (r instanceof Error) throw r;
|
if (r instanceof Error) throw r;
|
||||||
if (!r) throw Error('there was no last result')
|
if (!r) throw Error("there was no last result");
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function assertNoPendingUpdate(): Promise<void> {
|
async function assertNoPendingUpdate(): Promise<void> {
|
||||||
await new Promise((res, rej) => {
|
await new Promise((res, rej) => {
|
||||||
const tid = setTimeout(() => {
|
const tid = setTimeout(() => {
|
||||||
res(undefined)
|
res(undefined);
|
||||||
}, 10)
|
}, 10);
|
||||||
|
|
||||||
listener.push(() => {
|
listener.push(() => {
|
||||||
clearTimeout(tid)
|
clearTimeout(tid);
|
||||||
rej(Error(`Expecting no pending result but the hook got updated.
|
rej(
|
||||||
|
Error(`Expecting no pending result but the hook got updated.
|
||||||
If the update was not intended you need to check the hook dependencies
|
If the update was not intended you need to check the hook dependencies
|
||||||
(or dependencies of the internal state) but otherwise make
|
(or dependencies of the internal state) but otherwise make
|
||||||
sure to consume the result before ending the test.`))
|
sure to consume the result before ending the test.`),
|
||||||
})
|
);
|
||||||
})
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const r = getLastResult()
|
const r = getLastResult();
|
||||||
if (r) throw Error(`There are still pending results.
|
if (r)
|
||||||
|
throw Error(`There are still pending results.
|
||||||
This may happen because the hook did a new update but the test didn't consume the result using getLastResult`);
|
This may happen because the hook did a new update but the test didn't consume the result using getLastResult`);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
unmount, getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate
|
unmount,
|
||||||
}
|
getLastResultOrThrow,
|
||||||
|
waitNextUpdate,
|
||||||
|
assertNoPendingUpdate,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -14,8 +14,11 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AmountJson, Amounts, GetExchangeTosResult } from "@gnu-taler/taler-util";
|
import {
|
||||||
|
AmountJson,
|
||||||
|
Amounts,
|
||||||
|
GetExchangeTosResult,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
|
|
||||||
function getJsonIfOk(r: Response): Promise<any> {
|
function getJsonIfOk(r: Response): Promise<any> {
|
||||||
if (r.ok) {
|
if (r.ok) {
|
||||||
@ -27,15 +30,13 @@ function getJsonIfOk(r: Response): Promise<any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Try another server: (${r.status}) ${r.statusText || "internal server error"
|
`Try another server: (${r.status}) ${
|
||||||
|
r.statusText || "internal server error"
|
||||||
}`,
|
}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function queryToSlashConfig<T>(
|
export async function queryToSlashConfig<T>(url: string): Promise<T> {
|
||||||
url: string,
|
|
||||||
): Promise<T> {
|
|
||||||
return fetch(new URL("config", url).href)
|
return fetch(new URL("config", url).href)
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
throw new Error(`Network error`);
|
throw new Error(`Network error`);
|
||||||
@ -46,25 +47,27 @@ export async function queryToSlashConfig<T>(
|
|||||||
function timeout<T>(ms: number, promise: Promise<T>): Promise<T> {
|
function timeout<T>(ms: number, promise: Promise<T>): Promise<T> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
reject(new Error(`Timeout: the query took longer than ${Math.floor(ms / 1000)} secs`))
|
reject(
|
||||||
}, ms)
|
new Error(
|
||||||
|
`Timeout: the query took longer than ${Math.floor(ms / 1000)} secs`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}, ms);
|
||||||
|
|
||||||
promise
|
promise
|
||||||
.then(value => {
|
.then((value) => {
|
||||||
clearTimeout(timer)
|
clearTimeout(timer);
|
||||||
resolve(value)
|
resolve(value);
|
||||||
})
|
})
|
||||||
.catch(reason => {
|
.catch((reason) => {
|
||||||
clearTimeout(timer)
|
clearTimeout(timer);
|
||||||
reject(reason)
|
reject(reason);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function queryToSlashKeys<T>(
|
export async function queryToSlashKeys<T>(url: string): Promise<T> {
|
||||||
url: string,
|
const endpoint = new URL("keys", url);
|
||||||
): Promise<T> {
|
|
||||||
const endpoint = new URL("keys", url)
|
|
||||||
endpoint.searchParams.set("cacheBreaker", new Date().getTime() + "");
|
endpoint.searchParams.set("cacheBreaker", new Date().getTime() + "");
|
||||||
|
|
||||||
const query = fetch(endpoint.href)
|
const query = fetch(endpoint.href)
|
||||||
@ -73,29 +76,37 @@ export async function queryToSlashKeys<T>(
|
|||||||
})
|
})
|
||||||
.then(getJsonIfOk);
|
.then(getJsonIfOk);
|
||||||
|
|
||||||
return timeout(3000, query)
|
return timeout(3000, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildTermsOfServiceState(tos: GetExchangeTosResult): TermsState {
|
export function buildTermsOfServiceState(
|
||||||
|
tos: GetExchangeTosResult,
|
||||||
|
): TermsState {
|
||||||
const content: TermsDocument | undefined = parseTermsOfServiceContent(
|
const content: TermsDocument | undefined = parseTermsOfServiceContent(
|
||||||
tos.contentType,
|
tos.contentType,
|
||||||
tos.content,
|
tos.content,
|
||||||
);
|
);
|
||||||
|
|
||||||
const status: TermsStatus = buildTermsOfServiceStatus(tos.content, tos.acceptedEtag, tos.currentEtag);
|
const status: TermsStatus = buildTermsOfServiceStatus(
|
||||||
|
tos.content,
|
||||||
return { content, status, version: tos.currentEtag }
|
tos.acceptedEtag,
|
||||||
|
tos.currentEtag,
|
||||||
|
);
|
||||||
|
|
||||||
|
return { content, status, version: tos.currentEtag };
|
||||||
}
|
}
|
||||||
export function buildTermsOfServiceStatus(content: string | undefined, acceptedVersion: string | undefined, currentVersion: string | undefined): TermsStatus {
|
export function buildTermsOfServiceStatus(
|
||||||
|
content: string | undefined,
|
||||||
|
acceptedVersion: string | undefined,
|
||||||
|
currentVersion: string | undefined,
|
||||||
|
): TermsStatus {
|
||||||
return !content
|
return !content
|
||||||
? "notfound"
|
? "notfound"
|
||||||
: !acceptedVersion
|
: !acceptedVersion
|
||||||
? "new"
|
? "new"
|
||||||
: acceptedVersion !== currentVersion
|
: acceptedVersion !== currentVersion
|
||||||
? "changed"
|
? "changed"
|
||||||
: "accepted";
|
: "accepted";
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTermsOfServiceContent(
|
function parseTermsOfServiceContent(
|
||||||
|
@ -25,15 +25,13 @@ import { SelectFieldHandler, TextFieldHandler } from "../mui/handlers.js";
|
|||||||
import { mountHook } from "../test-utils.js";
|
import { mountHook } from "../test-utils.js";
|
||||||
import { useComponentState } from "./CreateManualWithdraw.js";
|
import { useComponentState } from "./CreateManualWithdraw.js";
|
||||||
|
|
||||||
|
|
||||||
const exchangeListWithARSandUSD = {
|
const exchangeListWithARSandUSD = {
|
||||||
"url1": "USD",
|
url1: "USD",
|
||||||
"url2": "ARS",
|
url2: "ARS",
|
||||||
"url3": "ARS",
|
url3: "ARS",
|
||||||
};
|
};
|
||||||
|
|
||||||
const exchangeListEmpty = {
|
const exchangeListEmpty = {};
|
||||||
};
|
|
||||||
|
|
||||||
describe("CreateManualWithdraw states", () => {
|
describe("CreateManualWithdraw states", () => {
|
||||||
it("should set noExchangeFound when exchange list is empty", () => {
|
it("should set noExchangeFound when exchange list is empty", () => {
|
||||||
@ -41,9 +39,9 @@ describe("CreateManualWithdraw states", () => {
|
|||||||
useComponentState(exchangeListEmpty, undefined, undefined),
|
useComponentState(exchangeListEmpty, undefined, undefined),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { noExchangeFound } = getLastResultOrThrow()
|
const { noExchangeFound } = getLastResultOrThrow();
|
||||||
|
|
||||||
expect(noExchangeFound).equal(true)
|
expect(noExchangeFound).equal(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should set noExchangeFound when exchange list doesn't include selected currency", () => {
|
it("should set noExchangeFound when exchange list doesn't include selected currency", () => {
|
||||||
@ -51,20 +49,19 @@ describe("CreateManualWithdraw states", () => {
|
|||||||
useComponentState(exchangeListWithARSandUSD, undefined, "COL"),
|
useComponentState(exchangeListWithARSandUSD, undefined, "COL"),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { noExchangeFound } = getLastResultOrThrow()
|
const { noExchangeFound } = getLastResultOrThrow();
|
||||||
|
|
||||||
expect(noExchangeFound).equal(true)
|
expect(noExchangeFound).equal(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it("should select the first exchange from the list", () => {
|
it("should select the first exchange from the list", () => {
|
||||||
const { getLastResultOrThrow } = mountHook(() =>
|
const { getLastResultOrThrow } = mountHook(() =>
|
||||||
useComponentState(exchangeListWithARSandUSD, undefined, undefined),
|
useComponentState(exchangeListWithARSandUSD, undefined, undefined),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { exchange } = getLastResultOrThrow()
|
const { exchange } = getLastResultOrThrow();
|
||||||
|
|
||||||
expect(exchange.value).equal("url1")
|
expect(exchange.value).equal("url1");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should select the first exchange with the selected currency", () => {
|
it("should select the first exchange with the selected currency", () => {
|
||||||
@ -72,9 +69,9 @@ describe("CreateManualWithdraw states", () => {
|
|||||||
useComponentState(exchangeListWithARSandUSD, undefined, "ARS"),
|
useComponentState(exchangeListWithARSandUSD, undefined, "ARS"),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { exchange } = getLastResultOrThrow()
|
const { exchange } = getLastResultOrThrow();
|
||||||
|
|
||||||
expect(exchange.value).equal("url2")
|
expect(exchange.value).equal("url2");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should change the exchange when currency change", async () => {
|
it("should change the exchange when currency change", async () => {
|
||||||
@ -82,22 +79,20 @@ describe("CreateManualWithdraw states", () => {
|
|||||||
useComponentState(exchangeListWithARSandUSD, undefined, "ARS"),
|
useComponentState(exchangeListWithARSandUSD, undefined, "ARS"),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
const { exchange, currency } = getLastResultOrThrow()
|
const { exchange, currency } = getLastResultOrThrow();
|
||||||
|
|
||||||
expect(exchange.value).equal("url2")
|
expect(exchange.value).equal("url2");
|
||||||
if (currency.onChange === undefined) expect.fail();
|
if (currency.onChange === undefined) expect.fail();
|
||||||
currency.onChange("USD")
|
currency.onChange("USD");
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const { exchange } = getLastResultOrThrow()
|
const { exchange } = getLastResultOrThrow();
|
||||||
expect(exchange.value).equal("url1")
|
expect(exchange.value).equal("url1");
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should change the currency when exchange change", async () => {
|
it("should change the currency when exchange change", async () => {
|
||||||
@ -106,22 +101,22 @@ describe("CreateManualWithdraw states", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { exchange, currency } = getLastResultOrThrow()
|
const { exchange, currency } = getLastResultOrThrow();
|
||||||
|
|
||||||
expect(exchange.value).equal("url2")
|
expect(exchange.value).equal("url2");
|
||||||
expect(currency.value).equal("ARS")
|
expect(currency.value).equal("ARS");
|
||||||
|
|
||||||
if (exchange.onChange === undefined) expect.fail();
|
if (exchange.onChange === undefined) expect.fail();
|
||||||
exchange.onChange("url1")
|
exchange.onChange("url1");
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const { exchange, currency } = getLastResultOrThrow()
|
const { exchange, currency } = getLastResultOrThrow();
|
||||||
|
|
||||||
expect(exchange.value).equal("url1")
|
expect(exchange.value).equal("url1");
|
||||||
expect(currency.value).equal("USD")
|
expect(currency.value).equal("USD");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -131,21 +126,23 @@ describe("CreateManualWithdraw states", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { amount, parsedAmount } = getLastResultOrThrow()
|
const { amount, parsedAmount } = getLastResultOrThrow();
|
||||||
|
|
||||||
expect(parsedAmount).equal(undefined)
|
expect(parsedAmount).equal(undefined);
|
||||||
|
|
||||||
amount.onInput("12")
|
amount.onInput("12");
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const { parsedAmount } = getLastResultOrThrow()
|
const { parsedAmount } = getLastResultOrThrow();
|
||||||
|
|
||||||
expect(parsedAmount).deep.equals({
|
expect(parsedAmount).deep.equals({
|
||||||
value: 12, fraction: 0, currency: "ARS"
|
value: 12,
|
||||||
})
|
fraction: 0,
|
||||||
|
currency: "ARS",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -154,67 +151,79 @@ describe("CreateManualWithdraw states", () => {
|
|||||||
useComponentState(exchangeListWithARSandUSD, undefined, "ARS"),
|
useComponentState(exchangeListWithARSandUSD, undefined, "ARS"),
|
||||||
);
|
);
|
||||||
|
|
||||||
await defaultTestForInputText(waitNextUpdate, () => getLastResultOrThrow().amount)
|
await defaultTestForInputText(
|
||||||
})
|
waitNextUpdate,
|
||||||
|
() => getLastResultOrThrow().amount,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("should have an exchange selector ", async () => {
|
it("should have an exchange selector ", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate } = mountHook(() =>
|
const { getLastResultOrThrow, waitNextUpdate } = mountHook(() =>
|
||||||
useComponentState(exchangeListWithARSandUSD, undefined, "ARS"),
|
useComponentState(exchangeListWithARSandUSD, undefined, "ARS"),
|
||||||
);
|
);
|
||||||
|
|
||||||
await defaultTestForInputSelect(waitNextUpdate, () => getLastResultOrThrow().exchange)
|
await defaultTestForInputSelect(
|
||||||
})
|
waitNextUpdate,
|
||||||
|
() => getLastResultOrThrow().exchange,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("should have a currency selector ", async () => {
|
it("should have a currency selector ", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate } = mountHook(() =>
|
const { getLastResultOrThrow, waitNextUpdate } = mountHook(() =>
|
||||||
useComponentState(exchangeListWithARSandUSD, undefined, "ARS"),
|
useComponentState(exchangeListWithARSandUSD, undefined, "ARS"),
|
||||||
);
|
);
|
||||||
|
|
||||||
await defaultTestForInputSelect(waitNextUpdate, () => getLastResultOrThrow().currency)
|
await defaultTestForInputSelect(
|
||||||
})
|
waitNextUpdate,
|
||||||
|
() => getLastResultOrThrow().currency,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function defaultTestForInputText(
|
||||||
async function defaultTestForInputText(awaiter: () => Promise<void>, getField: () => TextFieldHandler): Promise<void> {
|
awaiter: () => Promise<void>,
|
||||||
let nextValue = ''
|
getField: () => TextFieldHandler,
|
||||||
|
): Promise<void> {
|
||||||
|
let nextValue = "";
|
||||||
{
|
{
|
||||||
const field = getField()
|
const field = getField();
|
||||||
const initialValue = field.value;
|
const initialValue = field.value;
|
||||||
nextValue = `${initialValue} something else`
|
nextValue = `${initialValue} something else`;
|
||||||
field.onInput(nextValue)
|
field.onInput(nextValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
await awaiter()
|
await awaiter();
|
||||||
|
|
||||||
{
|
{
|
||||||
const field = getField()
|
const field = getField();
|
||||||
expect(field.value).equal(nextValue)
|
expect(field.value).equal(nextValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function defaultTestForInputSelect(
|
||||||
async function defaultTestForInputSelect(awaiter: () => Promise<void>, getField: () => SelectFieldHandler): Promise<void> {
|
awaiter: () => Promise<void>,
|
||||||
let nextValue = ''
|
getField: () => SelectFieldHandler,
|
||||||
|
): Promise<void> {
|
||||||
|
let nextValue = "";
|
||||||
|
|
||||||
{
|
{
|
||||||
const field = getField();
|
const field = getField();
|
||||||
const initialValue = field.value;
|
const initialValue = field.value;
|
||||||
const keys = Object.keys(field.list)
|
const keys = Object.keys(field.list);
|
||||||
const nextIdx = keys.indexOf(initialValue) + 1
|
const nextIdx = keys.indexOf(initialValue) + 1;
|
||||||
if (keys.length < nextIdx) {
|
if (keys.length < nextIdx) {
|
||||||
throw new Error('no enough values')
|
throw new Error("no enough values");
|
||||||
}
|
}
|
||||||
nextValue = keys[nextIdx]
|
nextValue = keys[nextIdx];
|
||||||
if (field.onChange === undefined) expect.fail();
|
if (field.onChange === undefined) expect.fail();
|
||||||
field.onChange(nextValue)
|
field.onChange(nextValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
await awaiter()
|
await awaiter();
|
||||||
|
|
||||||
{
|
{
|
||||||
const field = getField();
|
const field = getField();
|
||||||
|
|
||||||
expect(field.value).equal(nextValue)
|
expect(field.value).equal(nextValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,392 +19,412 @@
|
|||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Amounts, Balance, BalancesResponse, parsePaytoUri } from "@gnu-taler/taler-util";
|
import {
|
||||||
|
Amounts,
|
||||||
|
Balance,
|
||||||
|
BalancesResponse,
|
||||||
|
parsePaytoUri,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
import { DepositGroupFees } from "@gnu-taler/taler-wallet-core/src/operations/deposits";
|
import { DepositGroupFees } from "@gnu-taler/taler-wallet-core/src/operations/deposits";
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
import { mountHook } from "../test-utils.js";
|
import { mountHook } from "../test-utils.js";
|
||||||
import { useComponentState } from "./DepositPage.js";
|
import { useComponentState } from "./DepositPage.js";
|
||||||
import * as wxApi from "../wxApi.js";
|
import * as wxApi from "../wxApi.js";
|
||||||
|
|
||||||
|
const currency = "EUR";
|
||||||
const currency = "EUR"
|
|
||||||
const withoutFee = async (): Promise<DepositGroupFees> => ({
|
const withoutFee = async (): Promise<DepositGroupFees> => ({
|
||||||
coin: Amounts.parseOrThrow(`${currency}:0`),
|
coin: Amounts.parseOrThrow(`${currency}:0`),
|
||||||
wire: Amounts.parseOrThrow(`${currency}:0`),
|
wire: Amounts.parseOrThrow(`${currency}:0`),
|
||||||
refresh: Amounts.parseOrThrow(`${currency}:0`)
|
refresh: Amounts.parseOrThrow(`${currency}:0`),
|
||||||
})
|
});
|
||||||
|
|
||||||
const withSomeFee = async (): Promise<DepositGroupFees> => ({
|
const withSomeFee = async (): Promise<DepositGroupFees> => ({
|
||||||
coin: Amounts.parseOrThrow(`${currency}:1`),
|
coin: Amounts.parseOrThrow(`${currency}:1`),
|
||||||
wire: Amounts.parseOrThrow(`${currency}:1`),
|
wire: Amounts.parseOrThrow(`${currency}:1`),
|
||||||
refresh: Amounts.parseOrThrow(`${currency}:1`)
|
refresh: Amounts.parseOrThrow(`${currency}:1`),
|
||||||
})
|
});
|
||||||
|
|
||||||
const freeJustForIBAN = async (account: string): Promise<DepositGroupFees> => /IBAN/i.test(account) ? withoutFee() : withSomeFee()
|
const freeJustForIBAN = async (account: string): Promise<DepositGroupFees> =>
|
||||||
|
/IBAN/i.test(account) ? withoutFee() : withSomeFee();
|
||||||
|
|
||||||
const someBalance = [{
|
const someBalance = [
|
||||||
available: 'EUR:10'
|
{
|
||||||
} as Balance]
|
available: "EUR:10",
|
||||||
|
} as Balance,
|
||||||
|
];
|
||||||
|
|
||||||
const nullFunction: any = () => null;
|
const nullFunction: any = () => null;
|
||||||
type VoidFunction = () => void;
|
type VoidFunction = () => void;
|
||||||
|
|
||||||
describe("DepositPage states", () => {
|
describe("DepositPage states", () => {
|
||||||
it("should have status 'no-balance' when balance is empty", async () => {
|
it("should have status 'no-balance' when balance is empty", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
useComponentState(currency, nullFunction, nullFunction, {
|
mountHook(() =>
|
||||||
getBalance: async () => ({
|
useComponentState(currency, nullFunction, nullFunction, {
|
||||||
balances: [{ available: `${currency}:0`, }]
|
getBalance: async () =>
|
||||||
} as Partial<BalancesResponse>),
|
({
|
||||||
listKnownBankAccounts: async () => ({ accounts: [] })
|
balances: [{ available: `${currency}:0` }],
|
||||||
} as Partial<typeof wxApi> as any)
|
} as Partial<BalancesResponse>),
|
||||||
);
|
listKnownBankAccounts: async () => ({ accounts: [] }),
|
||||||
|
} as Partial<typeof wxApi> as any),
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status } = getLastResultOrThrow()
|
const { status } = getLastResultOrThrow();
|
||||||
expect(status).equal("loading")
|
expect(status).equal("loading");
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status } = getLastResultOrThrow()
|
const { status } = getLastResultOrThrow();
|
||||||
expect(status).equal("no-balance")
|
expect(status).equal("no-balance");
|
||||||
}
|
}
|
||||||
|
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should have status 'no-accounts' when balance is not empty and accounts is empty", async () => {
|
it("should have status 'no-accounts' when balance is not empty and accounts is empty", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
useComponentState(currency, nullFunction, nullFunction, {
|
mountHook(() =>
|
||||||
getBalance: async () => ({
|
useComponentState(currency, nullFunction, nullFunction, {
|
||||||
balances: [{ available: `${currency}:1`, }]
|
getBalance: async () =>
|
||||||
} as Partial<BalancesResponse>),
|
({
|
||||||
listKnownBankAccounts: async () => ({ accounts: [] })
|
balances: [{ available: `${currency}:1` }],
|
||||||
} as Partial<typeof wxApi> as any)
|
} as Partial<BalancesResponse>),
|
||||||
);
|
listKnownBankAccounts: async () => ({ accounts: [] }),
|
||||||
|
} as Partial<typeof wxApi> as any),
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status } = getLastResultOrThrow()
|
const { status } = getLastResultOrThrow();
|
||||||
expect(status).equal("loading")
|
expect(status).equal("loading");
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
{
|
{
|
||||||
const r = getLastResultOrThrow()
|
const r = getLastResultOrThrow();
|
||||||
if (r.status !== "no-accounts") expect.fail();
|
if (r.status !== "no-accounts") expect.fail();
|
||||||
expect(r.cancelHandler.onClick).not.undefined;
|
expect(r.cancelHandler.onClick).not.undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const ibanPayto = parsePaytoUri("payto://iban/ES8877998399652238")!;
|
const ibanPayto = parsePaytoUri("payto://iban/ES8877998399652238")!;
|
||||||
const talerBankPayto = parsePaytoUri("payto://x-taler-bank/ES8877998399652238")!;
|
const talerBankPayto = parsePaytoUri(
|
||||||
|
"payto://x-taler-bank/ES8877998399652238",
|
||||||
|
)!;
|
||||||
|
|
||||||
it("should have status 'ready' but unable to deposit ", async () => {
|
it("should have status 'ready' but unable to deposit ", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
useComponentState(currency, nullFunction, nullFunction, {
|
mountHook(() =>
|
||||||
getBalance: async () => ({
|
useComponentState(currency, nullFunction, nullFunction, {
|
||||||
balances: [{ available: `${currency}:1`, }]
|
getBalance: async () =>
|
||||||
} as Partial<BalancesResponse>),
|
({
|
||||||
listKnownBankAccounts: async () => ({ accounts: [ibanPayto] })
|
balances: [{ available: `${currency}:1` }],
|
||||||
} as Partial<typeof wxApi> as any)
|
} as Partial<BalancesResponse>),
|
||||||
);
|
listKnownBankAccounts: async () => ({ accounts: [ibanPayto] }),
|
||||||
|
} as Partial<typeof wxApi> as any),
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status } = getLastResultOrThrow()
|
const { status } = getLastResultOrThrow();
|
||||||
expect(status).equal("loading")
|
expect(status).equal("loading");
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const r = getLastResultOrThrow()
|
const r = getLastResultOrThrow();
|
||||||
if (r.status !== "ready") expect.fail();
|
if (r.status !== "ready") expect.fail();
|
||||||
expect(r.cancelHandler.onClick).not.undefined;
|
expect(r.cancelHandler.onClick).not.undefined;
|
||||||
expect(r.currency).eq(currency);
|
expect(r.currency).eq(currency);
|
||||||
expect(r.account.value).eq("0")
|
expect(r.account.value).eq("0");
|
||||||
expect(r.amount.value).eq("0")
|
expect(r.amount.value).eq("0");
|
||||||
expect(r.depositHandler.onClick).undefined;
|
expect(r.depositHandler.onClick).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not be able to deposit more than the balance ", async () => {
|
it("should not be able to deposit more than the balance ", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
useComponentState(currency, nullFunction, nullFunction, {
|
mountHook(() =>
|
||||||
getBalance: async () => ({
|
useComponentState(currency, nullFunction, nullFunction, {
|
||||||
balances: [{ available: `${currency}:1`, }]
|
getBalance: async () =>
|
||||||
} as Partial<BalancesResponse>),
|
({
|
||||||
listKnownBankAccounts: async () => ({ accounts: [ibanPayto] }),
|
balances: [{ available: `${currency}:1` }],
|
||||||
getFeeForDeposit: withoutFee
|
} as Partial<BalancesResponse>),
|
||||||
} as Partial<typeof wxApi> as any)
|
listKnownBankAccounts: async () => ({ accounts: [ibanPayto] }),
|
||||||
);
|
getFeeForDeposit: withoutFee,
|
||||||
|
} as Partial<typeof wxApi> as any),
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status } = getLastResultOrThrow()
|
const { status } = getLastResultOrThrow();
|
||||||
expect(status).equal("loading")
|
expect(status).equal("loading");
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const r = getLastResultOrThrow()
|
const r = getLastResultOrThrow();
|
||||||
if (r.status !== "ready") expect.fail();
|
if (r.status !== "ready") expect.fail();
|
||||||
expect(r.cancelHandler.onClick).not.undefined;
|
expect(r.cancelHandler.onClick).not.undefined;
|
||||||
expect(r.currency).eq(currency);
|
expect(r.currency).eq(currency);
|
||||||
expect(r.account.value).eq("0")
|
expect(r.account.value).eq("0");
|
||||||
expect(r.amount.value).eq("0")
|
expect(r.amount.value).eq("0");
|
||||||
expect(r.depositHandler.onClick).undefined;
|
expect(r.depositHandler.onClick).undefined;
|
||||||
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`))
|
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
|
||||||
|
|
||||||
r.amount.onInput("10")
|
r.amount.onInput("10");
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const r = getLastResultOrThrow()
|
const r = getLastResultOrThrow();
|
||||||
if (r.status !== "ready") expect.fail();
|
if (r.status !== "ready") expect.fail();
|
||||||
expect(r.cancelHandler.onClick).not.undefined;
|
expect(r.cancelHandler.onClick).not.undefined;
|
||||||
expect(r.currency).eq(currency);
|
expect(r.currency).eq(currency);
|
||||||
expect(r.account.value).eq("0")
|
expect(r.account.value).eq("0");
|
||||||
expect(r.amount.value).eq("10")
|
expect(r.amount.value).eq("10");
|
||||||
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`))
|
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
|
||||||
expect(r.depositHandler.onClick).undefined;
|
expect(r.depositHandler.onClick).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should calculate the fee upon entering amount ", async () => {
|
it("should calculate the fee upon entering amount ", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
useComponentState(currency, nullFunction, nullFunction, {
|
mountHook(() =>
|
||||||
getBalance: async () => ({
|
useComponentState(currency, nullFunction, nullFunction, {
|
||||||
balances: [{ available: `${currency}:1`, }]
|
getBalance: async () =>
|
||||||
} as Partial<BalancesResponse>),
|
({
|
||||||
listKnownBankAccounts: async () => ({ accounts: [ibanPayto] }),
|
balances: [{ available: `${currency}:1` }],
|
||||||
getFeeForDeposit: withSomeFee
|
} as Partial<BalancesResponse>),
|
||||||
} as Partial<typeof wxApi> as any)
|
listKnownBankAccounts: async () => ({ accounts: [ibanPayto] }),
|
||||||
);
|
getFeeForDeposit: withSomeFee,
|
||||||
|
} as Partial<typeof wxApi> as any),
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status } = getLastResultOrThrow()
|
const { status } = getLastResultOrThrow();
|
||||||
expect(status).equal("loading")
|
expect(status).equal("loading");
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const r = getLastResultOrThrow()
|
const r = getLastResultOrThrow();
|
||||||
if (r.status !== "ready") expect.fail();
|
if (r.status !== "ready") expect.fail();
|
||||||
expect(r.cancelHandler.onClick).not.undefined;
|
expect(r.cancelHandler.onClick).not.undefined;
|
||||||
expect(r.currency).eq(currency);
|
expect(r.currency).eq(currency);
|
||||||
expect(r.account.value).eq("0")
|
expect(r.account.value).eq("0");
|
||||||
expect(r.amount.value).eq("0")
|
expect(r.amount.value).eq("0");
|
||||||
expect(r.depositHandler.onClick).undefined;
|
expect(r.depositHandler.onClick).undefined;
|
||||||
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`))
|
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
|
||||||
|
|
||||||
r.amount.onInput("10")
|
r.amount.onInput("10");
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const r = getLastResultOrThrow()
|
const r = getLastResultOrThrow();
|
||||||
if (r.status !== "ready") expect.fail();
|
if (r.status !== "ready") expect.fail();
|
||||||
expect(r.cancelHandler.onClick).not.undefined;
|
expect(r.cancelHandler.onClick).not.undefined;
|
||||||
expect(r.currency).eq(currency);
|
expect(r.currency).eq(currency);
|
||||||
expect(r.account.value).eq("0")
|
expect(r.account.value).eq("0");
|
||||||
expect(r.amount.value).eq("10")
|
expect(r.amount.value).eq("10");
|
||||||
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`))
|
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`));
|
||||||
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:7`))
|
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:7`));
|
||||||
expect(r.depositHandler.onClick).undefined;
|
expect(r.depositHandler.onClick).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should calculate the fee upon selecting account ", async () => {
|
it("should calculate the fee upon selecting account ", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
useComponentState(currency, nullFunction, nullFunction, {
|
mountHook(() =>
|
||||||
getBalance: async () => ({
|
useComponentState(currency, nullFunction, nullFunction, {
|
||||||
balances: [{ available: `${currency}:1`, }]
|
getBalance: async () =>
|
||||||
} as Partial<BalancesResponse>),
|
({
|
||||||
listKnownBankAccounts: async () => ({ accounts: [ibanPayto, talerBankPayto] }),
|
balances: [{ available: `${currency}:1` }],
|
||||||
getFeeForDeposit: freeJustForIBAN
|
} as Partial<BalancesResponse>),
|
||||||
} as Partial<typeof wxApi> as any)
|
listKnownBankAccounts: async () => ({
|
||||||
);
|
accounts: [ibanPayto, talerBankPayto],
|
||||||
|
}),
|
||||||
|
getFeeForDeposit: freeJustForIBAN,
|
||||||
|
} as Partial<typeof wxApi> as any),
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status } = getLastResultOrThrow()
|
const { status } = getLastResultOrThrow();
|
||||||
expect(status).equal("loading")
|
expect(status).equal("loading");
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const r = getLastResultOrThrow()
|
const r = getLastResultOrThrow();
|
||||||
if (r.status !== "ready") expect.fail();
|
if (r.status !== "ready") expect.fail();
|
||||||
expect(r.cancelHandler.onClick).not.undefined;
|
expect(r.cancelHandler.onClick).not.undefined;
|
||||||
expect(r.currency).eq(currency);
|
expect(r.currency).eq(currency);
|
||||||
expect(r.account.value).eq("0")
|
expect(r.account.value).eq("0");
|
||||||
expect(r.amount.value).eq("0")
|
expect(r.amount.value).eq("0");
|
||||||
expect(r.depositHandler.onClick).undefined;
|
expect(r.depositHandler.onClick).undefined;
|
||||||
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`))
|
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
|
||||||
|
|
||||||
if (r.account.onChange === undefined) expect.fail();
|
if (r.account.onChange === undefined) expect.fail();
|
||||||
r.account.onChange("1")
|
r.account.onChange("1");
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const r = getLastResultOrThrow()
|
const r = getLastResultOrThrow();
|
||||||
if (r.status !== "ready") expect.fail();
|
if (r.status !== "ready") expect.fail();
|
||||||
expect(r.cancelHandler.onClick).not.undefined;
|
expect(r.cancelHandler.onClick).not.undefined;
|
||||||
expect(r.currency).eq(currency);
|
expect(r.currency).eq(currency);
|
||||||
expect(r.account.value).eq("1")
|
expect(r.account.value).eq("1");
|
||||||
expect(r.amount.value).eq("0")
|
expect(r.amount.value).eq("0");
|
||||||
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`))
|
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`));
|
||||||
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:0`))
|
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
|
||||||
expect(r.depositHandler.onClick).undefined;
|
expect(r.depositHandler.onClick).undefined;
|
||||||
|
|
||||||
r.amount.onInput("10")
|
r.amount.onInput("10");
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const r = getLastResultOrThrow()
|
const r = getLastResultOrThrow();
|
||||||
if (r.status !== "ready") expect.fail();
|
if (r.status !== "ready") expect.fail();
|
||||||
expect(r.cancelHandler.onClick).not.undefined;
|
expect(r.cancelHandler.onClick).not.undefined;
|
||||||
expect(r.currency).eq(currency);
|
expect(r.currency).eq(currency);
|
||||||
expect(r.account.value).eq("1")
|
expect(r.account.value).eq("1");
|
||||||
expect(r.amount.value).eq("10")
|
expect(r.amount.value).eq("10");
|
||||||
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`))
|
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`));
|
||||||
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:7`))
|
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:7`));
|
||||||
expect(r.depositHandler.onClick).undefined;
|
expect(r.depositHandler.onClick).undefined;
|
||||||
|
|
||||||
if (r.account.onChange === undefined) expect.fail();
|
if (r.account.onChange === undefined) expect.fail();
|
||||||
r.account.onChange("0")
|
r.account.onChange("0");
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const r = getLastResultOrThrow()
|
const r = getLastResultOrThrow();
|
||||||
if (r.status !== "ready") expect.fail();
|
if (r.status !== "ready") expect.fail();
|
||||||
expect(r.cancelHandler.onClick).not.undefined;
|
expect(r.cancelHandler.onClick).not.undefined;
|
||||||
expect(r.currency).eq(currency);
|
expect(r.currency).eq(currency);
|
||||||
expect(r.account.value).eq("0")
|
expect(r.account.value).eq("0");
|
||||||
expect(r.amount.value).eq("10")
|
expect(r.amount.value).eq("10");
|
||||||
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`))
|
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
|
||||||
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:10`))
|
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:10`));
|
||||||
expect(r.depositHandler.onClick).undefined;
|
expect(r.depositHandler.onClick).undefined;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it("should be able to deposit if has the enough balance ", async () => {
|
it("should be able to deposit if has the enough balance ", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() =>
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
useComponentState(currency, nullFunction, nullFunction, {
|
mountHook(() =>
|
||||||
getBalance: async () => ({
|
useComponentState(currency, nullFunction, nullFunction, {
|
||||||
balances: [{ available: `${currency}:15`, }]
|
getBalance: async () =>
|
||||||
} as Partial<BalancesResponse>),
|
({
|
||||||
listKnownBankAccounts: async () => ({ accounts: [ibanPayto] }),
|
balances: [{ available: `${currency}:15` }],
|
||||||
getFeeForDeposit: withSomeFee
|
} as Partial<BalancesResponse>),
|
||||||
} as Partial<typeof wxApi> as any)
|
listKnownBankAccounts: async () => ({ accounts: [ibanPayto] }),
|
||||||
);
|
getFeeForDeposit: withSomeFee,
|
||||||
|
} as Partial<typeof wxApi> as any),
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
const { status } = getLastResultOrThrow()
|
const { status } = getLastResultOrThrow();
|
||||||
expect(status).equal("loading")
|
expect(status).equal("loading");
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const r = getLastResultOrThrow()
|
const r = getLastResultOrThrow();
|
||||||
if (r.status !== "ready") expect.fail();
|
if (r.status !== "ready") expect.fail();
|
||||||
expect(r.cancelHandler.onClick).not.undefined;
|
expect(r.cancelHandler.onClick).not.undefined;
|
||||||
expect(r.currency).eq(currency);
|
expect(r.currency).eq(currency);
|
||||||
expect(r.account.value).eq("0")
|
expect(r.account.value).eq("0");
|
||||||
expect(r.amount.value).eq("0")
|
expect(r.amount.value).eq("0");
|
||||||
expect(r.depositHandler.onClick).undefined;
|
expect(r.depositHandler.onClick).undefined;
|
||||||
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`))
|
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
|
||||||
|
|
||||||
r.amount.onInput("10")
|
r.amount.onInput("10");
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const r = getLastResultOrThrow()
|
const r = getLastResultOrThrow();
|
||||||
if (r.status !== "ready") expect.fail();
|
if (r.status !== "ready") expect.fail();
|
||||||
expect(r.cancelHandler.onClick).not.undefined;
|
expect(r.cancelHandler.onClick).not.undefined;
|
||||||
expect(r.currency).eq(currency);
|
expect(r.currency).eq(currency);
|
||||||
expect(r.account.value).eq("0")
|
expect(r.account.value).eq("0");
|
||||||
expect(r.amount.value).eq("10")
|
expect(r.amount.value).eq("10");
|
||||||
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`))
|
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`));
|
||||||
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:7`))
|
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:7`));
|
||||||
expect(r.depositHandler.onClick).not.undefined;
|
expect(r.depositHandler.onClick).not.undefined;
|
||||||
|
|
||||||
r.amount.onInput("13")
|
r.amount.onInput("13");
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const r = getLastResultOrThrow()
|
const r = getLastResultOrThrow();
|
||||||
if (r.status !== "ready") expect.fail();
|
if (r.status !== "ready") expect.fail();
|
||||||
expect(r.cancelHandler.onClick).not.undefined;
|
expect(r.cancelHandler.onClick).not.undefined;
|
||||||
expect(r.currency).eq(currency);
|
expect(r.currency).eq(currency);
|
||||||
expect(r.account.value).eq("0")
|
expect(r.account.value).eq("0");
|
||||||
expect(r.amount.value).eq("13")
|
expect(r.amount.value).eq("13");
|
||||||
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`))
|
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`));
|
||||||
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:10`))
|
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:10`));
|
||||||
expect(r.depositHandler.onClick).not.undefined;
|
expect(r.depositHandler.onClick).not.undefined;
|
||||||
|
|
||||||
r.amount.onInput("15")
|
r.amount.onInput("15");
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const r = getLastResultOrThrow()
|
const r = getLastResultOrThrow();
|
||||||
if (r.status !== "ready") expect.fail();
|
if (r.status !== "ready") expect.fail();
|
||||||
expect(r.cancelHandler.onClick).not.undefined;
|
expect(r.cancelHandler.onClick).not.undefined;
|
||||||
expect(r.currency).eq(currency);
|
expect(r.currency).eq(currency);
|
||||||
expect(r.account.value).eq("0")
|
expect(r.account.value).eq("0");
|
||||||
expect(r.amount.value).eq("15")
|
expect(r.amount.value).eq("15");
|
||||||
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`))
|
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`));
|
||||||
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:12`))
|
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:12`));
|
||||||
expect(r.depositHandler.onClick).not.undefined;
|
expect(r.depositHandler.onClick).not.undefined;
|
||||||
r.amount.onInput("17")
|
r.amount.onInput("17");
|
||||||
}
|
}
|
||||||
await waitNextUpdate()
|
await waitNextUpdate();
|
||||||
|
|
||||||
{
|
{
|
||||||
const r = getLastResultOrThrow()
|
const r = getLastResultOrThrow();
|
||||||
if (r.status !== "ready") expect.fail();
|
if (r.status !== "ready") expect.fail();
|
||||||
expect(r.cancelHandler.onClick).not.undefined;
|
expect(r.cancelHandler.onClick).not.undefined;
|
||||||
expect(r.currency).eq(currency);
|
expect(r.currency).eq(currency);
|
||||||
expect(r.account.value).eq("0")
|
expect(r.account.value).eq("0");
|
||||||
expect(r.amount.value).eq("17")
|
expect(r.amount.value).eq("17");
|
||||||
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`))
|
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`));
|
||||||
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:14`))
|
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:14`));
|
||||||
expect(r.depositHandler.onClick).undefined;
|
expect(r.depositHandler.onClick).undefined;
|
||||||
}
|
}
|
||||||
await assertNoPendingUpdate()
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
});
|
|
||||||
|
@ -151,7 +151,6 @@ export function runGarbageCollector(): Promise<void> {
|
|||||||
return callBackend("run-gc", {});
|
return callBackend("run-gc", {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function getFeeForDeposit(
|
export function getFeeForDeposit(
|
||||||
depositPaytoUri: string,
|
depositPaytoUri: string,
|
||||||
amount: AmountString,
|
amount: AmountString,
|
||||||
@ -338,7 +337,7 @@ export function acceptWithdrawal(
|
|||||||
return callBackend("acceptBankIntegratedWithdrawal", {
|
return callBackend("acceptBankIntegratedWithdrawal", {
|
||||||
talerWithdrawUri,
|
talerWithdrawUri,
|
||||||
exchangeBaseUrl: selectedExchange,
|
exchangeBaseUrl: selectedExchange,
|
||||||
restrictAge
|
restrictAge,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -356,7 +355,7 @@ export function acceptManualWithdrawal(
|
|||||||
return callBackend("acceptManualWithdrawal", {
|
return callBackend("acceptManualWithdrawal", {
|
||||||
amount,
|
amount,
|
||||||
exchangeBaseUrl,
|
exchangeBaseUrl,
|
||||||
restrictAge
|
restrictAge,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -432,11 +431,12 @@ export function addExchange(req: AddExchangeRequest): Promise<void> {
|
|||||||
return callBackend("addExchange", req);
|
return callBackend("addExchange", req);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function prepareRefund(req: PrepareRefundRequest): Promise<PrepareRefundResult> {
|
export function prepareRefund(
|
||||||
|
req: PrepareRefundRequest,
|
||||||
|
): Promise<PrepareRefundResult> {
|
||||||
return callBackend("prepareRefund", req);
|
return callBackend("prepareRefund", req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function prepareTip(req: PrepareTipRequest): Promise<PrepareTipResult> {
|
export function prepareTip(req: PrepareTipRequest): Promise<PrepareTipResult> {
|
||||||
return callBackend("prepareTip", req);
|
return callBackend("prepareTip", req);
|
||||||
}
|
}
|
||||||
|
@ -26,9 +26,11 @@
|
|||||||
import {
|
import {
|
||||||
classifyTalerUri,
|
classifyTalerUri,
|
||||||
CoreApiResponse,
|
CoreApiResponse,
|
||||||
CoreApiResponseSuccess, Logger, TalerErrorCode,
|
CoreApiResponseSuccess,
|
||||||
|
Logger,
|
||||||
|
TalerErrorCode,
|
||||||
TalerUriType,
|
TalerUriType,
|
||||||
WalletDiagnostics
|
WalletDiagnostics,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import {
|
import {
|
||||||
DbAccess,
|
DbAccess,
|
||||||
@ -40,7 +42,7 @@ import {
|
|||||||
openPromise,
|
openPromise,
|
||||||
openTalerDatabase,
|
openTalerDatabase,
|
||||||
Wallet,
|
Wallet,
|
||||||
WalletStoresV1
|
WalletStoresV1,
|
||||||
} from "@gnu-taler/taler-wallet-core";
|
} from "@gnu-taler/taler-wallet-core";
|
||||||
import { SetTimeoutTimerAPI } from "@gnu-taler/taler-wallet-core";
|
import { SetTimeoutTimerAPI } from "@gnu-taler/taler-wallet-core";
|
||||||
import { BrowserCryptoWorkerFactory } from "./browserCryptoWorkerFactory.js";
|
import { BrowserCryptoWorkerFactory } from "./browserCryptoWorkerFactory.js";
|
||||||
@ -133,14 +135,14 @@ async function dispatch(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "run-gc": {
|
case "run-gc": {
|
||||||
logger.info("gc")
|
logger.info("gc");
|
||||||
const dump = await exportDb(currentDatabase!.idbHandle());
|
const dump = await exportDb(currentDatabase!.idbHandle());
|
||||||
await deleteTalerDatabase(indexedDB as any);
|
await deleteTalerDatabase(indexedDB as any);
|
||||||
logger.info("cleaned")
|
logger.info("cleaned");
|
||||||
await reinitWallet();
|
await reinitWallet();
|
||||||
logger.info("init")
|
logger.info("init");
|
||||||
await importDb(currentDatabase!.idbHandle(), dump)
|
await importDb(currentDatabase!.idbHandle(), dump);
|
||||||
logger.info("imported")
|
logger.info("imported");
|
||||||
r = wrapResponse({ result: true });
|
r = wrapResponse({ result: true });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -156,7 +158,9 @@ async function dispatch(
|
|||||||
platform.registerTalerHeaderListener(parseTalerUriAndRedirect);
|
platform.registerTalerHeaderListener(parseTalerUriAndRedirect);
|
||||||
r = wrapResponse({ newValue: true });
|
r = wrapResponse({ newValue: true });
|
||||||
} else {
|
} else {
|
||||||
const rem = await platform.getPermissionsApi().removeHostPermissions();
|
const rem = await platform
|
||||||
|
.getPermissionsApi()
|
||||||
|
.removeHostPermissions();
|
||||||
logger.trace("permissions removed:", rem);
|
logger.trace("permissions removed:", rem);
|
||||||
r = wrapResponse({ newVal: false });
|
r = wrapResponse({ newVal: false });
|
||||||
}
|
}
|
||||||
@ -184,7 +188,7 @@ async function dispatch(
|
|||||||
|
|
||||||
sendResponse(r);
|
sendResponse(r);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(`Error sending operation: ${req.operation}`, e)
|
logger.error(`Error sending operation: ${req.operation}`, e);
|
||||||
// might fail if tab disconnected
|
// might fail if tab disconnected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -218,7 +222,12 @@ async function reinitWallet(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.info("Setting up wallet");
|
logger.info("Setting up wallet");
|
||||||
const wallet = await Wallet.create(currentDatabase, httpLib, timer, cryptoWorker);
|
const wallet = await Wallet.create(
|
||||||
|
currentDatabase,
|
||||||
|
httpLib,
|
||||||
|
timer,
|
||||||
|
cryptoWorker,
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
await wallet.handleCoreApiRequest("initWallet", "native-init", {});
|
await wallet.handleCoreApiRequest("initWallet", "native-init", {});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -228,14 +237,14 @@ async function reinitWallet(): Promise<void> {
|
|||||||
}
|
}
|
||||||
wallet.addNotificationListener((x) => {
|
wallet.addNotificationListener((x) => {
|
||||||
const message: MessageFromBackend = { type: x.type };
|
const message: MessageFromBackend = { type: x.type };
|
||||||
platform.sendMessageToAllChannels(message)
|
platform.sendMessageToAllChannels(message);
|
||||||
});
|
});
|
||||||
|
|
||||||
platform.keepAlive(() => {
|
platform.keepAlive(() => {
|
||||||
return wallet.runTaskLoop().catch((e) => {
|
return wallet.runTaskLoop().catch((e) => {
|
||||||
logger.error("error during wallet task loop", e);
|
logger.error("error during wallet task loop", e);
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
// Useful for debugging in the background page.
|
// Useful for debugging in the background page.
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
(window as any).talerWallet = wallet;
|
(window as any).talerWallet = wallet;
|
||||||
@ -279,14 +288,13 @@ function parseTalerUriAndRedirect(tabId: number, talerUri: string): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main function to run for the WebExtension backend.
|
* Main function to run for the WebExtension backend.
|
||||||
*
|
*
|
||||||
* Sets up all event handlers and other machinery.
|
* Sets up all event handlers and other machinery.
|
||||||
*/
|
*/
|
||||||
export async function wxMain(): Promise<void> {
|
export async function wxMain(): Promise<void> {
|
||||||
logger.trace("starting")
|
logger.trace("starting");
|
||||||
const afterWalletIsInitialized = reinitWallet();
|
const afterWalletIsInitialized = reinitWallet();
|
||||||
|
|
||||||
platform.registerReloadOnNewVersion();
|
platform.registerReloadOnNewVersion();
|
||||||
@ -297,9 +305,9 @@ export async function wxMain(): Promise<void> {
|
|||||||
afterWalletIsInitialized.then(() => {
|
afterWalletIsInitialized.then(() => {
|
||||||
dispatch(message, sender, callback);
|
dispatch(message, sender, callback);
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
platform.registerAllIncomingConnections()
|
platform.registerAllIncomingConnections();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
platform.registerTalerHeaderListener(parseTalerUriAndRedirect);
|
platform.registerTalerHeaderListener(parseTalerUriAndRedirect);
|
||||||
@ -311,7 +319,10 @@ export async function wxMain(): Promise<void> {
|
|||||||
// modification of permissions.
|
// modification of permissions.
|
||||||
platform.getPermissionsApi().addPermissionsListener((perm, lastError) => {
|
platform.getPermissionsApi().addPermissionsListener((perm, lastError) => {
|
||||||
if (lastError) {
|
if (lastError) {
|
||||||
logger.error(`there was a problem trying to get permission ${perm}`, lastError);
|
logger.error(
|
||||||
|
`there was a problem trying to get permission ${perm}`,
|
||||||
|
lastError,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
platform.registerTalerHeaderListener(parseTalerUriAndRedirect);
|
platform.registerTalerHeaderListener(parseTalerUriAndRedirect);
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"lib": [
|
"lib": [
|
||||||
"es6",
|
"es2021",
|
||||||
"DOM"
|
"DOM"
|
||||||
],
|
],
|
||||||
"jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
"jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||||
@ -37,4 +37,4 @@
|
|||||||
"include": [
|
"include": [
|
||||||
"src/**/*"
|
"src/**/*"
|
||||||
]
|
]
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user