playbook_ui 16.2.0.pre.alpha.advancedtablecascadingcollapsereact14676 → 16.2.0.pre.alpha.faiconbuttonfix14520

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_advanced_table/Components/CustomCell.tsx +4 -17
  3. data/app/pb_kits/playbook/pb_advanced_table/Components/TableHeaderCell.tsx +1 -3
  4. data/app/pb_kits/playbook/pb_advanced_table/Hooks/useTableActions.ts +9 -21
  5. data/app/pb_kits/playbook/pb_advanced_table/Utilities/ExpansionControlHelpers.tsx +1 -25
  6. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.tsx +1 -5
  7. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +1 -74
  8. data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +0 -1
  9. data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +1 -2
  10. data/app/pb_kits/playbook/pb_collapsible/index.js +4 -16
  11. data/app/pb_kits/playbook/pb_date_picker/_date_picker.tsx +1 -4
  12. data/app/pb_kits/playbook/pb_date_picker/date_picker.html.erb +2 -2
  13. data/app/pb_kits/playbook/pb_dialog/index.js +5 -45
  14. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +0 -2
  15. data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +2 -2
  16. data/app/pb_kits/playbook/pb_dropdown/dropdown.test.jsx +1 -1
  17. data/app/pb_kits/playbook/pb_dropdown/index.js +13 -68
  18. data/app/pb_kits/playbook/pb_dropdown/keyboard_accessibility.js +3 -19
  19. data/app/pb_kits/playbook/pb_enhanced_element/element_observer.ts +1 -1
  20. data/app/pb_kits/playbook/pb_enhanced_element/index.ts +1 -2
  21. data/app/pb_kits/playbook/pb_icon/icon.rb +19 -168
  22. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +0 -2
  23. data/app/pb_kits/playbook/pb_multi_level_select/docs/example.yml +0 -1
  24. data/app/pb_kits/playbook/pb_multi_level_select/docs/index.js +0 -1
  25. data/app/pb_kits/playbook/pb_pagination/_pagination.scss +1 -101
  26. data/app/pb_kits/playbook/pb_pagination/_pagination.test.jsx +1 -172
  27. data/app/pb_kits/playbook/pb_pagination/_pagination.tsx +15 -178
  28. data/app/pb_kits/playbook/pb_pagination/docs/_pagination_default.jsx +1 -1
  29. data/app/pb_kits/playbook/pb_passphrase/_passphrase.tsx +0 -4
  30. data/app/pb_kits/playbook/pb_rich_text_editor/_rich_text_editor.tsx +0 -2
  31. data/app/pb_kits/playbook/pb_select/_select.tsx +0 -2
  32. data/app/pb_kits/playbook/pb_select/select.html.erb +2 -2
  33. data/app/pb_kits/playbook/pb_star_rating/star_rating.html.erb +1 -1
  34. data/app/pb_kits/playbook/pb_star_rating/subcomponents/_star_rating_interactive.tsx +0 -1
  35. data/app/pb_kits/playbook/pb_text_input/_text_input.tsx +0 -2
  36. data/app/pb_kits/playbook/pb_text_input/text_input.html.erb +2 -2
  37. data/app/pb_kits/playbook/pb_textarea/_textarea.tsx +21 -43
  38. data/app/pb_kits/playbook/pb_textarea/docs/example.yml +0 -1
  39. data/app/pb_kits/playbook/pb_textarea/docs/index.js +8 -9
  40. data/app/pb_kits/playbook/pb_textarea/textarea.html.erb +4 -4
  41. data/app/pb_kits/playbook/pb_textarea/textarea.rb +2 -6
  42. data/app/pb_kits/playbook/pb_textarea/textarea.test.js +1 -134
  43. data/app/pb_kits/playbook/pb_time_picker/_time_picker.tsx +0 -6
  44. data/app/pb_kits/playbook/pb_time_picker/time_picker.test.jsx +2 -2
  45. data/app/pb_kits/playbook/pb_tooltip/index.js +15 -60
  46. data/app/pb_kits/playbook/pb_typeahead/_typeahead.test.jsx +1 -1
  47. data/dist/chunks/{_pb_line_graph-BGY7jEks.js → _pb_line_graph-BSLb5VXP.js} +1 -1
  48. data/dist/chunks/{_typeahead-QhswHQnq.js → _typeahead-DXIBDeMj.js} +1 -1
  49. data/dist/chunks/{globalProps-CK2YuA9O.js → globalProps-DyTB8IdV.js} +1 -1
  50. data/dist/chunks/{lib-DspaUdlc.js → lib-9wz3x5jl.js} +1 -1
  51. data/dist/chunks/vendor.js +5 -5
  52. data/dist/menu.yml +1 -1
  53. data/dist/playbook-rails-react-bindings.js +1 -1
  54. data/dist/playbook-rails.js +1 -1
  55. data/dist/playbook.css +1 -1
  56. data/lib/playbook/forms/builder/checkbox_field.rb +1 -1
  57. data/lib/playbook/version.rb +1 -1
  58. metadata +6 -12
  59. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_cascade_collapse.jsx +0 -50
  60. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_cascade_collapse.md +0 -1
  61. data/app/pb_kits/playbook/pb_kit_registry/index.ts +0 -180
  62. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_react_reset_key.jsx +0 -100
  63. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_react_reset_key.md +0 -1
  64. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_input_options.jsx +0 -68
@@ -23,7 +23,7 @@ export default class ElementObserver {
23
23
  }
24
24
 
25
25
  stop(): void {
26
- this.mutationObserver.disconnect()
26
+ this.mutationObserverdisconnect()
27
27
  }
28
28
 
29
29
  catchup(): void {
@@ -50,7 +50,6 @@ export default class PbEnhancedElement {
50
50
  const enhansedElement = this.elements.get(element)
51
51
  enhansedElement.disconnect()
52
52
  this.elements.delete(element)
53
- delete element._pbEnhanced
54
53
  }
55
54
 
56
55
  static start(): void {
@@ -58,7 +57,7 @@ export default class PbEnhancedElement {
58
57
  }
59
58
 
60
59
  static stop(): void {
61
- this.observer.stop()
60
+ this.mutationObserver.stop()
62
61
  }
63
62
 
64
63
  connect(): void {
@@ -2,14 +2,10 @@
2
2
 
3
3
  require "open-uri"
4
4
  require "json"
5
- require "digest"
6
5
 
7
6
  module Playbook
8
7
  module PbIcon
9
8
  class Icon < Playbook::KitBase
10
- ICON_PATH_DEV_CACHE_TTL_SECONDS = 2
11
- ICON_PATH_PROD_CACHE_TTL_SECONDS = 60
12
-
13
9
  prop :border, type: Playbook::Props::Boolean,
14
10
  default: false
15
11
  prop :fixed_width, type: Playbook::Props::Boolean,
@@ -86,35 +82,30 @@ module Playbook
86
82
  )
87
83
  end
88
84
 
89
- # Instance-level memoization of alias map lookup result
90
85
  def icon_alias_map
91
- return @icon_alias_map if defined?(@icon_alias_map)
86
+ return unless Rails.application.config.respond_to?(:icon_alias_path)
92
87
 
93
- @icon_alias_map = self.class.icon_alias_map
88
+ base_path = Rails.application.config.icon_alias_path
89
+ json = File.read(Rails.root.join(base_path))
90
+ JSON.parse(json)["aliases"].freeze
94
91
  end
95
92
 
96
- # Instance-level memoization of resolved asset path
97
93
  def asset_path
98
- return @asset_path if defined?(@asset_path)
99
-
100
- @asset_path =
101
- if Rails.application.config.respond_to?(:icon_path)
102
- resolved_icon = resolve_alias(icon)
103
- path = self.class.icon_path_index[resolved_icon]
104
- path if path && File.exist?(path)
105
- end
106
- end
107
-
108
- def is_svg?
109
- return @is_svg if defined?(@is_svg)
94
+ return unless Rails.application.config.respond_to?(:icon_path)
110
95
 
111
- @is_svg = (icon || custom_icon.to_s).include?(".svg") || asset_path.present?
96
+ base_path = Rails.application.config.icon_path
97
+ resolved_icon = resolve_alias(icon)
98
+ icon_path = Dir.glob(Rails.root.join(base_path, "**", "#{resolved_icon}.svg")).first
99
+ icon_path if icon_path && File.exist?(icon_path)
112
100
  end
113
101
 
114
102
  def render_svg
115
103
  doc = Nokogiri::XML(URI.open(asset_path || icon || custom_icon)) # rubocop:disable Security/Open
116
- svg = doc.at_css("svg")
117
- return "" unless svg
104
+ svg = doc.at_css "svg"
105
+
106
+ unless svg
107
+ return "" # Return an empty string if SVG element is not found
108
+ end
118
109
 
119
110
  svg["class"] = %w[pb_custom_icon svg-inline--fa].concat([object.custom_icon_classname]).join(" ")
120
111
  svg["id"] = object.id
@@ -122,9 +113,7 @@ module Playbook
122
113
  svg["width"] = "auto"
123
114
  svg["tabindex"] = object.tabindex
124
115
  fill_color = object.color || "currentColor"
125
-
126
- # Safely apply fill to all paths (avoids nil errors + handles multi-path icons)
127
- doc.css("path").each { |p| p["fill"] = fill_color }
116
+ doc.at_css("path")["fill"] = fill_color
128
117
 
129
118
  if object.data.present?
130
119
  object.data.each do |key, value|
@@ -146,152 +135,14 @@ module Playbook
146
135
  ""
147
136
  end
148
137
 
149
- # Class-level caches
150
- class << self
151
- @cache_mutex = Mutex.new
152
-
153
- # Cache aliases.json across the process, but invalidate when the file changes (dev-safe)
154
- def icon_alias_map
155
- return @icon_alias_map if alias_cache_fresh?
156
-
157
- cache_mutex.synchronize do
158
- return @icon_alias_map if alias_cache_fresh?
159
-
160
- @icon_alias_map =
161
- if Rails.application.config.respond_to?(:icon_alias_path)
162
- base_path = Rails.application.config.icon_alias_path
163
- full_path = Rails.root.join(base_path)
164
- @icon_alias_map_mtime = safe_mtime(full_path)
165
-
166
- json = File.read(full_path)
167
- parsed = JSON.parse(json)
168
- parsed.fetch("aliases", {}).freeze
169
- end
170
- end
171
-
172
- @icon_alias_map
173
- end
174
-
175
- # Cache an index of icon_name to file path for all SVGs in the configured directory, with invalidation based on directory mtime
176
- # Avoids recursive Dir.glob for every icon render
177
- def icon_path_index
178
- return @icon_path_index if index_cache_fresh?
179
-
180
- cache_mutex.synchronize do
181
- return @icon_path_index if index_cache_fresh?
182
-
183
- @icon_path_index =
184
- if Rails.application.config.respond_to?(:icon_path)
185
- base_path = Rails.application.config.icon_path
186
- root = Rails.root.join(base_path)
187
-
188
- # If path doesn't exist, keep behavior aligned (no path resolution)
189
- if Dir.exist?(root)
190
- @icon_path_index_cache_key = icon_path_cache_key(root)
191
-
192
- # One scan builds the map for O(1) lookups
193
- # Key is the filename (without .svg) to match existing usage
194
- index = {}
195
- Dir.glob(File.join(root.to_s, "**", "*.svg")).sort.each do |p|
196
- name = File.basename(p, ".svg")
197
- index[name] ||= p
198
- end
199
- index.freeze
200
- else
201
- @icon_path_index_cache_key = nil
202
- {}
203
- end
204
- else
205
- {}
206
- end
207
-
208
- @icon_path_index_checked_at = monotonic_now
209
- end
210
-
211
- @icon_path_index
212
- end
213
-
214
- private
215
-
216
- def cache_mutex
217
- @cache_mutex ||= Mutex.new
218
- end
219
-
220
- def alias_cache_fresh?
221
- return false unless defined?(@icon_alias_map)
222
-
223
- return true unless Rails.application.config.respond_to?(:icon_alias_path)
224
-
225
- full_path = Rails.root.join(Rails.application.config.icon_alias_path)
226
- safe_mtime(full_path) == @icon_alias_map_mtime
227
- rescue
228
- false
229
- end
230
-
231
- def index_cache_fresh?
232
- return false unless defined?(@icon_path_index)
233
-
234
- return true unless Rails.application.config.respond_to?(:icon_path)
235
-
236
- # In development and production, skip re-checks for a short TTL window
237
- # to avoid repeated tree scans on hot paths.
238
- return true if Rails.env.development? && within_icon_index_ttl?(ICON_PATH_DEV_CACHE_TTL_SECONDS)
239
- return true if Rails.env.production? && within_icon_index_ttl?(ICON_PATH_PROD_CACHE_TTL_SECONDS)
240
-
241
- root = Rails.root.join(Rails.application.config.icon_path)
242
- fresh = icon_path_cache_key(root) == @icon_path_index_cache_key
243
- @icon_path_index_checked_at = monotonic_now
244
- fresh
245
- rescue
246
- false
247
- end
248
-
249
- def within_icon_index_ttl?(ttl_seconds)
250
- return false unless defined?(@icon_path_index_checked_at)
251
-
252
- (monotonic_now - @icon_path_index_checked_at) < ttl_seconds
253
- rescue
254
- false
255
- end
256
-
257
- def monotonic_now
258
- Process.clock_gettime(Process::CLOCK_MONOTONIC)
259
- end
260
-
261
- def icon_path_cache_key(root)
262
- return safe_mtime(root) unless Rails.env.development? || Rails.env.production?
263
-
264
- digest = Digest::SHA1.new
265
- root_prefix = "#{root}/"
266
-
267
- Dir.glob(File.join(root.to_s, "**", "*.svg")).sort.each do |path|
268
- digest << path.delete_prefix(root_prefix)
269
- next unless Rails.env.development?
270
-
271
- # Development tracks file metadata for rapid local edits.
272
- # Production only needs path-set change detection during periodic checks.
273
- stat = File.stat(path)
274
- digest << stat.mtime.to_f.to_s
275
- digest << stat.size.to_s
276
- end
277
-
278
- digest.hexdigest
279
- rescue
280
- nil
281
- end
282
-
283
- def safe_mtime(path)
284
- File.exist?(path) ? File.mtime(path) : nil
285
- rescue
286
- nil
287
- end
138
+ def is_svg?
139
+ (icon || custom_icon.to_s).include?(".svg") || asset_path.present?
288
140
  end
289
141
 
290
142
  private
291
143
 
292
144
  def resolve_alias(icon)
293
145
  return icon unless icon_alias_map
294
- return icon if icon.nil?
295
146
 
296
147
  aliases = icon_alias_map[icon]
297
148
  return icon unless aliases
@@ -304,8 +155,8 @@ module Playbook
304
155
  end
305
156
 
306
157
  def file_exists?(alias_name)
307
- # Use the cached index (no recursive glob)
308
- self.class.icon_path_index.key?(alias_name)
158
+ base_path = Rails.application.config.icon_path
159
+ File.exist?(Dir.glob(Rails.root.join(base_path, "**", "#{alias_name}.svg")).first)
309
160
  end
310
161
 
311
162
  def svg_size
@@ -532,7 +532,6 @@ const MultiLevelSelect = forwardRef<HTMLInputElement, MultiLevelSelectProps>(
532
532
  {requiredIndicator ? (
533
533
  <Caption
534
534
  className="pb_multi_level_select_kit_label"
535
- color="lighter"
536
535
  marginBottom="xs"
537
536
  >
538
537
  {label} <span className="required_indicator">*</span>
@@ -540,7 +539,6 @@ const MultiLevelSelect = forwardRef<HTMLInputElement, MultiLevelSelectProps>(
540
539
  ) : (
541
540
  <Caption
542
541
  className="pb_multi_level_select_kit_label"
543
- color="lighter"
544
542
  marginBottom="xs"
545
543
  text={label}
546
544
  />
@@ -39,4 +39,3 @@ examples:
39
39
  - multi_level_select_disabled_options_parent: Disabled Parent Option (Return All Selected)
40
40
  - multi_level_select_single_disabled: Disabled Options (Single Select)
41
41
  - multi_level_select_required_indicator: Required Indicator
42
- - multi_level_select_react_reset_key: Reset with Key (React)
@@ -17,4 +17,3 @@ export { default as MultiLevelSelectDisabledOptionsDefault } from "./_multi_leve
17
17
  export { default as MultiLevelSelectLabel } from "./_multi_level_select_label.jsx"
18
18
  export { default as MultiLevelSelectSingleDisabled } from "./_multi_level_select_single_disabled.jsx"
19
19
  export { default as MultiLevelSelectRequiredIndicator } from "./_multi_level_select_required_indicator.jsx"
20
- export { default as MultiLevelSelectReactResetKey } from "./_multi_level_select_react_reset_key.jsx"
@@ -73,9 +73,8 @@ $top_bottom_radius: 0px;
73
73
  }
74
74
  .disabled {
75
75
  pointer-events: none;
76
- cursor: not-allowed;
77
76
  opacity: 0.5;
78
- color: #B0BDC7; // replace with $text_disabled once added to tokens
77
+ color: grey;
79
78
 
80
79
  & > span {
81
80
  padding: $pagination_padding;
@@ -105,103 +104,4 @@ $top_bottom_radius: 0px;
105
104
  border-right: none;
106
105
  margin-left: $space_xxs;
107
106
  }
108
- }
109
-
110
- .pb_pagination_mobile {
111
- padding: $space_xxs 0px !important;
112
- position: relative;
113
-
114
- .pagination-right {
115
- border-left: none !important;
116
- padding: 6px 13px !important;
117
- flex-shrink: 0;
118
-
119
- @media (max-width: 435px) {
120
- padding: 6px 10px !important;
121
- }
122
- }
123
- .pagination-left {
124
- border-right: none !important;
125
- padding: 6px 13px !important;
126
- flex-shrink: 0;
127
-
128
- @media (max-width: 435px) {
129
- padding: 6px 10px !important;
130
- }
131
- }
132
- .pagination-dropdown {
133
- position: static;
134
- flex: 1;
135
- margin: 0 $space_xxs;
136
- min-width: 0;
137
- overflow: hidden;
138
- }
139
-
140
- .pagination-dropdown-trigger {
141
- padding: 6px 10px 6px 13px !important;
142
- background-color: $bg_light;
143
- border-radius: $border_rad_light;
144
- cursor: pointer;
145
- transition: all 0.2s ease;
146
- white-space: nowrap;
147
- overflow: hidden;
148
-
149
- &:hover {
150
- background-color: $active_light;
151
- }
152
-
153
- @media (max-width: 435px) {
154
- padding: 6px 8px 6px 10px !important;
155
- }
156
- }
157
-
158
- .pagination-dropdown-menu {
159
- position: absolute;
160
- left: 0;
161
- right: 0;
162
- background-color: $white;
163
- border: $border_rad_lightest solid $border_light;
164
- border-radius: $border_rad_light;
165
- box-shadow: $shadow_deep;
166
- max-height: 200px;
167
- overflow-y: auto;
168
- z-index: 1000;
169
-
170
- &.below {
171
- top: 100%;
172
- margin-top: $space_xs;
173
- }
174
-
175
- &.above {
176
- bottom: 100%;
177
- margin-bottom: $space_xs;
178
- }
179
- }
180
-
181
- .pagination-dropdown-option {
182
- padding: $pagination_padding;
183
- cursor: pointer;
184
- transition: background-color 0.2s ease;
185
-
186
- &:hover {
187
- background-color: $active_light;
188
- }
189
-
190
- &.active {
191
- background-color: $primary;
192
- .pb_body_kit {
193
- color: $white;
194
- }
195
- }
196
-
197
- &:first-child {
198
- border-top-left-radius: $border_rad_light;
199
- border-top-right-radius: $border_rad_light;
200
- }
201
-
202
- &:last-child {
203
- border-bottom-left-radius: $border_rad_light;
204
- border-bottom-right-radius: $border_rad_light;
205
- }
206
- }
207
107
  }
@@ -209,175 +209,4 @@ describe('Pagination Component', () => {
209
209
 
210
210
  expect(screen.getByText('19')).toBeInTheDocument()
211
211
  })
212
- })
213
-
214
- describe('Pagination Mobile View', () => {
215
- let originalInnerWidth
216
-
217
- beforeEach(() => {
218
- // Store original value
219
- originalInnerWidth = window.innerWidth
220
-
221
- // Mock window.innerWidth for mobile
222
- Object.defineProperty(window, 'innerWidth', {
223
- writable: true,
224
- configurable: true,
225
- value: 767
226
- })
227
- })
228
-
229
- afterEach(() => {
230
- // Restore original value
231
- Object.defineProperty(window, 'innerWidth', {
232
- writable: true,
233
- configurable: true,
234
- value: originalInnerWidth
235
- })
236
- })
237
-
238
- test('renders mobile layout on small screens', () => {
239
- render(<Pagination {...defaultProps} />)
240
-
241
- const mobilePagination = document.querySelector('.pb_pagination_mobile')
242
- expect(mobilePagination).toBeInTheDocument()
243
- })
244
-
245
- test('renders Prev and Next buttons in mobile view', () => {
246
- render(<Pagination {...defaultProps} />)
247
-
248
- expect(screen.getByText('Prev')).toBeInTheDocument()
249
- expect(screen.getByText('Next')).toBeInTheDocument()
250
- })
251
-
252
- test('displays current page and total in dropdown trigger', () => {
253
- render(<Pagination {...defaultProps}
254
- current={3}
255
- total={10}
256
- />)
257
-
258
- expect(screen.getByText('3', { exact: false })).toBeInTheDocument()
259
- expect(screen.getByText(/of 10/)).toBeInTheDocument()
260
- })
261
-
262
- test('opens dropdown when trigger is clicked', () => {
263
- render(<Pagination {...defaultProps} />)
264
-
265
- const dropdownTrigger = document.querySelector('.pagination-dropdown-trigger')
266
- expect(dropdownTrigger).toBeInTheDocument()
267
-
268
- let dropdownMenu = document.querySelector('.pagination-dropdown-menu')
269
- expect(dropdownMenu).not.toBeInTheDocument()
270
-
271
- fireEvent.click(dropdownTrigger)
272
-
273
- dropdownMenu = document.querySelector('.pagination-dropdown-menu')
274
- expect(dropdownMenu).toBeInTheDocument()
275
- })
276
-
277
- test('displays all page options in dropdown', () => {
278
- render(<Pagination {...defaultProps}
279
- total={5}
280
- />)
281
-
282
- const dropdownTrigger = document.querySelector('.pagination-dropdown-trigger')
283
- fireEvent.click(dropdownTrigger)
284
-
285
- expect(screen.getByText('Page 1')).toBeInTheDocument()
286
- expect(screen.getByText('Page 2')).toBeInTheDocument()
287
- expect(screen.getByText('Page 3')).toBeInTheDocument()
288
- expect(screen.getByText('Page 4')).toBeInTheDocument()
289
- expect(screen.getByText('Page 5')).toBeInTheDocument()
290
- })
291
-
292
- test('highlights current page in dropdown', () => {
293
- render(<Pagination {...defaultProps}
294
- current={3}
295
- total={5}
296
- />)
297
-
298
- const dropdownTrigger = document.querySelector('.pagination-dropdown-trigger')
299
- fireEvent.click(dropdownTrigger)
300
-
301
- const activePage = screen.getByText('Page 3').parentElement
302
- expect(activePage).toHaveClass('active')
303
- })
304
-
305
- test('changes page when dropdown option is clicked', () => {
306
- const mockOnChange = jest.fn()
307
- render(<Pagination {...defaultProps}
308
- onChange={mockOnChange}
309
- total={5}
310
- />)
311
-
312
- const dropdownTrigger = document.querySelector('.pagination-dropdown-trigger')
313
- fireEvent.click(dropdownTrigger)
314
-
315
- const page3Option = screen.getByText('Page 3')
316
- fireEvent.click(page3Option)
317
-
318
- expect(mockOnChange).toHaveBeenCalledWith(3)
319
- })
320
-
321
- test('closes dropdown after selecting a page', () => {
322
- render(<Pagination {...defaultProps}
323
- total={5}
324
- />)
325
-
326
- const dropdownTrigger = document.querySelector('.pagination-dropdown-trigger')
327
- fireEvent.click(dropdownTrigger)
328
-
329
- let dropdownMenu = document.querySelector('.pagination-dropdown-menu')
330
- expect(dropdownMenu).toBeInTheDocument()
331
-
332
- const page3Option = screen.getByText('Page 3')
333
- fireEvent.click(page3Option)
334
-
335
- dropdownMenu = document.querySelector('.pagination-dropdown-menu')
336
- expect(dropdownMenu).not.toBeInTheDocument()
337
- })
338
-
339
- test('Prev button navigates to previous page', () => {
340
- const mockOnChange = jest.fn()
341
- render(<Pagination {...defaultProps}
342
- current={3}
343
- onChange={mockOnChange}
344
- />)
345
-
346
- const prevButton = document.querySelector('.pagination-left')
347
- fireEvent.click(prevButton)
348
-
349
- expect(mockOnChange).toHaveBeenCalledWith(2)
350
- })
351
-
352
- test('Next button navigates to next page', () => {
353
- const mockOnChange = jest.fn()
354
- render(<Pagination {...defaultProps}
355
- current={3}
356
- onChange={mockOnChange}
357
- />)
358
-
359
- const nextButton = document.querySelector('.pagination-right')
360
- fireEvent.click(nextButton)
361
-
362
- expect(mockOnChange).toHaveBeenCalledWith(4)
363
- })
364
-
365
- test('disables Prev button on first page', () => {
366
- render(<Pagination {...defaultProps}
367
- current={1}
368
- />)
369
-
370
- const prevButton = document.querySelector('.pagination-left')
371
- expect(prevButton).toHaveClass('disabled')
372
- })
373
-
374
- test('disables Next button on last page', () => {
375
- render(<Pagination {...defaultProps}
376
- current={10}
377
- />)
378
-
379
- const nextButton = document.querySelector('.pagination-right')
380
- expect(nextButton).toHaveClass('disabled')
381
- })
382
-
383
- })
212
+ })