Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add conditional arg types and metadata #36

Merged
merged 31 commits into from
Oct 22, 2022
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
789b78e
Add conditional arg types and metadata
shilman Feb 18, 2022
675f6a9
Add tests
shilman Feb 18, 2022
507502b
Rename includeIf/excludeIf to addIf/removeIf
shilman Feb 24, 2022
ff29134
Richer conditional args
shilman Apr 5, 2022
d38f8f1
Fix build
shilman Apr 5, 2022
92ac0db
Implicit test for truthiness
shilman Apr 5, 2022
161cb84
Add truthy test to conditional args
shilman Apr 5, 2022
7c6c115
Don't test dist dir
shilman Apr 5, 2022
4566f4d
Re-apply `TArgs` to CSF `render` type
tmeasday Apr 12, 2022
1e215f6
Add no arg/global specified test
shilman Apr 12, 2022
d3836e7
Add step to play context and `runStep` to project annotations
tmeasday Jul 8, 2022
80f2fb8
Update re: @ghengeveld
tmeasday Jul 9, 2022
0899bb7
Need to parameterize `runStep`
tmeasday Jul 9, 2022
f3e3044
Step functions are allowed to be async of course
tmeasday Jul 9, 2022
ecfc79e
Merge branch '6-4-new-types' into feat/conditional-args
shilman Oct 10, 2022
a3d8c69
Merge branch 'feat/conditional-args' into 41-tighten-render-type
shilman Oct 10, 2022
3bd2bd7
Merge branch '41-tighten-render-type' into add-step
shilman Oct 10, 2022
3049387
SB-738: Sound arg types
kasperpeulen Oct 4, 2022
2e05407
Use HKT's instead
kasperpeulen Oct 6, 2022
9a29dd4
update the test
kasperpeulen Oct 6, 2022
fbf3c7a
explain usage for higher kinded T
kasperpeulen Oct 6, 2022
9d84710
make T optional
kasperpeulen Oct 6, 2022
b089ae5
rename TArgsAnnotations -> TRequiredArgs
kasperpeulen Oct 10, 2022
258942b
fix subcomponents regression
kasperpeulen Oct 10, 2022
768ca23
ArgsFromMeta utility and generic ArgsStoryFn RT
kasperpeulen Oct 18, 2022
23af5af
use tsup and upgrade dependencies
kasperpeulen Oct 18, 2022
26e6539
add type test for ArgsFromMeta
kasperpeulen Oct 19, 2022
084d9eb
Merge pull request #51 from ComponentDriven/kasper/csf-3-improvements
shilman Oct 22, 2022
65292c2
Merge pull request #49 from ComponentDriven/kasper/sb-738/csf-3
shilman Oct 22, 2022
47fcdb1
Merge pull request #48 from ComponentDriven/add-step
shilman Oct 22, 2022
5edcaf6
Merge pull request #43 from ComponentDriven/41-tighten-render-type
shilman Oct 22, 2022
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
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@
},
"prettier": "@storybook/linter-config/prettier.config",
"jest": {
"testEnvironment": "node"
"testEnvironment": "node",
"roots": [
"<rootDir>/src"
]
},
"dependencies": {
"lodash": "^4.17.15"
Expand Down
167 changes: 167 additions & 0 deletions src/includeConditionalArg.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/* eslint-disable @typescript-eslint/ban-ts-ignore */
import { includeConditionalArg, testValue } from './includeConditionalArg';

describe('testValue', () => {
describe('truthy', () => {
it.each([
['implicit true', {}, true, true],
['implicit truthy', {}, 1, true],
['implicit falsey', {}, 0, false],
['truthy true', { truthy: true }, true, true],
['truthy truthy', { truthy: true }, 1, true],
['truthy falsey', { truthy: true }, 0, false],
['falsey true', { truthy: false }, true, false],
['falsey truthy', { truthy: false }, 1, false],
['falsey falsey', { truthy: false }, 0, true],
])('%s', (_name, cond, value, expected) => {
// @ts-ignore
expect(testValue(cond, value)).toBe(expected);
});
});
describe('exists', () => {
it.each([
['exist', { exists: true }, 1, true],
['exist false', { exists: true }, undefined, false],
['nexist', { exists: false }, undefined, true],
['nexist false', { exists: false }, 1, false],
])('%s', (_name, cond, value, expected) => {
// @ts-ignore
expect(testValue(cond, value)).toBe(expected);
});
});
describe('eq', () => {
it.each([
['true', { eq: 1 }, 1, true],
['false', { eq: 1 }, 2, false],
['undefined', { eq: undefined }, undefined, false],
['undefined false', { eq: 1 }, undefined, false],
['object true', { eq: { x: 1 } }, { x: 1 }, true],
['object true', { eq: { x: 1 } }, { x: 2 }, false],
])('%s', (_name, cond, value, expected) => {
// @ts-ignore
expect(testValue(cond, value)).toBe(expected);
});
});
describe('neq', () => {
it.each([
['true', { neq: 1 }, 2, true],
['false', { neq: 1 }, 1, false],
['undefined true', { neq: 1 }, undefined, true],
['undefined false', { neq: undefined }, undefined, false],
['object true', { neq: { x: 1 } }, { x: 2 }, true],
['object false', { neq: { x: 1 } }, { x: 1 }, false],
])('%s', (_name, cond, value, expected) => {
// @ts-ignore
expect(testValue(cond, value)).toBe(expected);
});
});
});

describe('includeConditionalArg', () => {
describe('errors', () => {
it('should throw if arg and global are both specified', () => {
expect(() =>
includeConditionalArg({ if: { arg: 'a', global: 'b' } }, {}, {})
).toThrowErrorMatchingInlineSnapshot(
`"Invalid conditional value {\\"arg\\":\\"a\\",\\"global\\":\\"b\\"}"`
);
});
it('should throw if mulitiple exists / eq / neq are specified', () => {
expect(() =>
includeConditionalArg({ if: { arg: 'a', exists: true, eq: 1 } }, {}, {})
).toThrowErrorMatchingInlineSnapshot(
`"Invalid conditional test {\\"exists\\":true,\\"eq\\":1}"`
);

expect(() =>
includeConditionalArg({ if: { arg: 'a', exists: false, neq: 0 } }, {}, {})
).toThrowErrorMatchingInlineSnapshot(
`"Invalid conditional test {\\"exists\\":false,\\"neq\\":0}"`
);

expect(() =>
includeConditionalArg({ if: { arg: 'a', eq: 1, neq: 0 } }, {}, {})
).toThrowErrorMatchingInlineSnapshot(`"Invalid conditional test {\\"eq\\":1,\\"neq\\":0}"`);
});
});

describe('args', () => {
describe('implicit', () => {
it.each([
['implicit true', { if: { arg: 'a' } }, { a: 1 }, {}, true],
['truthy true', { if: { arg: 'a', truthy: true } }, { a: 0 }, {}, false],
['truthy false', { if: { arg: 'a', truthy: false } }, {}, {}, true],
])('%s', (_name, argType, args, globals, expected) => {
// @ts-ignore
expect(includeConditionalArg(argType, args, globals)).toBe(expected);
});
});
describe('exists', () => {
it.each([
['exist', { if: { arg: 'a', exists: true } }, { a: 1 }, {}, true],
['exist false', { if: { arg: 'a', exists: true } }, {}, {}, false],
])('%s', (_name, argType, args, globals, expected) => {
// @ts-ignore
expect(includeConditionalArg(argType, args, globals)).toBe(expected);
});
});
describe('eq', () => {
it.each([
['scalar true', { if: { arg: 'a', eq: 1 } }, { a: 1 }, {}, true],
['scalar false', { if: { arg: 'a', eq: 1 } }, { a: 2 }, { a: 1 }, false],
])('%s', (_name, argType, args, globals, expected) => {
// @ts-ignore
expect(includeConditionalArg(argType, args, globals)).toBe(expected);
});
});
describe('neq', () => {
it.each([
['scalar true', { if: { arg: 'a', neq: 1 } }, { a: 2 }, {}, true],
['scalar false', { if: { arg: 'a', neq: 1 } }, { a: 1 }, { a: 2 }, false],
])('%s', (_name, argType, args, globals, expected) => {
// @ts-ignore
expect(includeConditionalArg(argType, args, globals)).toBe(expected);
});
});
});
describe('globals', () => {
describe('truthy', () => {
it.each([
['implicit true', { if: { global: 'a' } }, {}, { a: 1 }, true],
['implicit undefined', { if: { global: 'a' } }, {}, {}, false],
['truthy true', { if: { global: 'a', truthy: true } }, {}, { a: 0 }, false],
['truthy false', { if: { global: 'a', truthy: false } }, {}, { a: 0 }, true],
])('%s', (_name, argType, args, globals, expected) => {
// @ts-ignore
expect(includeConditionalArg(argType, args, globals)).toBe(expected);
});
});
describe('exists', () => {
it.each([
['implicit exist true', { if: { global: 'a', exists: true } }, {}, { a: 1 }, true],
['implicit exist false', { if: { global: 'a', exists: true } }, { a: 1 }, {}, false],
])('%s', (_name, argType, args, globals, expected) => {
// @ts-ignore
expect(includeConditionalArg(argType, args, globals)).toBe(expected);
});
});
describe('eq', () => {
it.each([
['scalar true', { if: { global: 'a', eq: 1 } }, {}, { a: 1 }, true],
['scalar false', { if: { arg: 'a', eq: 1 } }, { a: 2 }, { a: 1 }, false],
])('%s', (_name, argType, args, globals, expected) => {
// @ts-ignore
expect(includeConditionalArg(argType, args, globals)).toBe(expected);
});
});
describe('neq', () => {
it.each([
['scalar true', { if: { global: 'a', neq: 1 } }, {}, { a: 2 }, true],
['scalar false', { if: { global: 'a', neq: 1 } }, { a: 2 }, { a: 1 }, false],
])('%s', (_name, argType, args, globals, expected) => {
// @ts-ignore
expect(includeConditionalArg(argType, args, globals)).toBe(expected);
});
});
});
});
39 changes: 39 additions & 0 deletions src/includeConditionalArg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import isEqual from 'lodash/isEqual';
import { Args, Globals, InputType, Conditional } from './story';

const count = (vals: any[]) => vals.map(v => typeof v !== 'undefined').filter(Boolean).length;

export const testValue = (cond: Omit<Conditional, 'arg' | 'global'>, value: any) => {
const { exists, eq, neq, truthy } = cond as any;
if (count([exists, eq, neq, truthy]) > 1) {
throw new Error(`Invalid conditional test ${JSON.stringify({ exists, eq, neq })}`);
}
if (typeof eq !== 'undefined') {
return isEqual(value, eq);
}
if (typeof neq !== 'undefined') {
return !isEqual(value, neq);
}
if (typeof exists !== 'undefined') {
const valueExists = typeof value !== 'undefined';
return exists ? valueExists : !valueExists;
}
const shouldBeTruthy = typeof truthy === 'undefined' ? true : truthy;
return shouldBeTruthy ? !!value : !value;
};

/**
* Helper function to include/exclude an arg based on the value of other other args
* aka "conditional args"
*/
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 value = arg ? args[arg] : globals[global];
return testValue(argType.if!, value);
};
1 change: 1 addition & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/ban-ts-ignore */
import { toId, storyNameFromExport, isExportStory } from '.';

describe('toId', () => {
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,5 @@ export const parseKind = (kind: string, { rootSeparator, groupSeparator }: Separ
};
};

export { includeConditionalArg } from './includeConditionalArg';
export * from './story';
8 changes: 6 additions & 2 deletions src/story.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,15 @@ export interface StoryIdentifier {

export type Parameters = { [name: string]: any };

type ConditionalTest = { truthy?: boolean } | { exists: boolean } | { eq: any } | { neq: any };
type ConditionalValue = { arg: string } | { global: string };
export type Conditional = ConditionalValue & ConditionalTest;
export interface InputType {
name?: string;
description?: string;
defaultValue?: any;
type?: SBType | SBScalarType['name'];
if?: Conditional;
[key: string]: any;
}

Expand Down Expand Up @@ -260,7 +264,7 @@ export type ComponentAnnotations<
*
* By defining them each component will have its tab in the args table.
*/
subcomponents?: Record<string, TFramework['component']>;
subcomponents?: Record<string, TFramework['component']>;
};

export type StoryAnnotations<
Expand All @@ -276,7 +280,7 @@ export type StoryAnnotations<
* Override the display name in the UI (CSF v2)
*/
storyName?: StoryName;

/**
* Function that is executed after the story is rendered.
*/
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
"outDir": "dist"
},
"include": ["src"],
"exclude": ["dist"]
"exclude": ["src/**/*.test.ts", "dist"]
}