-
Notifications
You must be signed in to change notification settings - Fork 60
[UIK-3464][time-picker] rewrote component to ts/refactoring time picker component/added unit tests for core time picker elements #2641
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
Open
slizhevskyv-semrush
wants to merge
33
commits into
release/v16
Choose a base branch
from
UIK-3464/time-picker-on-change-api
base: release/v16
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
2190694
[UIK-4606][notice-global] deprecate component (#2630)
slizhevskyv-semrush 8fea293
[UIK-4623][data-table] fixed virtual scroll for tables with unknown r…
ilyabrower da43009
[carousel] fixed indicator style (#2626)
sheila-semrush 829f8ba
UIK-4610/allure (#2639)
Valeria-Zimnitskaya a2ee282
[drag-and-drop] skip unstable test steps for firefox
Valeria-Zimnitskaya df8bbce
[drag-and-drop] fix unstable test
Valeria-Zimnitskaya 9d403cd
[UIK-4619][chore] added translations (#2640)
ilyabrower 37f1e68
[chore] fixed deps generator in ui package
ilyabrower 1357916
[chore] Merge branch 'release/v16' of github.com:semrush/intergalacti…
ilyabrower a15eb8c
[chore] moved closeTasks and sendReleaseNotes to separate tasks
ilyabrower efc2f96
[chore] moved publish release notes to separate tasks
ilyabrower f0715f3
[UIK-3464][time-picker] rewrote component to ts/refactoring time pick…
slizhevskyv-semrush 647997e
[UIK-4606][notice-global] deprecate component (#2630)
slizhevskyv-semrush 21779f9
[UIK-4623][data-table] fixed virtual scroll for tables with unknown r…
ilyabrower 6c80b3f
[carousel] fixed indicator style (#2626)
sheila-semrush d4babde
UIK-4610/allure (#2639)
Valeria-Zimnitskaya c3af396
[drag-and-drop] skip unstable test steps for firefox
Valeria-Zimnitskaya 24d9d07
[chore] fixed deps generator in ui package
ilyabrower 822c5fe
[drag-and-drop] fix unstable test
Valeria-Zimnitskaya e80a828
[UIK-4619][chore] added translations (#2640)
ilyabrower 2fc4135
[chore] moved closeTasks and sendReleaseNotes to separate tasks
ilyabrower 11d7394
[chore] moved publish release notes to separate tasks
ilyabrower 199b49f
[data-table] skip test for webkit
Valeria-Zimnitskaya 3e555a6
Merge remote-tracking branch 'origin/release/v16' into UIK-3464/time-…
slizhevskyv-semrush 6630411
[UIK-3464][time-picker] added reactive/changeOnProps decorators and u…
slizhevskyv-semrush 3dc1d2d
Merge remote-tracking branch 'origin/release/v16' into UIK-3464/time-…
slizhevskyv-semrush 18b5dd4
[UIK-3464][core] uncommented test for @callOnPropsChange
slizhevskyv-semrush dd31537
[UIK-3464][time-picker] updated tests/small pr fixes
slizhevskyv-semrush 633823c
[UIK-3464][time-picker] new trackPropsChanges decorator for class/uni…
slizhevskyv-semrush 1b976c4
[UIK-3464][time-picker] updated import to @semcore/core
slizhevskyv-semrush 63df0d2
[UIK-3464][time-picker] removed watchProps references
slizhevskyv-semrush ad658b0
[UIK-3464][time-picker] removed watchProps references
slizhevskyv-semrush 442740d
[UIK-3464][time-picker] updated unit tests
slizhevskyv-semrush File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| type Primitive = string | number | boolean | symbol | bigint | null | undefined; | ||
| type IsReadonly<This, Property extends keyof This> = | ||
| (<F>() => F extends { [P in Property]: This[Property] } ? 1 : 2) extends | ||
| (<F>() => F extends { -readonly [P in Property]: This[Property] } ? 1 : 2) | ||
| ? false | ||
| : true; | ||
| type Callback<This> = (this: This, field: string | symbol, newValue: any) => void; | ||
| type ReturnType< | ||
| This, | ||
| Property extends keyof This = keyof This, | ||
| > = IsReadonly<This, Property> extends true | ||
| ? (_: undefined, ctx: ClassFieldDecoratorContext<This, This[Property]>) => void | ||
| : This[Property] extends Primitive | ||
| ? (_: undefined, ctx: ClassFieldDecoratorContext<This, This[Property]>) => void | ||
| : never; | ||
|
|
||
| const isPrimitiveValue = (value: any) => value !== Object(value); | ||
|
|
||
| function reactive<This>(cb: Callback<This>): ReturnType<This>; | ||
| function reactive< | ||
| This, | ||
| Property extends keyof This, | ||
| Value = This[Property], | ||
| >(watchedFields: Value extends Primitive ? never : Array<keyof Value>, cb: Callback<This>): ReturnType<This>; | ||
|
|
||
| function reactive< | ||
| This, | ||
| Property extends keyof This, | ||
| >(watchedFieldsOrCb: Array<keyof This[Property]> | Callback<This>, cb?: Callback<This>) { | ||
| return function (_: undefined, ctx: ClassFieldDecoratorContext<This, This[Property]>) { | ||
| const { addInitializer, name } = ctx; | ||
|
|
||
| addInitializer(function (this: This) { | ||
| const thisRoot = this; | ||
|
|
||
| const isPrimitive = isPrimitiveValue(this[name as Property]); | ||
|
|
||
| const callback = typeof watchedFieldsOrCb === 'function' ? watchedFieldsOrCb : cb!; | ||
| const fields = Array.isArray(watchedFieldsOrCb) ? watchedFieldsOrCb : null; | ||
|
|
||
| if (isPrimitive) { | ||
| let value = this[name as Property]; | ||
|
|
||
| Object.defineProperty(this, name, { | ||
| get() { | ||
| return value; | ||
| }, | ||
| set(newValue) { | ||
| const oldValue = value; | ||
|
|
||
| value = newValue; | ||
|
|
||
| if (oldValue !== newValue) { | ||
| callback.call(thisRoot, name, newValue); | ||
| } | ||
| }, | ||
| enumerable: true, | ||
| configurable: true, | ||
| }); | ||
| } else { | ||
| // @ts-ignore | ||
| this[name] = new Proxy(this[name], { | ||
| set(target, p, newValue) { | ||
| target[p] = newValue; | ||
|
|
||
| if (fields === null || fields.includes(p as keyof This[Property])) { | ||
| callback.call(thisRoot, p, newValue); | ||
| } | ||
|
|
||
| return true; | ||
| }, | ||
| }); | ||
| } | ||
| }); | ||
| }; | ||
| } | ||
|
|
||
| export default reactive; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| type WatchedProps<Props> = { [key in keyof Props]?: any }; | ||
|
|
||
| type Constructor<Props> = new (...args: any) => { | ||
| props: Props; | ||
| onPropsChange(changedProps: WatchedProps<Props>): void; | ||
| render(): any; | ||
| }; | ||
|
|
||
| function trackPropsChanges< | ||
| P, | ||
| C extends Constructor<P> = Constructor<P>, | ||
| >(propsToWatch: Array<keyof P>) { | ||
| const watchedProps: WatchedProps<P> = {}; | ||
|
|
||
| return function (Class: C) { | ||
| return class extends Class { | ||
| constructor(...args: any[]) { | ||
| super(...args); | ||
|
|
||
| propsToWatch.reduce((acc, prop) => { | ||
| acc[prop] = this.props?.[prop]; | ||
|
|
||
| return acc; | ||
| }, watchedProps); | ||
| } | ||
|
|
||
| onPropsChange(_?: WatchedProps<P>) { | ||
| let shouldCallFunc = false; | ||
| const changedProps: WatchedProps<P> = {}; | ||
|
|
||
| propsToWatch.forEach((prop) => { | ||
| const isPropValueEqual = Object.is(watchedProps[prop], this.props[prop]); | ||
|
|
||
| if (!isPropValueEqual) { | ||
| watchedProps[prop] = this.props[prop]; | ||
| changedProps[prop] = this.props[prop]; | ||
|
|
||
| shouldCallFunc = true; | ||
| } | ||
| }); | ||
|
|
||
| if (!shouldCallFunc) return; | ||
|
|
||
| super.onPropsChange(changedProps); | ||
| } | ||
|
|
||
| render() { | ||
| this.onPropsChange(); | ||
|
|
||
| return super.render(); | ||
| } | ||
| }; | ||
| }; | ||
| } | ||
|
|
||
| export default trackPropsChanges; |
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,231 @@ | ||
| import { runDependencyCheckTests } from '@semcore/testing-utils/shared-tests'; | ||
| import { describe, it, expect } from '@semcore/testing-utils/vitest'; | ||
|
|
||
| import TimePickerEntity from '../src/entity/TimePickerEntity'; | ||
|
|
||
| describe('time-picker Dependency imports', () => { | ||
| runDependencyCheckTests('time-picker'); | ||
| }); | ||
|
|
||
| describe('TimePickerEntity', () => { | ||
| describe('constructor', () => { | ||
| it('should initialize with default empty time when no value provided', () => { | ||
| const entity = new TimePickerEntity(':', false); | ||
|
|
||
| expect(entity.hours).toBe(''); | ||
| expect(entity.minutes).toBe(''); | ||
| }); | ||
|
|
||
| it('should parse hours and minutes from value string', () => { | ||
| const entity = new TimePickerEntity('14:30', false); | ||
|
|
||
| expect(entity.hours).toBe('14'); | ||
| expect(entity.minutes).toBe('30'); | ||
| }); | ||
|
|
||
| it('should handle single digit hours and minutes', () => { | ||
| const entity = new TimePickerEntity('9:5', false); | ||
|
|
||
| expect(entity.hours).toBe('09'); | ||
| expect(entity.minutes).toBe('05'); | ||
| }); | ||
|
|
||
| it('should initialize with AM meridiem by default', () => { | ||
| const entity = new TimePickerEntity('10:00', true); | ||
|
|
||
| expect(entity.meridiem).toBe('AM'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('12-hour format', () => { | ||
| it('should format midnight (00:00) as 12:00 AM', () => { | ||
| const entity = new TimePickerEntity('0:00', true); | ||
|
|
||
| expect(entity.hours).toBe('12'); | ||
| expect(entity.minutes).toBe('00'); | ||
| }); | ||
|
|
||
| it('should format hours 1-11 AM correctly', () => { | ||
| const entity = new TimePickerEntity('9:30', true); | ||
|
|
||
| expect(entity.hours).toBe('09'); | ||
| }); | ||
|
|
||
| it('should format noon (12:00) as 12:00', () => { | ||
| const entity = new TimePickerEntity('12:00', true); | ||
|
|
||
| expect(entity.hours).toBe('12'); | ||
| }); | ||
|
|
||
| it('should format hours 13-23 as 1-11 PM', () => { | ||
| const entity = new TimePickerEntity('14:45', true); | ||
|
|
||
| expect(entity.hours).toBe('02'); | ||
| }); | ||
|
|
||
| it('should format 23:59 as 11:59', () => { | ||
| const entity = new TimePickerEntity('23:59', true); | ||
|
|
||
| expect(entity.hours).toBe('11'); | ||
| expect(entity.minutes).toBe('59'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('24-hour format', () => { | ||
| it('should format hours with leading zero in 24-hour mode', () => { | ||
| const entity = new TimePickerEntity('9:30', false); | ||
|
|
||
| expect(entity.hours).toBe('09'); | ||
| }); | ||
|
|
||
| it('should handle midnight in 24-hour format', () => { | ||
| const entity = new TimePickerEntity('0:00', false); | ||
|
|
||
| expect(entity.hours).toBe('00'); | ||
| }); | ||
|
|
||
| it('should convert 12 AM to 00:00 in 24-hour format', () => { | ||
| const entity = new TimePickerEntity('12:00', false); | ||
| entity.meridiem = 'AM'; | ||
|
|
||
| expect(entity.hours).toBe('00'); | ||
| }); | ||
|
|
||
| it('should convert 12 PM to 12:00 in 24-hour format', () => { | ||
| const entity = new TimePickerEntity('12:00', false); | ||
| entity.meridiem = 'PM'; | ||
|
|
||
| expect(entity.hours).toBe('12'); | ||
| }); | ||
|
|
||
| it('should convert PM hours correctly', () => { | ||
| const entity = new TimePickerEntity('3:00', false); | ||
| entity.meridiem = 'PM'; | ||
|
|
||
| expect(entity.hours).toBe('15'); | ||
| }); | ||
|
|
||
| it('should keep AM hours unchanged (except 12)', () => { | ||
| const entity = new TimePickerEntity('9:00', false); | ||
| entity.meridiem = 'AM'; | ||
|
|
||
| expect(entity.hours).toBe('09'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('getters and setters', () => { | ||
| it('should update hours via setter', () => { | ||
| const entity = new TimePickerEntity('10:00', false); | ||
| entity.hours = '15'; | ||
|
|
||
| expect(entity.hours).toBe('15'); | ||
| }); | ||
|
|
||
| it('should update minutes via setter', () => { | ||
| const entity = new TimePickerEntity('10:00', false); | ||
| entity.minutes = '45'; | ||
|
|
||
| expect(entity.minutes).toBe('45'); | ||
| }); | ||
|
|
||
| it('should update meridiem via setter', () => { | ||
| const entity = new TimePickerEntity('10:00', true); | ||
| entity.meridiem = 'PM'; | ||
|
|
||
| expect(entity.meridiem).toBe('PM'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('toggleMeridiem', () => { | ||
| it('should toggle from AM to PM', () => { | ||
| const entity = new TimePickerEntity('10:00', true); | ||
|
|
||
| entity.toggleMeridiem(); | ||
|
|
||
| expect(entity.meridiem).toBe('PM'); | ||
| }); | ||
|
|
||
| it('should toggle from PM to AM', () => { | ||
| const entity = new TimePickerEntity('10:00', true); | ||
| entity.meridiem = 'PM'; | ||
|
|
||
| entity.toggleMeridiem(); | ||
|
|
||
| expect(entity.meridiem).toBe('AM'); | ||
| }); | ||
|
|
||
| it('should toggle multiple times correctly', () => { | ||
| const entity = new TimePickerEntity('10:00', true); | ||
|
|
||
| entity.toggleMeridiem(); | ||
| expect(entity.meridiem).toBe('PM'); | ||
|
|
||
| entity.toggleMeridiem(); | ||
| expect(entity.meridiem).toBe('AM'); | ||
|
|
||
| entity.toggleMeridiem(); | ||
| expect(entity.meridiem).toBe('PM'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('toString', () => { | ||
| it('should return 24-hour format string when is12Hour is false', () => { | ||
| const entity = new TimePickerEntity('14:30', false); | ||
|
|
||
| expect(entity.toString()).toBe('14:30'); | ||
| }); | ||
|
|
||
| it('should convert to 24-hour format string when is12Hour is true', () => { | ||
| const entity = new TimePickerEntity('2:30', true); | ||
| entity.meridiem = 'PM'; | ||
|
|
||
| expect(entity.toString()).toBe('14:30'); | ||
| }); | ||
|
|
||
| it('should handle midnight (12 AM) conversion', () => { | ||
| const entity = new TimePickerEntity('12:00', true); | ||
| entity.meridiem = 'AM'; | ||
|
|
||
| expect(entity.toString()).toBe('00:00'); | ||
| }); | ||
|
|
||
| it('should handle noon (12 PM) conversion', () => { | ||
| const entity = new TimePickerEntity('12:00', true); | ||
| entity.meridiem = 'PM'; | ||
|
|
||
| expect(entity.toString()).toBe('12:00'); | ||
| }); | ||
|
|
||
| it('should add leading zeros to output', () => { | ||
| const entity = new TimePickerEntity('9:5', false); | ||
|
|
||
| expect(entity.toString()).toBe('09:05'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('edge cases', () => { | ||
| it('should handle invalid hours gracefully', () => { | ||
| const entity = new TimePickerEntity('invalid:30', false); | ||
|
|
||
| expect(entity.hours).toBe('invalid'); | ||
| }); | ||
|
|
||
| it('should handle empty minutes', () => { | ||
| const entity = new TimePickerEntity('10:', false); | ||
|
|
||
| expect(entity.minutes).toBe(''); | ||
| }); | ||
|
|
||
| it('should handle empty hours', () => { | ||
| const entity = new TimePickerEntity(':30', false); | ||
|
|
||
| expect(entity.hours).toBe(''); | ||
| }); | ||
|
|
||
| it('should preserve non-numeric hour values in 12-hour format', () => { | ||
| const entity = new TimePickerEntity('invalid:30', true); | ||
|
|
||
| expect(entity.hours).toBe('invalid'); | ||
| }); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
I think we can not to use
symbolhere