Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 32 additions & 2 deletions src/plugins/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,14 +180,34 @@ describe('actions', () => {
});

describe('plugins are accessible in events triggered from moves', () => {
const plugins = [
{
name: 'test',

setup: () => ({
initial: true,
}),

flush: () => ({ initial: true }),

api: ({ data }) => {
return {
get: () => data.initial,
};
},
},
];

test('turn/onBegin', () => {
const game = {
plugins,
moves: {
stop: (G, ctx) => ctx.events.endTurn(),
},
turn: {
onBegin: (G, ctx) => {
G.onBegin = ctx.random.Die(1);
G.test = ctx.test.get();
},
},
};
Expand All @@ -196,17 +216,20 @@ describe('plugins are accessible in events triggered from moves', () => {
client.moves.stop();
expect(client.getState().G).toEqual({
onBegin: 1,
test: true,
});
});

test('turn/onEnd', () => {
const game = {
plugins,
moves: {
stop: (G, ctx) => ctx.events.endTurn(),
},
turn: {
onEnd: (G, ctx) => {
G.onEnd = ctx.random.Die(1);
G.test = ctx.test.get();
},
},
};
Expand All @@ -215,11 +238,13 @@ describe('plugins are accessible in events triggered from moves', () => {
client.moves.stop();
expect(client.getState().G).toEqual({
onEnd: 1,
test: true,
});
});

test('phase/onBegin', () => {
const game = {
plugins,
moves: {
stop: (G, ctx) => ctx.events.setPhase('second'),
},
Expand All @@ -229,7 +254,8 @@ describe('plugins are accessible in events triggered from moves', () => {
},
second: {
onBegin: (G, ctx) => {
G.onEnd = ctx.random.Die(1);
G.onBegin = ctx.random.Die(1);
G.test = ctx.test.get();
},
},
},
Expand All @@ -238,12 +264,14 @@ describe('plugins are accessible in events triggered from moves', () => {
const client = Client({ game });
client.moves.stop();
expect(client.getState().G).toEqual({
onEnd: 1,
onBegin: 1,
test: true,
});
});

test('phase/onEnd', () => {
const game = {
plugins,
moves: {
stop: (G, ctx) => ctx.events.endPhase(),
},
Expand All @@ -252,6 +280,7 @@ describe('plugins are accessible in events triggered from moves', () => {
start: true,
onEnd: (G, ctx) => {
G.onEnd = ctx.random.Die(1);
G.test = ctx.test.get();
},
},
},
Expand All @@ -261,6 +290,7 @@ describe('plugins are accessible in events triggered from moves', () => {
client.moves.stop();
expect(client.getState().G).toEqual({
onEnd: 1,
test: true,
});
});
});
88 changes: 43 additions & 45 deletions src/plugins/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,8 @@ interface PluginOpts {
/**
* List of plugins that are always added.
*/
const DEFAULT_PLUGINS = [
PluginImmer,
PluginRandom,
PluginEvents,
PluginLog,
PluginSerializable,
];
const CORE_PLUGINS = [PluginImmer, PluginRandom, PluginLog, PluginSerializable];
const DEFAULT_PLUGINS = [...CORE_PLUGINS, PluginEvents];

/**
* Allow plugins to intercept actions and process them.
Expand Down Expand Up @@ -172,47 +167,50 @@ export const Enhance = (
* Allows plugins to update their state after a move / event.
*/
export const Flush = (state: State, opts: PluginOpts): State => {
// Note that we flush plugins in reverse order, to make sure that plugins
// that come before in the chain are still available.
[...DEFAULT_PLUGINS, ...opts.game.plugins].reverse().forEach((plugin) => {
const name = plugin.name;
const pluginState = state.plugins[name] || { data: {} };
// We flush the events plugin first, then custom plugins and the core plugins.
// This means custom plugins cannot use the events API but will be available in event hooks.
// Note that plugins are flushed in reverse, to allow custom plugins calling each other.
[...CORE_PLUGINS, ...opts.game.plugins, PluginEvents]
.reverse()
.forEach((plugin) => {
const name = plugin.name;
const pluginState = state.plugins[name] || { data: {} };

if (plugin.flush) {
const newData = plugin.flush({
G: state.G,
ctx: state.ctx,
game: opts.game,
api: pluginState.api,
data: pluginState.data,
});
if (plugin.flush) {
const newData = plugin.flush({
G: state.G,
ctx: state.ctx,
game: opts.game,
api: pluginState.api,
data: pluginState.data,
});

state = {
...state,
plugins: {
...state.plugins,
[plugin.name]: { data: newData },
},
};
} else if (plugin.dangerouslyFlushRawState) {
state = plugin.dangerouslyFlushRawState({
state,
game: opts.game,
api: pluginState.api,
data: pluginState.data,
});
state = {
...state,
plugins: {
...state.plugins,
[plugin.name]: { data: newData },
},
};
} else if (plugin.dangerouslyFlushRawState) {
state = plugin.dangerouslyFlushRawState({
state,
game: opts.game,
api: pluginState.api,
data: pluginState.data,
});

// Remove everything other than data.
const data = state.plugins[name].data;
state = {
...state,
plugins: {
...state.plugins,
[plugin.name]: { data },
},
};
}
});
// Remove everything other than data.
const data = state.plugins[name].data;
state = {
...state,
plugins: {
...state.plugins,
[plugin.name]: { data },
},
};
}
});

return state;
};
Expand Down