Skip to content

Commit

Permalink
Adds additional tests
Browse files Browse the repository at this point in the history
  • Loading branch information
bezoerb committed Nov 12, 2021
1 parent 85cb313 commit b61c201
Show file tree
Hide file tree
Showing 5 changed files with 297 additions and 71 deletions.
1 change: 1 addition & 0 deletions src/__test__/mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export const getContent = async () => {

export const getConfig = (fixture: Partial<Config> = {}): Config => {
return {
plugins:[],
...fixture,
} as Config;
};
Expand Down
151 changes: 151 additions & 0 deletions src/helper/hook-manager.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import type { Config, RuntimeContext } from '../types.js';
import { getRuntimeContext, getTransformContext, getConfig } from '../__test__/mock.js';
import { HookManager } from './hook-manager';

const getInstance = (overwrite: Partial<Config> = {}, context: Partial<RuntimeContext> = {}) => {
const config = getConfig(overwrite);
const runtimeContext = getRuntimeContext({ config, ...context });

return new HookManager(runtimeContext, config);
};

describe('Hook Manager', () => {
const transformContext = getTransformContext();

test('No Hooks & plugins', async () => {
const hooks = getInstance();

// We need to check the result as each hook modifies the runtime context
// and so the second one also alters the result of the first one
const runtimeUndefined = await hooks.before();
expect(runtimeUndefined).toEqual(hooks.runtimeContext);
const runtimeDefault = await hooks.before({test:true});
expect(runtimeDefault).toEqual({...hooks.runtimeContext, test:true});

const transformUndefined = await hooks.mapDirectory(getTransformContext());
expect(transformUndefined).toBeUndefined();
const transformDefault = await hooks.mapDirectory(getTransformContext(), 'defaultValue');
expect(transformDefault).toEqual('defaultValue');
});

test('Runs before & after hooks in correct order', async () => {
const hook = (value) => (context) => ({ ...context, test: [...(context.test || []), value] });

const hooks = getInstance(
{
before: hook(5),
after: hook(10),
plugins: [
{ before: hook(1), after: hook(6) },
{ before: hook(2), after: hook(7) },
{ before: hook(3), after: hook(8) },
{ before: hook(4), after: hook(9) },
],
},
{ test: [0] }
);

const ctxBefore = await hooks.before();
const ctxAfter = await hooks.after();

expect(ctxBefore.test).toEqual([0, 1, 2, 3, 4, 5]);
expect(ctxAfter.test).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
});

test('Runs async before & after hooks in correct order', async () => {
const hook = (value) => async (context) => ({
...context,
test: [...(context.test || []), value],
});

const hooks = getInstance(
{
before: hook(5),
after: hook(9),
plugins: [
{ before: hook(1), after: hook(6) },
{ before: hook(2), after: hook(7) },
{ before: hook(3) },
{ before: hook(4), after: hook(8) },
],
},
{ test: [0] }
);

const ctxBefore = await hooks.before();
const ctxAfter = await hooks.after();

expect(ctxBefore.test).toEqual([0, 1, 2, 3, 4, 5]);
expect(ctxAfter.test).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
});

test('Passes transformContext.content to next transform call (default: object)', async () => {
const asyncHook = (value) => async (context) => {
return { ...context.content, [value]: true };
};
const syncHook = (value) => (context) => {
return { ...context.content, [value]: true };
};

const hooks = getInstance(
{
transform: asyncHook('config'),
plugins: [
{ transform: syncHook('plugin1') },
{ transform: asyncHook('plugin2') },
{ transform: syncHook('plugin3') },
{ transform: asyncHook('plugin4') },
{ transform: syncHook('plugin5') },
],
},
{}
);

const result = await hooks.transform(transformContext, {});

expect(result).toEqual({
config: true,
plugin1: true,
plugin2: true,
plugin3: true,
plugin4: true,
plugin5: true,
});
});

test('Runs hooks', async () => {
const hooks = getInstance(
{
mapDirectory: (tc,rc,prev) => `wrapped-${prev}`,
mapFilename: (tc,rc,prev) => `wrapped-${prev}`,
mapMetaFields: (tc,rc,prev) => ({...prev, wrapped: true}),
mapAssetLink: (tc,rc,prev) => ({...prev, wrapped: true}),
mapEntryLink: (tc,rc,prev) => ({...prev, wrapped: true}),
plugins: [
{ mapDirectory: (tc,rc,prev) => `${prev || ''}plugin-1-directory: ${tc.value}` },
{ mapFilename: (tc,rc,prev) => `${prev || ''}plugin-2-filename: ${tc.value}` },
{ mapMetaFields: (tc,rc,prev) => ({...prev, mapMetaFields: `plugin-3: ${tc.value}`}) },
{ mapAssetLink: (tc,rc,prev) => ({...prev, mapAssetLink: `plugin-4: ${tc.value}`}) },
{ mapEntryLink: (tc,rc,prev) => ({...prev, mapEntryLink: `plugin-5: ${tc.value}`}) },
{ mapDirectory: (tc,rc,prev) => prev.replace(tc.value, `plugin-6-directory: ${tc.value}`)},
],
},
{}
);

transformContext.value = 'tc-value';

const mapDirectory = await hooks.mapDirectory(transformContext);
const mapFilename = await hooks.mapFilename(transformContext,'default-');
const mapMetaFields = await hooks.mapMetaFields(transformContext, () => ({initial: true}));
const mapAssetLink = await hooks.mapAssetLink(transformContext, {initial: true});
const mapEntryLink = await hooks.mapEntryLink(transformContext, async () => Promise.resolve({initial: true}));

expect(mapDirectory).toEqual('wrapped-plugin-1-directory: plugin-6-directory: tc-value');
expect(mapFilename).toEqual('wrapped-default-plugin-2-filename: tc-value');
expect(mapMetaFields).toEqual({ initial: true, mapMetaFields: 'plugin-3: tc-value', wrapped: true });
expect(mapAssetLink).toEqual({ initial: true, mapAssetLink: 'plugin-4: tc-value', wrapped: true });
expect(mapEntryLink).toEqual({ initial: true, mapEntryLink: 'plugin-5: tc-value', wrapped: true });

});
});
116 changes: 71 additions & 45 deletions src/helper/hook-manager.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,77 @@

import type {KeyValueMap, Config, RuntimeHook, Hooks, RuntimeContext, TransformContext, TransformHook} from '../types.js';
import {reduceAsync} from './array.js';
import type {
KeyValueMap,
Config,
RuntimeHook,
Hooks,
RuntimeContext,
TransformContext,
TransformHook,
} from '../types.js';
import { reduceAsync } from './array.js';

// eslint-disable-next-line @typescript-eslint/no-unsafe-call
const resolveValue = (something: any, ...args: any[]): unknown => (typeof something === 'function' ? something(...args) : something);

const hookUpRuntime = (name: keyof Pick<Hooks, 'before' | 'after'>, options: Config, defaultValue?: any): RuntimeHook => {
const {[name]: configHook} = options;
const resolveValue = (something: any, ...args: any[]): unknown =>
typeof something === 'function' ? something(...args) : something;

const hooks = (options.plugins).filter(plugin => Boolean(plugin[name])).map(plugin => plugin[name]);

if (hooks.length === 0 && !defaultValue) {
return;
}
const hookUpRuntime = (
name: keyof Pick<Hooks, 'before' | 'after'>,
options: Config,
defaultValue?: any
): RuntimeHook => {
const { [name]: configHook } = options;

if (typeof configHook === 'boolean') {
return configHook;
}
const hooks = (options.plugins || [])
.filter((plugin) => Boolean(plugin[name]))
.map((plugin) => plugin[name]);

return async (runtimeContext: RuntimeContext): Promise<Partial<RuntimeContext>> => {
const initialValue = await resolveValue(defaultValue, runtimeContext) as Partial<RuntimeContext>;
if (hooks.length === 0 && !defaultValue) {
return;
}
const initialValue = (await resolveValue(
defaultValue,
runtimeContext
)) as Partial<RuntimeContext>;

const value = await reduceAsync(
(hooks || []),
async (prev: Partial<RuntimeContext>, hook: RuntimeHook) => hook({...runtimeContext, ...prev}),
initialValue || {},
) as RuntimeContext;
const value = (await reduceAsync(
hooks || [],
async (prev: Partial<RuntimeContext>, hook: RuntimeHook) =>
hook({ ...runtimeContext, ...prev }),
initialValue || {}
)) as RuntimeContext;

if (typeof configHook === 'function') {
return (configHook)(value);
return configHook(value);
}

return value;
};
};

const hookUpTransform = <Type = unknown>(name: keyof Omit<Hooks, 'before' | 'after'>, options: Config, defaultValue?: any): TransformHook<Type> => {
const {[name]: configHook} = options;

const hooks = (options.plugins || []).filter(plugin => Boolean(plugin[name])).map(plugin => plugin[name]);

if (hooks.length === 0 && !defaultValue) {
return;
}

if (typeof configHook === 'boolean') {
return configHook;
}
const hookUpTransform = <Type = unknown>(
name: keyof Omit<Hooks, 'before' | 'after'>,
options: Config,
defaultValue?: any
): TransformHook<Type> => {
const { [name]: configHook } = options;

const hooks = (options.plugins || [])
.filter((plugin) => Boolean(plugin[name]))
.map((plugin) => plugin[name]);

return async (
transformContext: TransformContext,
runtimeContext: RuntimeContext
): Promise<Type> => {
if (hooks.length === 0 && !defaultValue) {
return;
}

return async (transformContext: TransformContext, runtimeContext: RuntimeContext): Promise<Type> => {
const initialValue = await resolveValue(defaultValue, transformContext, runtimeContext) as Type;
const initialValue = (await resolveValue(
defaultValue,
transformContext,
runtimeContext
)) as Type;

const value = await reduceAsync(
hooks || [],
Expand All @@ -60,9 +82,13 @@ const hookUpTransform = <Type = unknown>(name: keyof Omit<Hooks, 'before' | 'aft

return hook(transformContext, runtimeContext, prev);
},
initialValue,
initialValue
);

if (name === 'transform') {
transformContext.content = value;
}

if (typeof configHook === 'function') {
return (configHook as TransformHook<Type>)(transformContext, runtimeContext, value);
}
Expand All @@ -80,23 +106,23 @@ export class HookManager {
}

has(key: keyof Hooks): boolean {
const {[key]: hook} = this.config;
const pluginHooks = (this.config.plugins || []).some(plugin => Boolean(plugin[key]));
const { [key]: hook } = this.config;
const pluginHooks = (this.config.plugins || []).some((plugin) => Boolean(plugin[key]));

return Boolean(hook) && pluginHooks;
}

async before() {
const method = hookUpRuntime('before', this.config, {});
async before(defauleValue?: KeyValueMap) {
const method = hookUpRuntime('before', this.config, defauleValue);
const result = await method(this.runtimeContext);
this.runtimeContext = {...this.runtimeContext, ...(result || {})};
this.runtimeContext = { ...this.runtimeContext, ...(result || {}) };
return this.runtimeContext;
}

async after() {
const method = hookUpRuntime('after', this.config, {});
async after(defauleValue?: KeyValueMap) {
const method = hookUpRuntime('after', this.config, defauleValue);
const result = await method(this.runtimeContext);
this.runtimeContext = {...this.runtimeContext, ...(result || {})};
this.runtimeContext = { ...this.runtimeContext, ...(result || {}) };
return this.runtimeContext;
}

Expand Down
Loading

0 comments on commit b61c201

Please sign in to comment.