@dungarees/core 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/application.d.ts +66 -0
- package/application.js +80 -0
- package/fake.d.ts +17 -0
- package/fake.js +29 -0
- package/fake.test.d.ts +1 -0
- package/fake.test.js +53 -0
- package/marbles-vitest.d.ts +55 -0
- package/marbles-vitest.js +127 -0
- package/marbles-vitest.test.d.ts +1 -0
- package/marbles-vitest.test.js +104 -0
- package/marbles.d.ts +27 -0
- package/marbles.js +53 -0
- package/marbles.test.d.ts +1 -0
- package/marbles.test.js +36 -0
- package/package.json +481 -0
- package/type-util.d.ts +50 -0
- package/type-util.js +1 -0
- package/type-util.test.d.ts +1 -0
- package/type-util.test.js +135 -0
- package/url.d.ts +13 -0
- package/url.js +24 -0
- package/url.test.d.ts +1 -0
- package/url.test.js +48 -0
- package/util.d.ts +53 -0
- package/util.js +236 -0
- package/util.test.d.ts +1 -0
- package/util.test.js +202 -0
package/application.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type OptionalPatternList } from './util.ts';
|
|
2
|
+
export type Application<CONFIG extends Partial<ApplicationTypeConfig> = DefaultApplicationTypeConfig> = {
|
|
3
|
+
run: (identity?: CONFIG['identity'], overrides?: Partial<SingleApplicationArgs<ApplicationTypeConfigWithDefaults<CONFIG>>>) => ApplicationRunReturn<CONFIG>;
|
|
4
|
+
getArgs: () => Array<Partial<CreateApplicationArgs<CONFIG>>>;
|
|
5
|
+
};
|
|
6
|
+
export declare const createApplication: <const CONFIG extends Partial<ApplicationTypeConfig> = DefaultApplicationTypeConfig>(appArgs?: Partial<CreateApplicationArgs<ApplicationTypeConfigWithDefaults<CONFIG>>>, otherApp?: () => Application<ApplicationTypeConfigWithDefaults<CONFIG>>) => Application<ApplicationTypeConfigWithDefaults<CONFIG>>;
|
|
7
|
+
export type SingleApplicationArgs<CONFIG extends Partial<ApplicationTypeConfig>> = {
|
|
8
|
+
getExternalServices: GetExternalServices<CONFIG>;
|
|
9
|
+
getInternalServices: GetInternalServices<CONFIG>;
|
|
10
|
+
preMain: PreMain<CONFIG>;
|
|
11
|
+
getDelivery: GetDelivery<CONFIG>;
|
|
12
|
+
main: Main<CONFIG>;
|
|
13
|
+
onError: OnError;
|
|
14
|
+
topLevelErrorHandling: TopLevelErrorHandling;
|
|
15
|
+
exportState: ExportState<CONFIG>;
|
|
16
|
+
importState: ImportState<CONFIG>;
|
|
17
|
+
};
|
|
18
|
+
export type CreateApplicationArgs<CONFIG extends Partial<ApplicationTypeConfig>> = {
|
|
19
|
+
[KEY in keyof SingleApplicationArgs<CONFIG>]: OptionalPatternList<SingleApplicationArgs<CONFIG>[KEY], CONFIG['identity']>;
|
|
20
|
+
};
|
|
21
|
+
export type ApplicationTypeConfig = {
|
|
22
|
+
externalServices: Record<string, any>;
|
|
23
|
+
internalServices: Record<string, any>;
|
|
24
|
+
delivery: Record<string, any>;
|
|
25
|
+
output: any;
|
|
26
|
+
identity: any;
|
|
27
|
+
exportState: any;
|
|
28
|
+
};
|
|
29
|
+
type DefaultApplicationTypeConfig = {
|
|
30
|
+
externalServices: Record<string, never>;
|
|
31
|
+
internalServices: Record<string, never>;
|
|
32
|
+
delivery: Record<string, never>;
|
|
33
|
+
output: undefined;
|
|
34
|
+
identity: undefined;
|
|
35
|
+
exportState: undefined;
|
|
36
|
+
};
|
|
37
|
+
type GetExternalServices<CONFIG extends Partial<ApplicationTypeConfig>> = (identity: CONFIG['identity']) => CONFIG['externalServices'];
|
|
38
|
+
type GetInternalServices<CONFIG extends Partial<ApplicationTypeConfig>> = (externalServices: CONFIG['externalServices'], identity: CONFIG['identity']) => CONFIG['internalServices'];
|
|
39
|
+
type PreMain<CONFIG extends Partial<ApplicationTypeConfig>> = (internalServices: CONFIG['internalServices'], identity: CONFIG['identity']) => void;
|
|
40
|
+
type GetDelivery<CONFIG extends Partial<ApplicationTypeConfig>> = (injected: {
|
|
41
|
+
internalServices: CONFIG['internalServices'];
|
|
42
|
+
exportState: () => CONFIG['exportState'];
|
|
43
|
+
}, identity: CONFIG['identity']) => CONFIG['delivery'];
|
|
44
|
+
type Main<CONFIG extends Partial<ApplicationTypeConfig>> = (injected: {
|
|
45
|
+
internalServices: CONFIG['internalServices'];
|
|
46
|
+
delivery: CONFIG['delivery'];
|
|
47
|
+
}, identity: CONFIG['identity']) => CONFIG['output'];
|
|
48
|
+
type OnError = (error: unknown) => void;
|
|
49
|
+
type TopLevelErrorHandling = (onError: (error: unknown) => void) => void;
|
|
50
|
+
type ExportState<CONFIG extends Partial<ApplicationTypeConfig>> = (internalServices: CONFIG['internalServices']) => CONFIG['exportState'];
|
|
51
|
+
type ImportState<CONFIG extends Partial<ApplicationTypeConfig>> = (internalServices: CONFIG['internalServices'], newState: CONFIG['exportState']) => void;
|
|
52
|
+
type ApplicationTypeConfigWithDefaults<CONFIG extends Partial<ApplicationTypeConfig>> = {
|
|
53
|
+
[KEY in keyof DefaultApplicationTypeConfig]: CONFIG[KEY] extends ApplicationTypeConfig[KEY] ? CONFIG[KEY] : DefaultApplicationTypeConfig[KEY];
|
|
54
|
+
};
|
|
55
|
+
export type ApplicationTypeConfigWithAnys<CONFIG extends Partial<ApplicationTypeConfig>> = {
|
|
56
|
+
[KEY in keyof ApplicationTypeConfig]: CONFIG[KEY] extends ApplicationTypeConfig[KEY] ? CONFIG[KEY] : ApplicationTypeConfig[KEY];
|
|
57
|
+
};
|
|
58
|
+
export type ApplicationRunReturn<CONFIG extends Partial<ApplicationTypeConfig>> = {
|
|
59
|
+
externalServices: CONFIG['externalServices'];
|
|
60
|
+
internalServices: CONFIG['internalServices'];
|
|
61
|
+
delivery: CONFIG['delivery'];
|
|
62
|
+
output: CONFIG['output'];
|
|
63
|
+
importState: (newState: CONFIG['exportState']) => void;
|
|
64
|
+
exportState: () => CONFIG['exportState'];
|
|
65
|
+
};
|
|
66
|
+
export {};
|
package/application.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { assertDefined, findByPattern, optionalPatternToList, } from './util.js';
|
|
2
|
+
export const createApplication = (appArgs = {}, otherApp) => {
|
|
3
|
+
const DEFAULTS = {
|
|
4
|
+
getExternalServices: () => ({}),
|
|
5
|
+
getInternalServices: () => ({}),
|
|
6
|
+
preMain: () => { },
|
|
7
|
+
getDelivery: () => ({}),
|
|
8
|
+
main: () => { },
|
|
9
|
+
onError: () => { },
|
|
10
|
+
topLevelErrorHandling: () => { },
|
|
11
|
+
exportState: () => { },
|
|
12
|
+
importState: () => { },
|
|
13
|
+
};
|
|
14
|
+
const toPatternLists = (defaults) => Object.fromEntries(Object.entries(defaults).map(([key, value]) => {
|
|
15
|
+
return [key, optionalPatternToList(value)];
|
|
16
|
+
}));
|
|
17
|
+
const getArg = (patternLists, key, runIdentity) => {
|
|
18
|
+
const arg = findByPattern(patternLists[key], runIdentity);
|
|
19
|
+
return assertDefined(arg, `No matching identity for "${key}"`);
|
|
20
|
+
};
|
|
21
|
+
const getDefaultedArgs = (patternLists, identity) => Object.fromEntries(Object.entries(patternLists).map(([key]) => {
|
|
22
|
+
return [key, getArg(patternLists, key, identity)];
|
|
23
|
+
}));
|
|
24
|
+
const registerArg = (config, key, argPatternLists) => {
|
|
25
|
+
if (config[key] !== undefined) {
|
|
26
|
+
const oldArgPatterns = argPatternLists[key];
|
|
27
|
+
argPatternLists[key] = [
|
|
28
|
+
...argPatternLists[key],
|
|
29
|
+
...optionalPatternToList(config[key]),
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const register = (config, argPatternLists) => {
|
|
34
|
+
Object.keys(argPatternLists).forEach((argName) => {
|
|
35
|
+
registerArg(config, argName, argPatternLists);
|
|
36
|
+
});
|
|
37
|
+
return argPatternLists;
|
|
38
|
+
};
|
|
39
|
+
const app = {
|
|
40
|
+
run: (runIdentity, overrides = {}) => {
|
|
41
|
+
const [baseArg, ...restArgs] = app.getArgs();
|
|
42
|
+
const firstArgsWithDefaults = {
|
|
43
|
+
...DEFAULTS,
|
|
44
|
+
...baseArg,
|
|
45
|
+
};
|
|
46
|
+
const argPatternLists = restArgs.reduce((list, config) => register(config, list), toPatternLists(firstArgsWithDefaults));
|
|
47
|
+
const { getExternalServices, getInternalServices, preMain, getDelivery, main, onError, topLevelErrorHandling, exportState: exportStateOriginal, importState, } = {
|
|
48
|
+
...getDefaultedArgs(argPatternLists, runIdentity),
|
|
49
|
+
...overrides,
|
|
50
|
+
};
|
|
51
|
+
try {
|
|
52
|
+
topLevelErrorHandling(onError);
|
|
53
|
+
const externalServices = getExternalServices(runIdentity);
|
|
54
|
+
const internalServices = getInternalServices(externalServices, runIdentity);
|
|
55
|
+
preMain(internalServices, runIdentity);
|
|
56
|
+
const exportState = () => exportStateOriginal(internalServices);
|
|
57
|
+
const delivery = getDelivery({ internalServices, exportState }, runIdentity);
|
|
58
|
+
const output = main({ delivery, internalServices }, runIdentity);
|
|
59
|
+
return {
|
|
60
|
+
externalServices,
|
|
61
|
+
internalServices,
|
|
62
|
+
delivery,
|
|
63
|
+
output,
|
|
64
|
+
exportState,
|
|
65
|
+
importState: (newState) => {
|
|
66
|
+
importState(internalServices, newState);
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
onError(e);
|
|
72
|
+
throw new Error('Could not run application', { cause: e });
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
getArgs: () => {
|
|
76
|
+
return (otherApp !== undefined ? [...otherApp().getArgs(), appArgs] : [appArgs]);
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
return app;
|
|
80
|
+
};
|
package/fake.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Observable } from 'rxjs';
|
|
2
|
+
type MethodTypes = 'sync' | 'async' | 'observable';
|
|
3
|
+
export type FakeConfigs<FAKE extends object> = {
|
|
4
|
+
[KEY in keyof Partial<FAKE>]: FakeConfig;
|
|
5
|
+
};
|
|
6
|
+
type FakeConfig = {
|
|
7
|
+
type: MethodTypes;
|
|
8
|
+
error: Error;
|
|
9
|
+
};
|
|
10
|
+
type FakeWithThrowingMethods<FAKE extends object, FAKE_CONFIGS extends FakeConfigs<FAKE>> = {
|
|
11
|
+
[KEY in keyof FAKE]: KEY extends keyof FAKE_CONFIGS ? FAKE[KEY] extends (...args: any[]) => Observable<any> ? ThrowingObservable : FAKE[KEY] extends (...args: any[]) => Promise<any> ? ThrowingAsync : FAKE[KEY] extends (...args: any[]) => any ? ThrowingSync : FAKE[KEY] : FAKE[KEY];
|
|
12
|
+
};
|
|
13
|
+
type ThrowingSync = (...args: any[]) => never;
|
|
14
|
+
type ThrowingAsync = (...args: any[]) => Promise<never>;
|
|
15
|
+
type ThrowingObservable = (...args: any[]) => Observable<never>;
|
|
16
|
+
export declare const addErrorMethodsToFake: <T extends object, ARGS extends any[]>(originalFake: (...args: ARGS) => T) => (configs?: FakeConfigs<T>, ...restArgs: any[]) => FakeWithThrowingMethods<T, FakeConfigs<T>>;
|
|
17
|
+
export {};
|
package/fake.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { throwError, timer } from 'rxjs';
|
|
2
|
+
import { mergeMap } from 'rxjs/operators';
|
|
3
|
+
export const addErrorMethodsToFake = (originalFake) => (configs = {}, ...restArgs) => {
|
|
4
|
+
const fake = originalFake(...restArgs);
|
|
5
|
+
const throwingMethods = generateThrowingMethods(configs);
|
|
6
|
+
// It is a valid cast to more specific tpye
|
|
7
|
+
// eslint-disable-next-line
|
|
8
|
+
return {
|
|
9
|
+
...fake,
|
|
10
|
+
...throwingMethods,
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
const THROWING_METOD_GENERATORS = {
|
|
14
|
+
sync: (error) => () => {
|
|
15
|
+
throw error;
|
|
16
|
+
},
|
|
17
|
+
async: (error) => async () => {
|
|
18
|
+
throw error;
|
|
19
|
+
},
|
|
20
|
+
observable: (error) => () => timer(1).pipe(mergeMap(() => throwError(() => error))),
|
|
21
|
+
};
|
|
22
|
+
const generateThrowingMethods = (configs) => {
|
|
23
|
+
const entries = Object.entries(configs);
|
|
24
|
+
const throwingMethods = entries.map(([method, { error, type }]) => [
|
|
25
|
+
method,
|
|
26
|
+
THROWING_METOD_GENERATORS[type](error),
|
|
27
|
+
]);
|
|
28
|
+
return Object.fromEntries(throwingMethods);
|
|
29
|
+
};
|
package/fake.test.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/fake.test.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { addErrorMethodsToFake } from './fake.js';
|
|
2
|
+
import { coreMarbles } from '@dungarees/core/marbles-vitest.ts';
|
|
3
|
+
import { of } from 'rxjs';
|
|
4
|
+
import { expect, test } from 'vitest';
|
|
5
|
+
test('Create the original if args are empty', () => {
|
|
6
|
+
const originalFake = () => ({ method: () => 1 });
|
|
7
|
+
const fakeCreator = addErrorMethodsToFake(originalFake);
|
|
8
|
+
const fake = fakeCreator();
|
|
9
|
+
expect(fake.method).not.toThrow();
|
|
10
|
+
});
|
|
11
|
+
test('Create sync throwing function', () => {
|
|
12
|
+
const originalFake = () => ({ method: () => 1 });
|
|
13
|
+
const fakeCreator = addErrorMethodsToFake(originalFake);
|
|
14
|
+
const error = new Error('test error');
|
|
15
|
+
const fake = fakeCreator({ method: { type: 'sync', error } });
|
|
16
|
+
expect(fake.method).toThrow(error);
|
|
17
|
+
});
|
|
18
|
+
test('Create multiple sync throwing function', () => {
|
|
19
|
+
const originalFake = () => ({ method: () => 1, method2: () => 2 });
|
|
20
|
+
const fakeCreator = addErrorMethodsToFake(originalFake);
|
|
21
|
+
const error = new Error('test error');
|
|
22
|
+
const error2 = new Error('test error2');
|
|
23
|
+
const fake = fakeCreator({
|
|
24
|
+
method: { type: 'sync', error },
|
|
25
|
+
method2: { type: 'sync', error: error2 },
|
|
26
|
+
});
|
|
27
|
+
expect(fake.method).toThrow(error);
|
|
28
|
+
expect(fake.method2).toThrow(error2);
|
|
29
|
+
});
|
|
30
|
+
test('Create async throwing function', async () => {
|
|
31
|
+
const originalFake = () => ({ method: async () => 1 });
|
|
32
|
+
const fakeCreator = addErrorMethodsToFake(originalFake);
|
|
33
|
+
const error = new Error('test error');
|
|
34
|
+
const fake = fakeCreator({ method: { type: 'async', error } });
|
|
35
|
+
await expect(fake.method).rejects.toEqual(error);
|
|
36
|
+
});
|
|
37
|
+
test('Create observable throwing function', coreMarbles((m) => {
|
|
38
|
+
const originalFake = () => ({ method: () => of(1) });
|
|
39
|
+
const fakeCreator = addErrorMethodsToFake(originalFake);
|
|
40
|
+
const error = new Error('test error');
|
|
41
|
+
const fake = fakeCreator({ method: { type: 'observable', error } });
|
|
42
|
+
m.expect(fake.method()).toBeObservable('-#', {}, error);
|
|
43
|
+
}));
|
|
44
|
+
test('Forwarding constructor paramters', () => {
|
|
45
|
+
const originalFake = (num1, num2) => ({
|
|
46
|
+
method: () => num1,
|
|
47
|
+
method2: () => num2,
|
|
48
|
+
});
|
|
49
|
+
const fakeCreator = addErrorMethodsToFake(originalFake);
|
|
50
|
+
const fake = fakeCreator({}, 1, 2);
|
|
51
|
+
expect(fake.method()).toBe(1);
|
|
52
|
+
expect(fake.method2()).toBe(2);
|
|
53
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Observable } from 'rxjs';
|
|
2
|
+
import { type NamedCase, type UnnamedCase } from 'rxjs-marbles/cases.js';
|
|
3
|
+
import { type Configuration } from 'rxjs-marbles/configuration.js';
|
|
4
|
+
import { type Context } from 'rxjs-marbles/context.js';
|
|
5
|
+
import { Expect } from 'rxjs-marbles/expect.js';
|
|
6
|
+
import { type MarblesFunction } from 'rxjs-marbles/marbles.js';
|
|
7
|
+
import type { ExpectHelpers, TestObservableLike } from 'rxjs-marbles/types.js';
|
|
8
|
+
export type CasesFunction = {
|
|
9
|
+
<T extends UnnamedCase>(name: string, func: (context: Context, _case: T) => void, cases: Record<string, T>): void;
|
|
10
|
+
<T extends NamedCase>(name: string, func: (context: Context, _case: T) => void, cases: T[]): void;
|
|
11
|
+
};
|
|
12
|
+
export declare function configure(configuration: Configuration): {
|
|
13
|
+
cases: CasesFunction;
|
|
14
|
+
marbles: MarblesFunction;
|
|
15
|
+
};
|
|
16
|
+
declare const marbles: MarblesFunction;
|
|
17
|
+
type Marbles = typeof marbles;
|
|
18
|
+
type MarblesRunner = Parameters<Marbles>[0];
|
|
19
|
+
type MarblesParam = Parameters<MarblesRunner>[0];
|
|
20
|
+
type Runner = (m: MarblesExtensions, ...args: any[]) => ReturnType<MarblesRunner>;
|
|
21
|
+
type MarbleFunctions = Record<string, () => void>;
|
|
22
|
+
type MarblesExtensions = {
|
|
23
|
+
coldCall: (marble: string, functions: MarbleFunctions) => void;
|
|
24
|
+
coldBoolean: (marble: string) => TestObservableLike<boolean>;
|
|
25
|
+
coldValue: <T = any>(marble: string, value: T) => TestObservableLike<T>;
|
|
26
|
+
coldValueOrUndefined: <T = any>(marble: string, value: T) => TestObservableLike<T | undefined>;
|
|
27
|
+
coldStep: <T = any>(value: T, steps?: number) => TestObservableLike<T>;
|
|
28
|
+
coldStepAndClose: <T = any>(value: T, steps?: number) => TestObservableLike<T>;
|
|
29
|
+
coldError: (error: any, steps?: number) => TestObservableLike<any>;
|
|
30
|
+
coldStepAndError: <T = any>(value: any, error: any, steps?: number) => TestObservableLike<T>;
|
|
31
|
+
expect: <T = any>(actual: Observable<T>, subscription?: string) => ExtendedExpect<T>;
|
|
32
|
+
} & MarblesParam;
|
|
33
|
+
declare class ExtendedExpect<T> extends Expect<T> {
|
|
34
|
+
readonly actual_: Observable<T>;
|
|
35
|
+
readonly helpers_: ExpectHelpers;
|
|
36
|
+
readonly subscription_?: string;
|
|
37
|
+
constructor(actual_: Observable<T>, helpers_: ExpectHelpers, subscription_?: string);
|
|
38
|
+
toBeObservableBoolean(marble: string): void;
|
|
39
|
+
toBeObservableValue(value: T): void;
|
|
40
|
+
toBeObservableValue(marble: string, value: T): void;
|
|
41
|
+
toBeObservableValueAndClose(value: T): void;
|
|
42
|
+
toBeObservableValueAndError(value: T, error: any): void;
|
|
43
|
+
toBeObservableValueOrUndefined(marble: string, value: T): void;
|
|
44
|
+
toBeObservableStep(value: T, steps?: number): void;
|
|
45
|
+
toBeObservableStepAndClose(value: T, steps?: number): void;
|
|
46
|
+
toBeObservableError(error: any, steps?: number): void;
|
|
47
|
+
toBeObservableStepAndError(value: T, error: any, steps?: number): void;
|
|
48
|
+
}
|
|
49
|
+
export declare const coreMarbles: (runner: Runner) => (() => void);
|
|
50
|
+
export declare const MARBLES_BOOLEAN: {
|
|
51
|
+
t: boolean;
|
|
52
|
+
f: boolean;
|
|
53
|
+
};
|
|
54
|
+
export declare const mtest: (name: string, runner: Runner) => void;
|
|
55
|
+
export {};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { _cases } from 'rxjs-marbles/cases.js';
|
|
2
|
+
import { defaults } from 'rxjs-marbles/configuration.js';
|
|
3
|
+
import { Expect } from 'rxjs-marbles/expect.js';
|
|
4
|
+
import { configure as _configure } from 'rxjs-marbles/marbles.js';
|
|
5
|
+
import { describe, expect, test } from 'vitest';
|
|
6
|
+
export function configure(configuration) {
|
|
7
|
+
const { marbles } = _configure({
|
|
8
|
+
...defaults(),
|
|
9
|
+
assertDeepEqual: (a, e) => {
|
|
10
|
+
expect(a).toStrictEqual(e);
|
|
11
|
+
},
|
|
12
|
+
frameworkMatcher: true,
|
|
13
|
+
...configuration,
|
|
14
|
+
});
|
|
15
|
+
function cases(name, func, cases) {
|
|
16
|
+
describe(name, () => {
|
|
17
|
+
_cases((c) => {
|
|
18
|
+
const t = c?.only !== undefined ? test.only : c?.skip === undefined ? test.skip : test;
|
|
19
|
+
if (func.length > 2) {
|
|
20
|
+
t(c.name, marbles((m, second, ...rest) => func(m, c, second, ...rest)));
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
t(c.name, marbles((m, ...rest) => func(m, c, ...rest)));
|
|
24
|
+
}
|
|
25
|
+
}, cases);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return { cases, marbles };
|
|
29
|
+
}
|
|
30
|
+
const { marbles } = configure({});
|
|
31
|
+
class ExtendedExpect extends Expect {
|
|
32
|
+
constructor(actual_, helpers_, subscription_) {
|
|
33
|
+
super(actual_, helpers_, subscription_);
|
|
34
|
+
this.actual_ = actual_;
|
|
35
|
+
this.helpers_ = helpers_;
|
|
36
|
+
this.subscription_ = subscription_;
|
|
37
|
+
}
|
|
38
|
+
toBeObservableBoolean(marble) {
|
|
39
|
+
this.toBeObservable(marble, MARBLES_BOOLEAN);
|
|
40
|
+
}
|
|
41
|
+
toBeObservableValue(...args) {
|
|
42
|
+
const value = args.length === 1 ? args[0] : args[1];
|
|
43
|
+
const marble = args.length === 1 ? 'v' : args[0];
|
|
44
|
+
this.toBeObservable(marble, { v: value });
|
|
45
|
+
}
|
|
46
|
+
toBeObservableValueAndClose(value) {
|
|
47
|
+
this.toBeObservable('(v|)', { v: value });
|
|
48
|
+
}
|
|
49
|
+
toBeObservableValueAndError(value, error) {
|
|
50
|
+
const marble = '(v#)';
|
|
51
|
+
this.toBeObservable(marble, { v: value }, error);
|
|
52
|
+
}
|
|
53
|
+
toBeObservableValueOrUndefined(marble, value) {
|
|
54
|
+
this.toBeObservable(marble, { v: value, '0': undefined });
|
|
55
|
+
}
|
|
56
|
+
toBeObservableStep(value, steps = 1) {
|
|
57
|
+
const marble = `-`.repeat(steps) + 'v';
|
|
58
|
+
this.toBeObservable(marble, { v: value });
|
|
59
|
+
}
|
|
60
|
+
toBeObservableStepAndClose(value, steps = 1) {
|
|
61
|
+
const marble = `-`.repeat(steps) + '(v|)';
|
|
62
|
+
this.toBeObservable(marble, { v: value });
|
|
63
|
+
}
|
|
64
|
+
toBeObservableError(error, steps = 1) {
|
|
65
|
+
const marble = `-`.repeat(steps) + '#';
|
|
66
|
+
this.toBeObservable(marble, {}, error);
|
|
67
|
+
}
|
|
68
|
+
toBeObservableStepAndError(value, error, steps = 1) {
|
|
69
|
+
const marble = `-`.repeat(steps) + '(v#)';
|
|
70
|
+
this.toBeObservable(marble, { v: value }, error);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export const coreMarbles = (runner) => (...args) => marbles((m) => {
|
|
74
|
+
const coldCall = (marble, functions) => {
|
|
75
|
+
const marbleDefinition = Object.fromEntries(Object.keys(functions).map((key) => [key, key]));
|
|
76
|
+
m.cold(marble, marbleDefinition).subscribe((key) => {
|
|
77
|
+
functions[key]?.();
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
const coldBoolean = (marble) => m.cold(marble, MARBLES_BOOLEAN);
|
|
81
|
+
const coldValue = (marble, value) => m.cold(marble, { v: value });
|
|
82
|
+
const coldValueOrUndefined = (marble, value) => m.cold(marble, { v: value, 0: undefined });
|
|
83
|
+
const coldStep = (value, steps = 1) => m.cold(`-`.repeat(steps) + 'v', { v: value });
|
|
84
|
+
const coldStepAndClose = (value, steps = 1) => m.cold(`-`.repeat(steps) + '(v|)', { v: value });
|
|
85
|
+
const coldError = (error, steps = 1) => m.cold(`-`.repeat(steps) + '#', {}, error);
|
|
86
|
+
const coldStepAndError = (value, error, steps = 1) => m.cold(`-`.repeat(steps) + '(v#)', { v: value }, error);
|
|
87
|
+
// This function and the ExtendedExpect depends on internals of the `rxjs-marbles` library
|
|
88
|
+
// potentially not future proof
|
|
89
|
+
const expect = (actual, subscription) => {
|
|
90
|
+
const { helpers_ } = m;
|
|
91
|
+
return new ExtendedExpect(actual, helpers_, subscription);
|
|
92
|
+
};
|
|
93
|
+
// The methods on `m` (the RunContext) are on the prototype, so we have to bind the original
|
|
94
|
+
// ones to be able to use destructuring
|
|
95
|
+
return runner({
|
|
96
|
+
get autoFlush() {
|
|
97
|
+
return m.autoFlush;
|
|
98
|
+
},
|
|
99
|
+
coldCall,
|
|
100
|
+
coldBoolean,
|
|
101
|
+
coldValue,
|
|
102
|
+
coldValueOrUndefined,
|
|
103
|
+
coldStep,
|
|
104
|
+
coldStepAndClose,
|
|
105
|
+
coldError,
|
|
106
|
+
coldStepAndError,
|
|
107
|
+
expect,
|
|
108
|
+
equal: m.equal.bind(m),
|
|
109
|
+
cold: m.cold.bind(m),
|
|
110
|
+
bind: m.bind.bind(m),
|
|
111
|
+
configure: m.configure.bind(m),
|
|
112
|
+
flush: m.flush.bind(m),
|
|
113
|
+
has: m.has.bind(m),
|
|
114
|
+
hot: m.hot.bind(m),
|
|
115
|
+
reframe: m.reframe.bind(m),
|
|
116
|
+
scheduler: m.scheduler,
|
|
117
|
+
teardown: m.teardown.bind(m),
|
|
118
|
+
time: m.time.bind(m),
|
|
119
|
+
}, ...args);
|
|
120
|
+
})();
|
|
121
|
+
export const MARBLES_BOOLEAN = {
|
|
122
|
+
t: true,
|
|
123
|
+
f: false,
|
|
124
|
+
};
|
|
125
|
+
export const mtest = (name, runner) => {
|
|
126
|
+
test(name, coreMarbles((m) => runner(m)));
|
|
127
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { coreMarbles, mtest } from './marbles-vitest.js';
|
|
2
|
+
import { Subject } from 'rxjs';
|
|
3
|
+
import { test } from 'vitest';
|
|
4
|
+
test('it should run a single marble', coreMarbles(({ expect, cold }) => {
|
|
5
|
+
expect(cold('a')).toBeObservable('a', { a: 'a' });
|
|
6
|
+
}));
|
|
7
|
+
test.each([
|
|
8
|
+
['tf', 'tf'],
|
|
9
|
+
['ft', 'ft'],
|
|
10
|
+
])('it should work with each', coreMarbles(({ expect, coldBoolean }, a, b) => {
|
|
11
|
+
expect(coldBoolean(a)).toBeObservableBoolean(b);
|
|
12
|
+
}));
|
|
13
|
+
mtest('it should run without the wrapper', ({ expect, coldBoolean }) => {
|
|
14
|
+
expect(coldBoolean('tf')).toBeObservableBoolean('tf');
|
|
15
|
+
});
|
|
16
|
+
mtest('it should run a single function', ({ expect, coldCall }) => {
|
|
17
|
+
const s = new Subject();
|
|
18
|
+
coldCall('-cc', {
|
|
19
|
+
c: () => {
|
|
20
|
+
s.next('c');
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
expect(s).toBeObservable('-cc', { c: 'c' });
|
|
24
|
+
});
|
|
25
|
+
mtest('it should run multiple functions', ({ expect, coldCall }) => {
|
|
26
|
+
const s = new Subject();
|
|
27
|
+
coldCall('-cd', {
|
|
28
|
+
c: () => {
|
|
29
|
+
s.next('c');
|
|
30
|
+
},
|
|
31
|
+
d: () => {
|
|
32
|
+
s.next('d');
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
expect(s).toBeObservable('-cd', { c: 'c', d: 'd' });
|
|
36
|
+
});
|
|
37
|
+
mtest('it should create a boolean marble', ({ expect, coldBoolean }) => {
|
|
38
|
+
expect(coldBoolean('tf')).toBeObservable('tf', { t: true, f: false });
|
|
39
|
+
});
|
|
40
|
+
mtest('it should assert a boolean marble', ({ expect, cold }) => {
|
|
41
|
+
expect(cold('tf', { t: true, f: false })).toBeObservableBoolean('tf');
|
|
42
|
+
});
|
|
43
|
+
mtest('it should create a cold marble with a value', ({ expect, coldValue }) => {
|
|
44
|
+
expect(coldValue('-v', true)).toBeObservableBoolean('-t');
|
|
45
|
+
});
|
|
46
|
+
mtest('it should create a cold marble with a value or undefined', ({ expect, coldValueOrUndefined }) => {
|
|
47
|
+
expect(coldValueOrUndefined('-v0', true)).toBeObservable('-t0', {
|
|
48
|
+
t: true,
|
|
49
|
+
'0': undefined,
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
mtest('it should create a cold marble with a value step', ({ expect, coldStep }) => {
|
|
53
|
+
expect(coldStep(true)).toBeObservableBoolean('-t');
|
|
54
|
+
expect(coldStep(true, 2)).toBeObservableBoolean('--t');
|
|
55
|
+
});
|
|
56
|
+
mtest('it should create a cold marble with a value step and close', ({ expect, coldStepAndClose }) => {
|
|
57
|
+
expect(coldStepAndClose(true)).toBeObservableBoolean('-(t|)');
|
|
58
|
+
expect(coldStepAndClose(true, 2)).toBeObservableBoolean('--(t|)');
|
|
59
|
+
});
|
|
60
|
+
mtest('it should create a cold marble with an error', ({ expect, coldError }) => {
|
|
61
|
+
expect(coldError(new Error('test error'))).toBeObservable('-#', {}, new Error('test error'));
|
|
62
|
+
expect(coldError(new Error('test error'), 2)).toBeObservable('--#', {}, new Error('test error'));
|
|
63
|
+
});
|
|
64
|
+
mtest('it should create a cold marble with a value and error', ({ expect, coldStepAndError }) => {
|
|
65
|
+
expect(coldStepAndError(true, new Error('test error')))
|
|
66
|
+
.toBeObservable('-(t#)', { t: true }, new Error('test error'));
|
|
67
|
+
expect(coldStepAndError(true, new Error('test error'), 2))
|
|
68
|
+
.toBeObservable('--(t#)', { t: true }, new Error('test error'));
|
|
69
|
+
});
|
|
70
|
+
mtest('it should assert a value marble', ({ expect, coldStep }) => {
|
|
71
|
+
expect(coldStep(true)).toBeObservableValue('-v', true);
|
|
72
|
+
});
|
|
73
|
+
mtest('it should assert a value marble default pattern', ({ expect, coldStep }) => {
|
|
74
|
+
expect(coldStep(true, 0)).toBeObservableValue(true);
|
|
75
|
+
});
|
|
76
|
+
mtest('it should assert a value and close marble', ({ expect, coldStepAndClose }) => {
|
|
77
|
+
expect(coldStepAndClose(true, 0)).toBeObservableValueAndClose(true);
|
|
78
|
+
});
|
|
79
|
+
mtest('it should assert a value and close marble', ({ expect, coldStepAndError }) => {
|
|
80
|
+
expect(coldStepAndError(true, new Error('test error'), 0)).toBeObservableValueAndError(true, new Error('test error'));
|
|
81
|
+
});
|
|
82
|
+
mtest('it should assert a value marble or undefined', ({ expect, coldValueOrUndefined }) => {
|
|
83
|
+
expect(coldValueOrUndefined('-v0', true)).toBeObservableValueOrUndefined('-v0', true);
|
|
84
|
+
});
|
|
85
|
+
mtest('it should assert a step', ({ expect, coldStep }) => {
|
|
86
|
+
expect(coldStep(true)).toBeObservableStep(true);
|
|
87
|
+
expect(coldStep(true, 2)).toBeObservableStep(true, 2);
|
|
88
|
+
});
|
|
89
|
+
mtest('it should assert a step and close', ({ expect, coldStepAndClose }) => {
|
|
90
|
+
expect(coldStepAndClose(true)).toBeObservableStepAndClose(true);
|
|
91
|
+
expect(coldStepAndClose(true, 2)).toBeObservableStepAndClose(true, 2);
|
|
92
|
+
});
|
|
93
|
+
mtest('it should assert an error', ({ expect, coldError }) => {
|
|
94
|
+
expect(coldError(new Error('test error')))
|
|
95
|
+
.toBeObservableError(new Error('test error'));
|
|
96
|
+
expect(coldError(new Error('test error'), 2))
|
|
97
|
+
.toBeObservableError(new Error('test error'), 2);
|
|
98
|
+
});
|
|
99
|
+
mtest('it should assert a step with an error', ({ expect, coldStepAndError }) => {
|
|
100
|
+
expect(coldStepAndError(true, new Error('test error')))
|
|
101
|
+
.toBeObservableStepAndError(true, new Error('test error'));
|
|
102
|
+
expect(coldStepAndError(true, new Error('test error'), 2))
|
|
103
|
+
.toBeObservableStepAndError(true, new Error('test error'), 2);
|
|
104
|
+
});
|
package/marbles.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Observable } from 'rxjs';
|
|
2
|
+
import { Expect } from 'rxjs-marbles/expect.js';
|
|
3
|
+
import { marbles } from 'rxjs-marbles/jest/index.js';
|
|
4
|
+
import type { ExpectHelpers, TestObservableLike } from 'rxjs-marbles/types.js';
|
|
5
|
+
type Marbles = typeof marbles;
|
|
6
|
+
type MarblesRunner = Parameters<Marbles>[0];
|
|
7
|
+
type MarblesParam = Parameters<MarblesRunner>[0];
|
|
8
|
+
type Runner = (m: MarblesExtensions, ...args: any[]) => ReturnType<MarblesRunner>;
|
|
9
|
+
type MarbleFunctions = Record<string, () => void>;
|
|
10
|
+
type MarblesExtensions = {
|
|
11
|
+
coldCall: (marble: string, functions: MarbleFunctions) => void;
|
|
12
|
+
coldBoolean: (marble: string) => TestObservableLike<boolean>;
|
|
13
|
+
expect: <T = any>(actual: Observable<T>, subscription?: string) => ExtendedExpect<T>;
|
|
14
|
+
} & MarblesParam;
|
|
15
|
+
declare class ExtendedExpect<T> extends Expect<T> {
|
|
16
|
+
readonly actual_: Observable<T>;
|
|
17
|
+
readonly helpers_: ExpectHelpers;
|
|
18
|
+
readonly subscription_?: string;
|
|
19
|
+
constructor(actual_: Observable<T>, helpers_: ExpectHelpers, subscription_?: string);
|
|
20
|
+
toBeObservableBoolean(marble: string): void;
|
|
21
|
+
}
|
|
22
|
+
export declare const coreMarbles: (runner: Runner) => (() => void);
|
|
23
|
+
export declare const MARBLES_BOOLEAN: {
|
|
24
|
+
t: boolean;
|
|
25
|
+
f: boolean;
|
|
26
|
+
};
|
|
27
|
+
export {};
|
package/marbles.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Expect } from 'rxjs-marbles/expect.js';
|
|
2
|
+
import { marbles } from 'rxjs-marbles/jest/index.js';
|
|
3
|
+
class ExtendedExpect extends Expect {
|
|
4
|
+
constructor(actual_, helpers_, subscription_) {
|
|
5
|
+
super(actual_, helpers_, subscription_);
|
|
6
|
+
this.actual_ = actual_;
|
|
7
|
+
this.helpers_ = helpers_;
|
|
8
|
+
this.subscription_ = subscription_;
|
|
9
|
+
}
|
|
10
|
+
toBeObservableBoolean(marble) {
|
|
11
|
+
this.toBeObservable(marble, MARBLES_BOOLEAN);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export const coreMarbles = (runner) => (...args) => marbles((m) => {
|
|
15
|
+
const coldCall = (marble, functions) => {
|
|
16
|
+
const marbleDefinition = Object.fromEntries(Object.keys(functions).map((key) => [key, key]));
|
|
17
|
+
m.cold(marble, marbleDefinition).subscribe((key) => {
|
|
18
|
+
functions[key]?.();
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
const coldBoolean = (marble) => m.cold(marble, MARBLES_BOOLEAN);
|
|
22
|
+
// This function and the ExtendedExpect depends on internals of the `rxjs-marbles` library
|
|
23
|
+
// potentially not future proof
|
|
24
|
+
const expect = (actual, subscription) => {
|
|
25
|
+
const { helpers_ } = m;
|
|
26
|
+
return new ExtendedExpect(actual, helpers_, subscription);
|
|
27
|
+
};
|
|
28
|
+
// The methods on `m` (the RunContext) are on the prototype, so we have to bind the original
|
|
29
|
+
// ones to be able to use destructuring
|
|
30
|
+
return runner({
|
|
31
|
+
get autoFlush() {
|
|
32
|
+
return m.autoFlush;
|
|
33
|
+
},
|
|
34
|
+
coldCall,
|
|
35
|
+
coldBoolean,
|
|
36
|
+
expect,
|
|
37
|
+
equal: m.equal.bind(m),
|
|
38
|
+
cold: m.cold.bind(m),
|
|
39
|
+
bind: m.bind.bind(m),
|
|
40
|
+
configure: m.configure.bind(m),
|
|
41
|
+
flush: m.flush.bind(m),
|
|
42
|
+
has: m.has.bind(m),
|
|
43
|
+
hot: m.hot.bind(m),
|
|
44
|
+
reframe: m.reframe.bind(m),
|
|
45
|
+
scheduler: m.scheduler,
|
|
46
|
+
teardown: m.teardown.bind(m),
|
|
47
|
+
time: m.time.bind(m),
|
|
48
|
+
}, ...args);
|
|
49
|
+
})();
|
|
50
|
+
export const MARBLES_BOOLEAN = {
|
|
51
|
+
t: true,
|
|
52
|
+
f: false,
|
|
53
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/marbles.test.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { coreMarbles } from '@dungarees/core/marbles.ts';
|
|
2
|
+
import { Subject } from 'rxjs';
|
|
3
|
+
import { test } from 'vitest';
|
|
4
|
+
test('it should run a single function', coreMarbles(({ expect, coldCall }) => {
|
|
5
|
+
const s = new Subject();
|
|
6
|
+
coldCall('-cc', {
|
|
7
|
+
c: () => {
|
|
8
|
+
s.next('c');
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
expect(s).toBeObservable('-cc', { c: 'c' });
|
|
12
|
+
}));
|
|
13
|
+
test('it should run multiple functions', coreMarbles(({ expect, coldCall }) => {
|
|
14
|
+
const s = new Subject();
|
|
15
|
+
coldCall('-cd', {
|
|
16
|
+
c: () => {
|
|
17
|
+
s.next('c');
|
|
18
|
+
},
|
|
19
|
+
d: () => {
|
|
20
|
+
s.next('d');
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
expect(s).toBeObservable('-cd', { c: 'c', d: 'd' });
|
|
24
|
+
}));
|
|
25
|
+
test('it should create a boolean marble', coreMarbles(({ expect, coldBoolean }) => {
|
|
26
|
+
expect(coldBoolean('tf')).toBeObservable('tf', { t: true, f: false });
|
|
27
|
+
}));
|
|
28
|
+
test('it should assert a boolean marble', coreMarbles(({ expect, cold }) => {
|
|
29
|
+
expect(cold('tf', { t: true, f: false })).toBeObservableBoolean('tf');
|
|
30
|
+
}));
|
|
31
|
+
test.each([
|
|
32
|
+
['tf', 'tf'],
|
|
33
|
+
['ft', 'ft'],
|
|
34
|
+
])('it should work with each', coreMarbles(({ expect, coldBoolean }, a, b) => {
|
|
35
|
+
expect(coldBoolean(a)).toBeObservableBoolean(b);
|
|
36
|
+
}));
|