Skip to content

Commit

Permalink
Fix (core): makeTheme should perform deep merges of objects (#1403)
Browse files Browse the repository at this point in the history
* Fix: checkbox icon theming

* Fix (core): makeTheme should perform deep merges of objects

---------

Co-authored-by: Simon Boudrias <admin@simonboudrias.com>
  • Loading branch information
jeffwcx and SBoudrias committed May 13, 2024
1 parent 38c7981 commit 7f1f592
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 3 deletions.
53 changes: 53 additions & 0 deletions packages/checkbox/checkbox.test.mts
Expand Up @@ -672,6 +672,59 @@ describe('checkbox prompt', () => {
await expect(answer).resolves.toEqual([1]);
});

describe('theme: icon', () => {
it('checked/unchecked', async () => {
const { answer, events, getScreen } = await render(checkbox, {
message: 'Select a number',
choices: numberedChoices,
theme: {
icon: {
checked: '√',
unchecked: 'x',
},
},
});
events.keypress('space');
expect(getScreen()).toMatchInlineSnapshot(`
"? Select a number
❯√ 1
x 2
x 3
x 4
x 5
x 6
x 7"
`);
events.keypress('enter');
await answer;
});

it('cursor', async () => {
const { answer, events, getScreen } = await render(checkbox, {
message: 'Select a number',
choices: numberedChoices,
theme: {
icon: {
cursor: '>',
},
},
});
events.keypress('space');
expect(getScreen()).toMatchInlineSnapshot(`
"? Select a number
>◉ 1
◯ 2
◯ 3
◯ 4
◯ 5
◯ 6
◯ 7"
`);
events.keypress('enter');
await answer;
});
});

describe('theme: style.renderSelectedChoices', () => {
it('renderSelectedChoices', async () => {
const { answer, events, getScreen } = await render(checkbox, {
Expand Down
36 changes: 33 additions & 3 deletions packages/core/src/lib/make-theme.mts
@@ -1,10 +1,40 @@
import type { Prettify, PartialDeep } from '@inquirer/type';
import { defaultTheme, type Theme } from './theme.mjs';

function isPlainObject(value: unknown): value is object {
if (typeof value !== 'object' || value === null) return false;

let proto = value;
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto);
}

return Object.getPrototypeOf(value) === proto;
}

function deepMerge<T extends object>(...objects: Partial<T>[]): T {
const output: { [key: string]: unknown } = {};

for (const obj of objects) {
for (const [key, value] of Object.entries(obj)) {
const prevValue = output[key];

output[key] =
isPlainObject(prevValue) && isPlainObject(value)
? deepMerge(prevValue, value)
: value;
}
}

return output as T;
}

export function makeTheme<SpecificTheme extends object>(
...themes: ReadonlyArray<undefined | PartialDeep<Theme<SpecificTheme>>>
): Prettify<Theme<SpecificTheme>> {
return Object.assign({}, defaultTheme, ...themes, {
style: Object.assign({}, defaultTheme.style, ...themes.map((theme) => theme?.style)),
});
const themesToMerge = [
defaultTheme,
...themes.filter((theme) => theme != null),
] as Theme<SpecificTheme>[];
return deepMerge(...themesToMerge);
}

0 comments on commit 7f1f592

Please sign in to comment.