-
Notifications
You must be signed in to change notification settings - Fork 10
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
feat(utilities): Remove lodash dependency and replace it with custom functions #856
Changes from 17 commits
579f6ee
a5966f5
a7b2df6
3c54e13
a53ec6c
deb4816
e2da6e3
6070264
424711f
5b3c92a
63e58e7
b00112a
b4033df
0192040
939a74a
b0eae68
72c856e
f9d7971
18d6224
3deb754
41c4cfd
18406da
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"compilerOptions": { | ||
"module": "CommonJS", | ||
"target": "es2020", | ||
"checkJs": true | ||
}, | ||
"exclude": ["node_modules", ".yarn"] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import {debounce, isEqual} from '../lodash'; | ||
|
||
beforeEach(() => { | ||
jest.useFakeTimers(); | ||
}); | ||
|
||
describe('debounce', () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why describe? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is a way to organize the test suite. All According to jest doc:
And when you run the tests, they are shown with a pretty hierarchy There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't like it, and we don't use it anywhere. Organice the tests by file should be more than enough. And you already have a "debounce" or "isEqual" prefix in all your test names. It adds an extra nesting and can be tricky when combined with before/after methods There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't agree, but it also won't keep me awake. Removed the describe blocks |
||
test('debounce happy case', () => { | ||
const fn = jest.fn().mockImplementation((a) => a); | ||
const debounced = debounce(fn, 5000); | ||
|
||
debounced(1); | ||
jest.advanceTimersByTime(4500); | ||
debounced(2); | ||
jest.advanceTimersByTime(4500); | ||
debounced(3); | ||
expect(fn).not.toHaveBeenCalled(); | ||
|
||
jest.runAllTimers(); | ||
|
||
expect(fn).toHaveBeenCalledTimes(1); | ||
expect(fn.mock.calls).toEqual([[3]]); | ||
}); | ||
|
||
test('debounce with leading', () => { | ||
const fn = jest.fn().mockImplementation((a) => a); | ||
const debounced = debounce(fn, 5000, {leading: true}); | ||
|
||
debounced(1); | ||
expect(fn.mock.calls).toEqual([[1]]); | ||
|
||
debounced(2); | ||
debounced(3); | ||
expect(fn.mock.calls).toEqual([[1]]); | ||
|
||
jest.runAllTimers(); | ||
|
||
expect(fn).toHaveBeenCalledTimes(2); | ||
expect(fn.mock.calls).toEqual([[1], [3]]); | ||
}); | ||
|
||
test('debounce with maxWait', () => { | ||
const fn = jest.fn().mockImplementation((a) => a); | ||
const debounced = debounce(fn, 2500, {maxWait: 3000}); | ||
|
||
debounced(1); | ||
jest.advanceTimersByTime(2000); | ||
|
||
debounced(2); | ||
debounced(3); | ||
jest.advanceTimersByTime(2000); | ||
expect(fn.mock.calls).toEqual([[3]]); | ||
|
||
debounced(4); | ||
jest.runAllTimers(); | ||
|
||
expect(fn.mock.calls).toEqual([[3], [4]]); | ||
}); | ||
|
||
test('debounce cancel', () => { | ||
const fn = jest.fn().mockImplementation((a) => a); | ||
const debounced = debounce(fn, 5000); | ||
|
||
debounced(1); | ||
debounced(2); | ||
debounced(3); | ||
|
||
debounced.cancel(); | ||
|
||
jest.runAllTimers(); | ||
|
||
expect(fn).not.toHaveBeenCalled(); | ||
}); | ||
}); | ||
|
||
describe('isEqual', () => { | ||
const symbol = Symbol('abc'); | ||
|
||
test('isEqual happy case', () => { | ||
const a = { | ||
n: 123, | ||
s: 'abc', | ||
b: true, | ||
nul: null, | ||
und: undefined, | ||
arr: [1, false, null, undefined, new Date(1234567890), {a: 1, b: 2, c: 3}], | ||
date: new Date(1234567890), | ||
obj: {a: 1, b: 2, c: 3}, | ||
symbol, | ||
}; | ||
|
||
const b = { | ||
n: 123, | ||
s: 'abc', | ||
b: true, | ||
nul: null, | ||
und: undefined, | ||
arr: [1, false, null, undefined, new Date(1234567890), {a: 1, b: 2, c: 3}], | ||
date: new Date(1234567890), | ||
obj: {a: 1, b: 2, c: 3}, | ||
symbol, | ||
}; | ||
|
||
expect(isEqual(a, b)).toBe(true); | ||
}); | ||
|
||
test('isEqual with different primitives', () => { | ||
expect(isEqual(1, 2)).toBe(false); | ||
expect(isEqual('a', 'b')).toBe(false); | ||
expect(isEqual(true, false)).toBe(false); | ||
expect(isEqual(null, undefined)).toBe(false); | ||
expect(isEqual(symbol, Symbol('abc'))).toBe(false); | ||
expect(isEqual(new Date(1234567890), new Date(1234567891))).toBe(false); | ||
}); | ||
}); |
pladaria marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
/** | ||
* This file implements lodash alternatives used by components | ||
* | ||
* Importing the lodash library causes problems when building Mística as an ES module. | ||
* Importing lodash-es causes problems when running ssr tests (not sure why) | ||
* | ||
* Once Mística gets migrated to a proper ES module, we can consider to remove this file and use lodash-es | ||
*/ | ||
|
||
type Debounced<T> = T & {cancel: () => void}; | ||
|
||
export const debounce = <T extends (...args: Array<any>) => any>( | ||
func: T, | ||
wait: number, | ||
options: { | ||
leading?: boolean; | ||
maxWait?: number; | ||
} = {} | ||
): Debounced<T> => { | ||
let debounceTimeoutId: ReturnType<typeof setTimeout> | undefined; | ||
let maxWaitTimeoutId: ReturnType<typeof setTimeout> | undefined; | ||
let currentArgs: Parameters<T>; | ||
let isLeading = true; | ||
|
||
const debounced = (...args: Parameters<T>) => { | ||
if (debounceTimeoutId) { | ||
clearTimeout(debounceTimeoutId); | ||
} | ||
|
||
currentArgs = args; | ||
|
||
if (!maxWaitTimeoutId && options.maxWait) { | ||
maxWaitTimeoutId = setTimeout(() => { | ||
func(...currentArgs); | ||
maxWaitTimeoutId = undefined; | ||
clearTimeout(debounceTimeoutId); | ||
}, options.maxWait); | ||
} | ||
|
||
if (isLeading && options.leading) { | ||
isLeading = false; | ||
func(...args); | ||
return; | ||
} | ||
|
||
debounceTimeoutId = setTimeout(() => { | ||
func(...args); | ||
if (maxWaitTimeoutId) { | ||
clearTimeout(maxWaitTimeoutId); | ||
} | ||
debounceTimeoutId = undefined; | ||
maxWaitTimeoutId = undefined; | ||
// eslint-disable-next-line testing-library/await-async-utils | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. false positive. I've created an issue in js-toolbox to track these problems https://github.com/Telefonica/js-toolbox/issues/new |
||
}, wait); | ||
}; | ||
|
||
debounced.cancel = () => { | ||
if (debounceTimeoutId) { | ||
clearTimeout(debounceTimeoutId); | ||
debounceTimeoutId = undefined; | ||
} | ||
if (maxWaitTimeoutId) { | ||
clearTimeout(maxWaitTimeoutId); | ||
maxWaitTimeoutId = undefined; | ||
} | ||
}; | ||
|
||
return debounced as Debounced<T>; | ||
}; | ||
|
||
const isPrimitive = (v: unknown): v is string | number | undefined | null | boolean | symbol => { | ||
if (v === null) { | ||
return true; | ||
} | ||
if (typeof v === 'object' || typeof v === 'function') { | ||
return false; | ||
} | ||
return true; | ||
}; | ||
|
||
export const isEqual = (a: unknown, b: unknown): boolean => { | ||
if (a === b) { | ||
return true; | ||
} | ||
|
||
if (isPrimitive(a) || isPrimitive(b)) { | ||
return false; | ||
} | ||
|
||
if (typeof a !== typeof b) { | ||
return false; | ||
} | ||
|
||
if (typeof a === 'function') { | ||
// no need to check typeof b === 'function' because of the previous check | ||
return false; | ||
} | ||
|
||
if (Array.isArray(a) || Array.isArray(b)) { | ||
if (Array.isArray(a) && Array.isArray(b)) { | ||
return a.length === b.length && a.every((value, index) => isEqual(value, b[index])); | ||
} | ||
return false; | ||
} | ||
|
||
if (a instanceof Date || b instanceof Date) { | ||
if (a instanceof Date && b instanceof Date) { | ||
return a.getTime() === b.getTime(); | ||
} | ||
return false; | ||
} | ||
|
||
const keysA = Object.keys(a as any); | ||
const keysB = Object.keys(b as any); | ||
if (keysA.length === keysB.length) { | ||
return keysA.every((key) => isEqual((a as any)[key], (b as any)[key])); | ||
} | ||
|
||
return false; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,13 @@ export default defineConfig({ | |
fileNames: ({name}) => `${name.replace(/\.css$/, '.css-mistica')}.js`, | ||
}), | ||
], | ||
resolve: { | ||
alias: { | ||
// forbid lodash usage | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice trick |
||
lodash: '/dev/null', | ||
'lodash-es': '/dev/null', | ||
}, | ||
}, | ||
publicDir: false, | ||
build: { | ||
lib: { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moved to devDependencies