From 6850b76da71ba1a5e880c5fc03976f8f801fc30e Mon Sep 17 00:00:00 2001 From: Diogo Doreto Date: Thu, 15 Feb 2024 11:22:47 +0100 Subject: [PATCH] Add support for grouping conditions with and/or operators --- src/includeConditionalArg.test.ts | 148 +++++++++++++++++++++++++++++- src/includeConditionalArg.ts | 18 +++- src/story.ts | 4 +- 3 files changed, 162 insertions(+), 8 deletions(-) diff --git a/src/includeConditionalArg.test.ts b/src/includeConditionalArg.test.ts index 3da863e..1491f2c 100644 --- a/src/includeConditionalArg.test.ts +++ b/src/includeConditionalArg.test.ts @@ -55,10 +55,14 @@ describe('testValue', () => { describe('includeConditionalArg', () => { describe('errors', () => { - it('should throw if neither arg nor global is specified', () => { - expect(() => - includeConditionalArg({ if: {} as Conditional }, {}, {}) - ).toThrowErrorMatchingInlineSnapshot(`"Invalid conditional value {}"`); + it.each([ + ['empty if', { if: {} as Conditional }], + ['empty and', { if: { and: [{} as Conditional] } }], + ['empty or', { if: { or: [{} as Conditional] } }], + ])('should throw if neither arg, global, and nor or is specified; %s', (_name, argType) => { + expect(() => includeConditionalArg(argType, {}, {})).toThrowErrorMatchingInlineSnapshot( + `"Invalid conditional value {}"` + ); }); it('should throw if arg and global are both specified', () => { expect(() => @@ -151,4 +155,140 @@ describe('includeConditionalArg', () => { }); }); }); + describe('and/or collections', () => { + describe('and', () => { + it.each([ + ['and false', { if: { and: [{ global: 'a' }, { global: 'b' }] } }, {}, { a: 1 }, false], + ['and true', { if: { and: [{ global: 'a' }, { global: 'b' }] } }, {}, { a: 1, b: 1 }, true], + [ + 'mix args and globals', + { if: { and: [{ arg: 'a' }, { global: 'b' }] } }, + { a: 1 }, + { b: 1 }, + true, + ], + ])('%s', (_name, argType, args, globals, expected) => { + expect(includeConditionalArg(argType, args, globals)).toBe(expected); + }); + }); + describe('or', () => { + it.each([ + ['or true', { if: { or: [{ global: 'a' }, { global: 'b' }] } }, {}, { a: 1 }, true], + ['or true', { if: { or: [{ global: 'a' }, { global: 'b' }] } }, {}, { c: 1 }, false], + [ + 'mix args and globals, check arg', + { if: { or: [{ arg: 'a' }, { global: 'b' }] } }, + { a: 1 }, + {}, + true, + ], + [ + 'mix args and globals, check global', + { if: { or: [{ arg: 'a' }, { global: 'b' }] } }, + {}, + { b: 1 }, + true, + ], + ])('%s', (_name, argType, args, globals, expected) => { + expect(includeConditionalArg(argType, args, globals)).toBe(expected); + }); + }); + describe('nesting', () => { + it.each([ + ['both true', { a: 1, b: 2 }, {}, true], + ['first false', { a: 0, b: 2 }, {}, false], + ['second false', { a: 1, b: 0 }, {}, false], + ['both false', { a: 0, b: 0 }, {}, false], + ])('and/or, %s', (_name, args, globals, expected) => { + expect( + includeConditionalArg( + { + if: { + and: [ + { + or: [ + { arg: 'a', eq: 1 }, + { arg: 'a', eq: 2 }, + ], + }, + { + or: [ + { arg: 'b', eq: 1 }, + { arg: 'b', eq: 2 }, + ], + }, + ], + }, + }, + args, + globals + ) + ).toBe(expected); + }); + + it.each([ + ['both true', { a: 1, b: 2 }, {}, true], + ['first true', { a: 0, b: 2 }, {}, true], + ['second true', { a: 1, b: 0 }, {}, true], + ['both false', { a: 0, b: 0 }, {}, false], + ])('or/or, %s', (_name, args, globals, expected) => { + expect( + includeConditionalArg( + { + if: { + or: [ + { + or: [ + { arg: 'a', eq: 1 }, + { arg: 'a', eq: 2 }, + ], + }, + { + or: [ + { arg: 'b', eq: 1 }, + { arg: 'b', eq: 2 }, + ], + }, + ], + }, + }, + args, + globals + ) + ).toBe(expected); + }); + + it.each([ + ['both true', { a: 0, b: 0 }, {}, true], + ['first false', { a: 1, b: 0 }, {}, false], + ['second false', { a: 0, b: 2 }, {}, false], + ['both false', { a: 1, b: 2 }, {}, false], + ])('and/and, %s', (_name, args, globals, expected) => { + expect( + includeConditionalArg( + { + if: { + and: [ + { + and: [ + { arg: 'a', neq: 1 }, + { arg: 'a', neq: 2 }, + ], + }, + { + and: [ + { arg: 'b', neq: 1 }, + { arg: 'b', neq: 2 }, + ], + }, + ], + }, + }, + args, + globals + ) + ).toBe(expected); + }); + }); + }); }); diff --git a/src/includeConditionalArg.ts b/src/includeConditionalArg.ts index 95dac87..2a9b109 100644 --- a/src/includeConditionalArg.ts +++ b/src/includeConditionalArg.ts @@ -32,9 +32,21 @@ export const testValue = (cond: Omit, value: any) export const includeConditionalArg = (argType: InputType, args: Args, globals: Globals) => { if (!argType.if) return true; - const { arg, global } = argType.if as any; - if (count([arg, global]) !== 1) { - throw new Error(`Invalid conditional value ${JSON.stringify({ arg, global })}`); + const { arg, global, and, or } = argType.if as any; + if (count([arg, global, and, or]) !== 1) { + throw new Error(`Invalid conditional value ${JSON.stringify({ arg, global, and, or })}`); + } + + if (and) { + return and.every((condition: Conditional) => + includeConditionalArg({ if: condition }, args, globals) + ); + } + + if (or) { + return or.some((condition: Conditional) => + includeConditionalArg({ if: condition }, args, globals) + ); } const value = arg ? args[arg] : globals[global]; diff --git a/src/story.ts b/src/story.ts index 5841ddb..1da1073 100644 --- a/src/story.ts +++ b/src/story.ts @@ -36,7 +36,9 @@ export interface StrictParameters { type ConditionalTest = { truthy?: boolean } | { exists: boolean } | { eq: any } | { neq: any }; type ConditionalValue = { arg: string } | { global: string }; -export type Conditional = ConditionalValue & ConditionalTest; +type ConditionalGroup = { and: Conditional[] } | { or: Conditional[] }; +type ConditionalItem = ConditionalValue & ConditionalTest; +export type Conditional = ConditionalGroup | ConditionalItem; export interface InputType { name?: string; description?: string;