Skip to content

[PLAY-1918] Lodash removal 3 of 3 #4399

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

Merged
merged 22 commits into from
Mar 27, 2025
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react'
import { map } from 'lodash'
import { isEmpty, omitBy } from '../../utilities/object'
import { isEmpty, omitBy, map } from '../../utilities/object'

import Body from '../../pb_body/_body'
import Caption from '../../pb_caption/_caption'
Expand Down Expand Up @@ -46,12 +45,12 @@ const CurrentFilters = ({ dark, filters }: CurrentFiltersProps): React.ReactElem
dark={dark}
size={4}
tag="h4"
text={name}
text={`${name}`}
/> :
<div>
<Caption
dark={dark}
text={name}
text={`${name}`}
/>
<Title
dark={dark}
Expand Down
5 changes: 2 additions & 3 deletions playbook/app/pb_kits/playbook/pb_filter/Filter/SortMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { useState } from 'react'
import { map } from 'lodash'
import { find, partial } from '../../utilities/object'
import { find, partial, map } from '../../utilities/object'

import Button from '../../pb_button/_button'
import Icon from '../../pb_icon/_icon'
Expand All @@ -27,7 +26,7 @@ const directionIcon = (dir: Direction) => (

const renderOptions = (options: SortOptions, value: SortValue[], handleChange: (arg0: SortValue) => void) => (
map(options, (label, name) => {
const next = nextValue(value, name)
const next = nextValue(value, String(name))
return (
<ListItem key={`option-${next.name}-${next.dir}`}>
<Button
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import PbEnhancedElement from '../pb_enhanced_element'
import { debounce } from 'lodash'
import { debounce } from '../utilities/object'

// Kit selectors
const KIT_SELECTOR = '[class^="pb_"][class*="_kit"]'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { debounce } from 'lodash'
import { debounce } from "../../utilities/object"
import { useCallback, useMemo, useState } from 'react'

export default function useVisibility(initialState = false) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import {
buildDataProps,
buildHtmlProps,
} from "../utilities/props";
import { cloneDeep } from "../utilities/object";

import Icon from "../pb_icon/_icon";
import FormPill from "../pb_form_pill/_form_pill";
import Body from "../pb_body/_body";
import Caption from "../pb_caption/_caption";
import { cloneDeep } from "lodash";
import MultiLevelSelectOptions from "./multi_level_select_options";
import MultiLevelSelectContext from "./context";

Expand Down
4 changes: 2 additions & 2 deletions playbook/app/pb_kits/playbook/pb_typeahead/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import PbEnhancedElement from '../pb_enhanced_element'
import { debounce } from 'lodash'
import { debounce } from '../utilities/object'

export default class PbTypeahead extends PbEnhancedElement {
_searchInput: HTMLInputElement
Expand Down Expand Up @@ -284,4 +284,4 @@ export default class PbTypeahead extends PbEnhancedElement {
if (visible) visibilityProperty = '1'
this.resultsLoadingIndicator.style.opacity = visibilityProperty
}
}
}
2 changes: 1 addition & 1 deletion playbook/app/pb_kits/playbook/utilities/globalProps.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { omit } from 'lodash'
import { omit } from './object'
import { camelToSnakeCase } from './text'

import {
Expand Down
150 changes: 149 additions & 1 deletion playbook/app/pb_kits/playbook/utilities/object.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isEmpty, get, isString, uniqueId, omitBy, noop, merge, filter, find, partial } from './object';
import { isEmpty, get, isString, uniqueId, omitBy, noop, merge, filter, find, partial, map, cloneDeep, omit, debounce } from './object';

describe('Lodash functions', () => {
describe('isEmpty', () => {
Expand Down Expand Up @@ -234,4 +234,152 @@ describe('Lodash functions', () => {
expect(joinPartial('b', 'c')).toBe('a_b_c');
});
});

describe('map', () => {
test('maps over an array with a function iteratee', () => {
const arr = [1, 2, 3];
const result = map(arr, (num) => num * 2);
expect(result).toEqual([2, 4, 6]);
});

test('maps over an array with a string iteratee', () => {
const arr = [{ value: 1 }, { value: 2 }, { value: 3 }];
const result = map(arr, 'value');
expect(result).toEqual([1, 2, 3]);
});

test('maps over an object with a function iteratee', () => {
const obj = { a: 1, b: 2, c: 3 };
const result = map(obj, (val, key) => key + val);
expect(result.sort()).toEqual(['a1', 'b2', 'c3'].sort());
});

test('maps over an object with a string iteratee', () => {
const obj = {
one: { num: 1 },
two: { num: 2 },
three: { num: 3 },
};
const result = map(obj, 'num');
expect(result.sort()).toEqual([1, 2, 3].sort());
});

test('returns original values if no iteratee provided', () => {
const arr = [1, 2, 3];
const result = map(arr);
expect(result).toEqual([1, 2, 3]);
});
});

describe('cloneDeep', () => {
test('clones primitive values', () => {
expect(cloneDeep(42)).toBe(42);
expect(cloneDeep('test')).toBe('test');
expect(cloneDeep(null)).toBe(null);
});

test('clones arrays deeply', () => {
const arr = [1, [2, 3]];
const cloned = cloneDeep(arr);
expect(cloned).toEqual(arr);
cloned[1][0] = 99;
expect(arr[1][0]).toBe(2);
});

test('clones objects deeply', () => {
const obj = { a: { b: 2 } };
const cloned = cloneDeep(obj);
expect(cloned).toEqual(obj);
cloned.a.b = 99;
expect(obj.a.b).toBe(2);
});

test('clones Date objects', () => {
const date = new Date();
const cloned = cloneDeep(date);
expect(cloned).not.toBe(date);
expect(cloned.getTime()).toBe(date.getTime());
});

test('clones RegExp objects', () => {
const regex = /test/gi;
const cloned = cloneDeep(regex);
expect(cloned).not.toBe(regex);
expect(cloned.source).toBe(regex.source);
expect(cloned.flags).toBe(regex.flags);
});
});

describe('omit', () => {
test('omits specified keys from object', () => {
const obj = { a: 1, b: 2, c: 3 };
expect(omit(obj, 'a', 'c')).toEqual({ b: 2 });
});

test('supports array of keys to omit', () => {
const obj = { a: 1, b: 2, c: 3 };
expect(omit(obj, ['b'])).toEqual({ a: 1, c: 3 });
});

test('returns empty object for null or non-object input', () => {
expect(omit(null, 'a')).toEqual({});
expect(omit("string", 'a')).toEqual({});
});

test('returns original object if no keys match', () => {
const obj = { a: 1, b: 2 };
expect(omit(obj, 'c')).toEqual({ a: 1, b: 2 });
});
});

describe('debounce', () => {
beforeEach(() => {
jest.useFakeTimers();
});

afterEach(() => {
jest.useRealTimers();
});

test('delays execution until wait time has passed', () => {
const func = jest.fn();
const debounced = debounce(func, 1000);
debounced();
expect(func).not.toHaveBeenCalled();
jest.advanceTimersByTime(500);
expect(func).not.toHaveBeenCalled();
jest.advanceTimersByTime(500);
expect(func).toHaveBeenCalledTimes(1);
});

test('calls function only once when called repeatedly', () => {
const func = jest.fn();
const debounced = debounce(func, 1000);
debounced();
debounced();
debounced();
jest.advanceTimersByTime(1000);
expect(func).toHaveBeenCalledTimes(1);
});

test('immediate option calls function on first call', () => {
const func = jest.fn();
const debounced = debounce(func, 1000, true);
debounced();
expect(func).toHaveBeenCalledTimes(1);
debounced();
debounced();
jest.advanceTimersByTime(1000);
expect(func).toHaveBeenCalledTimes(1);
});

test('subsequent call after wait period works with immediate option', () => {
const func = jest.fn();
const debounced = debounce(func, 1000, true);
debounced();
jest.advanceTimersByTime(1100);
debounced();
expect(func).toHaveBeenCalledTimes(2);
});
});
});
Loading
Loading